{ "cells": [ { "cell_type": "markdown", "id": "7033a0a9", "metadata": {}, "source": [ "# Using cloudknot to run pyAFQ on AWS batch:\n", "\n", "One of the purposes of `pyAFQ` is to analyze large-scale openly-available\n", "datasets, such as those in the\n", "[Human Connectome Project](https://www.humanconnectome.org/).\n", "\n", "To analyze these datasets, large amounts of compute are needed.\n", "One way to gain access to massive computational power is by using\n", "cloud computing. Here, we will demonstrate\n", "how to use `pyAFQ` in the Amazon Web Services cloud.\n", "\n", "We will rely on the [AWS Batch Service](https://aws.amazon.com/batch/),\n", "and we will submit work into AWS Batch using software that our group\n", "developed called [Cloudknot](https://nrdg.github.io/cloudknot/)." ] }, { "cell_type": "markdown", "id": "9fd19b7b", "metadata": {}, "source": [ "Import cloudknot and set the AWS region within which computations will take\n", "place. Setting a region is important, because if the data that you are\n", "analyzing is stored in `AWS S3 `_ in a\n", "particular region, it is best to run the computation in that region as well.\n", "That is because AWS charges for inter-region transfer of data." ] }, { "cell_type": "code", "execution_count": null, "id": "9481b3ba", "metadata": {}, "outputs": [], "source": [ "import cloudknot as ck\n", "ck.set_region('us-east-1')" ] }, { "cell_type": "markdown", "id": "a5d45193", "metadata": {}, "source": [ "## Define the function to use\n", "\n", "`Cloudknot` uses the single program multiple data paradigm of computing.\n", "This means that the same function will be run on multiple different inputs.\n", "For example, a `pyAFQ` processing function run\n", "on multiple different subjects in a dataset.\n", "Below, we define the function that we will use. Notice that\n", "`Cloudknot` functions include the import statements of the dependencies\n", "used. This is necessary so that `Cloudknot` knows\n", "what dependencies to install into AWS Batch to run this function." ] }, { "cell_type": "code", "execution_count": null, "id": "41dffb63", "metadata": {}, "outputs": [], "source": [ "def afq_process_subject(subject):\n", " # define a function that each job will run\n", " # In this case, each process does a single subject\n", " import s3fs\n", " # all imports must be at the top of the function\n", " # cloudknot installs the appropriate packages from pip\n", " from s3bids.utils import S3BIDSStudy\n", " from AFQ.api.group import GroupAFQ\n", " import AFQ.definitions.image as afm\n", "\n", " # Download the given subject to your local machine from s3\n", " # Can find subjects more easily if they are specified in a\n", " # BIDS participants.tsv file, even if it is sparse\n", " study_ixi = S3BIDSStudy(\n", " \"my_study\",\n", " \"my_study_bucket\",\n", " \"my_study_prefix\",\n", " subjects=[subject],\n", " use_participants_tsv=True,\n", " anon=False)\n", " study_ixi.download(\n", " \"local_bids_dir\",\n", " include_derivs=[\"pipeline_name\"])\n", "\n", " # define the api AFQ object\n", " myafq = GroupAFQ(\n", " \"local_bids_dir\",\n", " dwi_preproc_pipeline=\"pipeline_name\",\n", " viz_backend_spec='plotly') # this will generate both interactive html and MP4s # noqa\n", "\n", " # export_all runs the entire pipeline and creates many useful derivates\n", " myafq.export_all()\n", "\n", " # upload the results to some location on s3\n", " myafq.upload_to_s3(\n", " s3fs.S3FileSystem(),\n", " \"my_study_bucket/my_study_prefix/derivatives/afq\")" ] }, { "cell_type": "markdown", "id": "cdcbb6b3", "metadata": {}, "source": [ "Here we provide a list of subjects that we have selected to process\n", "to randomly select 3 subjects without replacement, instead do:\n", "subjects = [[1], [2], [3]]\n", "see the docstring for S3BIDSStudy.__init__ for more information" ] }, { "cell_type": "code", "execution_count": null, "id": "be7b3f04", "metadata": {}, "outputs": [], "source": [ "subjects = [\"123456\", \"123457\", \"123458\"]" ] }, { "cell_type": "markdown", "id": "de6a870a", "metadata": {}, "source": [ "## Defining a `Knot` instance\n", "\n", "We instantiate a class instance of the `ck.Knot` class.\n", "This object will be used to run your jobs.\n", "The object is instantiated with the `'AmazonS3FullAccess'` policy,\n", "so that it can write the results\n", "out to S3, into a bucket that you have write permissions on.\n", "Setting the `bid_percentage` key-word makes AWS Batch use\n", "[spot EC2 instances](https://aws.amazon.com/ec2/spot/) for the\n", "computation. This can result in substantial cost-savings, as spot compute\n", "instances can cost much less than on-demand instances.\n", "However, not that spot instances can also\n", "be evicted, so if completing all of the work is very time-sensitive,\n", "do not set this key-word argument. Using the `image_github_installs`\n", "key-word argument will install pyAFQ from GitHub.\n", "You can also specify other forks and branches to install from." ] }, { "cell_type": "code", "execution_count": null, "id": "e50903ea", "metadata": {}, "outputs": [], "source": [ "knot = ck.Knot(\n", " name='afq-process-subject-201009-0',\n", " func=afq_process_subject,\n", " base_image='python:3.11',\n", " image_github_installs=\"https://github.com/tractometry/pyAFQ.git\",\n", " pars_policies=('AmazonS3FullAccess',),\n", " bid_percentage=100)" ] }, { "cell_type": "markdown", "id": "1cfa3ddd", "metadata": {}, "source": [ "## Launching the computation\n", "\n", "The `map` method of the `Knot` object maps each of the inputs\n", "provided as a sequence onto the function and executes the function on each\n", "one of them in parallel." ] }, { "cell_type": "code", "execution_count": null, "id": "5341b35f", "metadata": {}, "outputs": [], "source": [ "result_futures = knot.map(subjects)" ] }, { "cell_type": "markdown", "id": "17ef9987", "metadata": {}, "source": [ "Once computations have started, you can call the following\n", "function to view the progress of jobs:\n", "\n", "```python\n", "knot.view_jobs()\n", "```\n", "\n", "You can also view the status of a specific job:\n", "\n", "```python\n", "knot.jobs[0].status\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "f873473f", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "caefef48", "metadata": {}, "source": [ "When all jobs are finished, remember to use the `clobber` method to\n", "destroy all of the AWS resources created by the `Knot`" ] }, { "cell_type": "code", "execution_count": null, "id": "599f6df8", "metadata": {}, "outputs": [], "source": [ "result_futures.result()\n", "knot.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)" ] }, { "cell_type": "markdown", "id": "b0ec5d23", "metadata": {}, "source": [ "In a second `Knot` object, we use a function that takes the\n", "resulting profiles of each subject and combines them into one csv file." ] }, { "cell_type": "code", "execution_count": null, "id": "458259d7", "metadata": {}, "outputs": [], "source": [ "def afq_combine_profiles(dummy_argument):\n", " from AFQ.api import download_and_combine_afq_profiles\n", " download_and_combine_afq_profiles(\n", " \"my_study_bucket\", \"my_study_prefix\")\n", "\n", "\n", "knot2 = ck.Knot(\n", " name='afq_combine_subjects-201009-0',\n", " func=afq_combine_profiles,\n", " base_image='python:3.11',\n", " image_github_installs=\"https://github.com/tractometry/pyAFQ.git\",\n", " pars_policies=('AmazonS3FullAccess',),\n", " bid_percentage=100)" ] }, { "cell_type": "markdown", "id": "424be449", "metadata": {}, "source": [ "This knot is called with a dummy argument, which is not used within the\n", "function itself. The `job_type` key-word argument is used to signal to\n", "`Cloudknot` that only one job is submitted rather than the default\n", "array of jobs." ] }, { "cell_type": "code", "execution_count": null, "id": "ef9c028d", "metadata": {}, "outputs": [], "source": [ "result_futures2 = knot2.map([\"dummy_argument\"], job_type=\"independent\")\n", "result_futures2.result()\n", "knot2.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)" ] }, { "cell_type": "markdown", "id": "d7aab31b", "metadata": {}, "source": [ ":::{only} html\n", "{download}`Download as Jupyter Notebook `\n", ":::" ] } ], "metadata": { "file_format": "mystnb", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "pygments_lexer": "ipython3" }, "mystnb": { "execution_mode": "off" } }, "nbformat": 4, "nbformat_minor": 5 }