From be9576b4d58c1a3b480228d712ab5a3df92c24f1 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Wed, 27 Oct 2021 12:01:29 -0500 Subject: [PATCH 01/46] Fixed link formatting, adding parens [title]link.com -> [title](link.com) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78f0231..ad804e1 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ element-animal contains two modules, `subject` and `genotyping`. This workflow serves as an example of the upstream part of a typical data workflow, for examples using these two elements more intact workflows, refer to: -+ [workflow-array-ephys]https://github.com/datajoint/workflow-array-ephys -+ [workflow-calcium-imaging]https://github.com/datajoint/workflow-calcium-imaging ++ [workflow-array-ephys](https://github.com/datajoint/workflow-array-ephys) ++ [workflow-calcium-imaging](https://github.com/datajoint/workflow-calcium-imaging) ## Installation instructions From bfa2bc10cb973a8bb6fbbcbd51582b2eb1eddad9 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 30 Nov 2021 18:16:33 -0600 Subject: [PATCH 02/46] initial commit migrate wf-anim->wf-sess --- notebooks/explore_workflow.ipynb | 494 ++------ requirements.txt | 1 + setup.py | 11 +- temp_test.ipynb | 1098 +++++++++++++++++ .../animal/_Other_Genotyping_Data_TBD.csv | 0 user_data/animal/allele.csv | 2 + user_data/animal/line.csv | 1 + user_data/animal/source.csv | 1 + user_data/animal/strain.csv | 1 + user_data/animal/subjects.csv | 3 + user_data/lab/keywords.csv | 4 + user_data/lab/labs.csv | 3 + user_data/lab/projects.csv | 3 + user_data/lab/protocols.csv | 3 + user_data/lab/publications.csv | 3 + user_data/lab/sources.csv | 2 + user_data/lab/users.csv | 2 + user_data/session/sessions.csv | 3 + workflow_animal/pipeline.py | 23 - .../__init__.py | 0 workflow_session/ingest.py | 65 + workflow_session/paths.py | 11 + workflow_session/pipeline.py | 22 + 23 files changed, 1310 insertions(+), 446 deletions(-) create mode 100644 temp_test.ipynb create mode 100644 user_data/animal/_Other_Genotyping_Data_TBD.csv create mode 100644 user_data/animal/allele.csv create mode 100644 user_data/animal/line.csv create mode 100644 user_data/animal/source.csv create mode 100644 user_data/animal/strain.csv create mode 100644 user_data/animal/subjects.csv create mode 100644 user_data/lab/keywords.csv create mode 100644 user_data/lab/labs.csv create mode 100644 user_data/lab/projects.csv create mode 100644 user_data/lab/protocols.csv create mode 100644 user_data/lab/publications.csv create mode 100644 user_data/lab/sources.csv create mode 100644 user_data/lab/users.csv create mode 100644 user_data/session/sessions.csv delete mode 100644 workflow_animal/pipeline.py rename {workflow_animal => workflow_session}/__init__.py (100%) create mode 100644 workflow_session/ingest.py create mode 100644 workflow_session/paths.py create mode 100644 workflow_session/pipeline.py diff --git a/notebooks/explore_workflow.ipynb b/notebooks/explore_workflow.ipynb index dc736a6..e41fa30 100644 --- a/notebooks/explore_workflow.ipynb +++ b/notebooks/explore_workflow.ipynb @@ -19,18 +19,11 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting shan@localhost:3306\n" - ] - } - ], + "outputs": [], "source": [ "# change to the upper level folder to detect dj_local_conf.json\n", "import os\n", + "import datajoint as dj\n", "os.chdir('..')" ] }, @@ -47,7 +40,16 @@ "metadata": {}, "outputs": [], "source": [ - "from workflow_animal.pipeline import lab, subject, genotyping" + "from workflow_session import * #lab, subject, genotyping" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from element_lab import lab" ] }, { @@ -59,188 +61,41 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [ { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "Lab\n", - "\n", - "\n", - "Lab\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.LabMembership\n", - "\n", - "\n", - "lab.LabMembership\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Lab->lab.LabMembership\n", - "\n", - "\n", - "\n", - "\n", - "Location\n", - "\n", - "\n", - "Location\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Lab->Location\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project\n", - "\n", - "\n", - "lab.Project\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.ProjectUser\n", - "\n", - "\n", - "lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "lab.UserRole\n", - "\n", - "\n", - "lab.UserRole\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.UserRole->lab.LabMembership\n", - "\n", - "\n", - "\n", - "\n", - "lab.ProtocolType\n", - "\n", - "\n", - "lab.ProtocolType\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "Protocol\n", - "\n", - "\n", - "Protocol\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.ProtocolType->Protocol\n", - "\n", - "\n", - "\n", - "\n", - "Source\n", - "\n", - "\n", - "Source\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "User\n", - "\n", - "\n", - "User\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "User->lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "User->lab.LabMembership\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" + "ename": "AttributeError", + "evalue": "'NoneType' object has no attribute 'non_blobs'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 700\u001b[0m \u001b[0mtype_pprinters\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtype_printers\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 701\u001b[0m deferred_pprinters=self.deferred_printers)\n\u001b[0;32m--> 702\u001b[0;31m \u001b[0mprinter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpretty\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 703\u001b[0m \u001b[0mprinter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflush\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 704\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetvalue\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/lib/pretty.py\u001b[0m in \u001b[0;36mpretty\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 392\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcls\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m\\\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 393\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__dict__\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'__repr__'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 394\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_repr_pprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcycle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 395\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 396\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0m_default_pprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcycle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/lib/pretty.py\u001b[0m in \u001b[0;36m_repr_pprint\u001b[0;34m(obj, p, cycle)\u001b[0m\n\u001b[1;32m 698\u001b[0m \u001b[0;34m\"\"\"A pprint that just redirects to the normal repr function.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 699\u001b[0m \u001b[0;31m# Find newlines and replace them with p.break_()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 700\u001b[0;31m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrepr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 701\u001b[0m \u001b[0mlines\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0moutput\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplitlines\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 702\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroup\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36m__repr__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0mrtype\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 528\u001b[0m \"\"\"\n\u001b[0;32m--> 529\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'loglevel'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'debug'\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 530\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36mpreview\u001b[0;34m(self, limit, width)\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 532\u001b[0m \u001b[0;34m\"\"\" :return: a string of preview of the contents of the query. \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 533\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 534\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 535\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_html_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/preview.py\u001b[0m in \u001b[0;36mpreview\u001b[0;34m(query_expression, limit, width)\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_expression\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mheading\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mrel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnon_blobs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlimit\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mlimit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'display.limit'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'non_blobs'" + ] + }, + { + "ename": "AttributeError", + "evalue": "'NoneType' object has no attribute 'non_blobs'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_real_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprint_method\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 344\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 345\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 346\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36m_repr_html_\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 535\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_html_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 536\u001b[0m \u001b[0;34m\"\"\" :return: HTML to display table in Jupyter notebook. \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 537\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrepr_html\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 538\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 539\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/preview.py\u001b[0m in \u001b[0;36mrepr_html\u001b[0;34m(query_expression)\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrepr_html\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_expression\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0mheading\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 32\u001b[0;31m \u001b[0mrel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnon_blobs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 33\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_status\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 34\u001b[0m \u001b[0mtuples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'display.limit'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mformat\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'array'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'non_blobs'" + ] } ], "source": [ - "dj.Diagram(lab)" + "lab.Lab()" ] }, { @@ -249,234 +104,37 @@ "metadata": {}, "outputs": [ { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "`subject`.`subject__protocol`\n", - "\n", - "`subject`.`subject__protocol`\n", - "\n", - "\n", - "\n", - "`subject`.`#allele__source`\n", - "\n", - "`subject`.`#allele__source`\n", - "\n", - "\n", - "\n", - "`subject`.`subject__source`\n", - "\n", - "`subject`.`subject__source`\n", - "\n", - "\n", - "\n", - "`subject`.`#line__allele`\n", - "\n", - "`subject`.`#line__allele`\n", - "\n", - "\n", - "\n", - "`subject`.`subject__strain`\n", - "\n", - "`subject`.`subject__strain`\n", - "\n", - "\n", - "\n", - "`subject`.`subject__line`\n", - "\n", - "`subject`.`subject__line`\n", - "\n", - "\n", - "\n", - "`subject`.`subject__lab`\n", - "\n", - "`subject`.`subject__lab`\n", - "\n", - "\n", - "\n", - "`subject`.`subject__user`\n", - "\n", - "`subject`.`subject__user`\n", - "\n", - "\n", - "\n", - "subject.Subject\n", - "\n", - "\n", - "subject.Subject\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__protocol`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__source`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__strain`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__line`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__lab`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->`subject`.`subject__user`\n", - "\n", - "\n", - "\n", - "\n", - "subject.SubjectDeath\n", - "\n", - "\n", - "subject.SubjectDeath\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->subject.SubjectDeath\n", - "\n", - "\n", - "\n", - "\n", - "subject.SubjectCullMethod\n", - "\n", - "\n", - "subject.SubjectCullMethod\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->subject.SubjectCullMethod\n", - "\n", - "\n", - "\n", - "\n", - "subject.Zygosity\n", - "\n", - "\n", - "subject.Zygosity\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->subject.Zygosity\n", - "\n", - "\n", - "\n", - "\n", - "subject.Strain\n", - "\n", - "\n", - "subject.Strain\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Strain->`subject`.`subject__strain`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele\n", - "\n", - "\n", - "subject.Allele\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->`subject`.`#allele__source`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->`subject`.`#line__allele`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->subject.Zygosity\n", - "\n", - "\n", - "\n", - "\n", - "subject.Line\n", - "\n", - "\n", - "subject.Line\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Line->`subject`.`#line__allele`\n", - "\n", - "\n", - "\n", - "\n", - "subject.Line->`subject`.`subject__line`\n", - "\n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" + "ename": "AttributeError", + "evalue": "'NoneType' object has no attribute 'dependencies'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24597/1338749693.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, source, context)\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 110\u001b[0m \u001b[0;31m# initialize graph from dependencies\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 111\u001b[0;31m \u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdependencies\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 112\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdependencies\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 113\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'dependencies'" + ] + } + ], + "source": [ + "dj.Diagram(lab)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'subject' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_36962/3159175528.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'subject' is not defined" + ] } ], "source": [ @@ -1230,9 +888,9 @@ ], "metadata": { "kernelspec": { - "display_name": "workflow-imaging", + "display_name": "venv-nwb", "language": "python", - "name": "workflow-imaging" + "name": "venv-nwb" }, "language_info": { "codemirror_mode": { @@ -1244,9 +902,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.0" + "version": "3.8.11" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/requirements.txt b/requirements.txt index 6455282..147ef16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ datajoint>=0.13.0 element-lab element-animal +element-session ipykernel diff --git a/setup.py b/setup.py index 1c10e13..30ecffe 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,12 @@ here = path.abspath(path.dirname(__file__)) long_description = """" -# Workflow for lab management and animal management +# Workflow for lab, animal, and session management Build a workflow for lab management and animal metadata using DataJoint Elements -+ [elements-lab](https://github.com/datajoint/elements-lab) -+ [elements-animal](https://github.com/datajoint/elements-animal) ++ [elements-lab](https://github.com/datajoint/element-lab) ++ [elements-animal](https://github.com/datajoint/element-animal) ++ [elements-session](https://github.com/datajoint/element-session) """ with open(path.join(here, 'requirements.txt')) as f: @@ -24,8 +25,8 @@ author='DataJoint NEURO', author_email='info@vathes.com', license='MIT', - url='https://github.com/datajoint/workflow-animal', - keywords='neuroscience lab-management animal-management datajoint', + url='https://github.com/datajoint/workflow-session', + keywords='neuroscience lab-management animal-management session-management datajoint', packages=find_packages(exclude=['contrib', 'docs', 'tests*']), install_requires=requirements, ) diff --git a/temp_test.ipynb b/temp_test.ipynb new file mode 100644 index 0000000..236734f --- /dev/null +++ b/temp_test.ipynb @@ -0,0 +1,1098 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 10, + "id": "f13c2734-23db-435f-bc9e-68064cdcc82b", + "metadata": {}, + "outputs": [], + "source": [ + "import datajoint as dj\n", + "dj.conn()\n", + "import csv" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cb32b7a1-37b4-45c1-8d03-02c8bbd09b04", + "metadata": {}, + "outputs": [], + "source": [ + "from workflow_session.pipeline import lab, subject, session" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b4a7e312-63ba-4380-9e91-02ee518ab77e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "

lab

\n", + " Abbreviated lab name\n", + "
\n", + "

lab_name

\n", + " full lab name\n", + "
\n", + "

institution

\n", + " \n", + "
\n", + "

address

\n", + " \n", + "
\n", + "

time_zone

\n", + " UTC offset suggested e.g., UTC+1\n", + "
\n", + " \n", + "

Total: 0

\n", + " " + ], + "text/plain": [ + "*lab lab_name institution address time_zone \n", + "+-----+ +----------+ +------------+ +---------+ +-----------+\n", + "\n", + " (Total: 0)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Lab()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ac97aba9-6451-41e6-a645-4e75b7dc5c50", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Animal Subject\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

sex

\n", + " \n", + "
\n", + "

subject_birth_date

\n", + " \n", + "
\n", + "

subject_description

\n", + " \n", + "
\n", + " \n", + "

Total: 0

\n", + " " + ], + "text/plain": [ + "*subject sex subject_birth_ subject_descri\n", + "+---------+ +-----+ +------------+ +------------+\n", + "\n", + " (Total: 0)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject.Subject()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "142729e3-94c9-4d1b-8748-4c14e7da37fb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
\n", + " \n", + "

Total: 0

\n", + " " + ], + "text/plain": [ + "*subject *session_datet\n", + "+---------+ +------------+\n", + "\n", + " (Total: 0)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session.Session()" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "ac95397f-87e9-47eb-a6f6-198581885230", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Deleting 2 rows from `neuro_lab`.`#location`\n", + "Deleting 2 rows from `neuro_lab`.`#lab`\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Commit deletes? [yes, No]: yes\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Deletes committed.\n", + "Deleting 2 rows from `neuro_lab`.`#project`\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Commit deletes? [yes, No]: yes\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Deletes committed.\n", + "Deleting 2 rows from `neuro_lab`.`#protocol`\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "Commit deletes? [yes, No]: yes\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Deletes committed.\n" + ] + }, + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Lab.delete()\n", + "lab.Project.delete()\n", + "lab.Protocol.delete()" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "id": "f5676e0b-3908-4dc0-b9f9-dbabb6ed5f2f", + "metadata": {}, + "outputs": [], + "source": [ + "import element_data_loader.utils\n", + "from workflow_session.paths import *" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "id": "e31cb826-30ba-459c-9722-e0dc531ef919", + "metadata": {}, + "outputs": [], + "source": [ + "def get_root_data_dir():\n", + " root_data_dirs = dj.config.get('custom', {}).get('root_data_dir', None)\n", + " return root_data_dirs if root_data_dirs else None" + ] + }, + { + "cell_type": "code", + "execution_count": 159, + "id": "6eccb1c1-64f2-4361-92b1-7f59e3d015a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Project's resulting publications\n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "

project

\n", + " \n", + "
\n", + "

publication

\n", + " \n", + "
\n", + " \n", + "

Total: 0

\n", + " " + ], + "text/plain": [ + "*project *publication \n", + "+---------+ +------------+\n", + "\n", + " (Total: 0)" + ] + }, + "execution_count": 159, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Project.Publication()" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "id": "c1c0ec34-5e7b-4c9d-b78a-832470411d7d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "def ingest_lab(lab_csv_path='./user_data/lab/labs.csv',\n", + " project_csv_path='./user_data/lab/projects.csv',\n", + " pubs_csv_path='./user_data/lab/publications.csv',\n", + " keyw_csv_path='./user_data/lab/keywords.csv',\n", + " protocol_csv_path='./user_data/lab/protocols.csv'\n", + " ):\n", + " # -------------- Insert new \"Lab\" --------------\n", + " with open(lab_csv_path, newline= '') as f:\n", + " input_labs = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert {len(input_labs)} entry(s) into lab tables ----')\n", + " lab.Lab.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True)\n", + " lab.Location.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True)\n", + " # -------------- Insert new \"Project\" --------------\n", + " with open(project_csv_path, newline= '') as f:\n", + " input_projs = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert {len(input_projs)} entry(s) into project table ----')\n", + " lab.Project.insert(input_projs, skip_duplicates=True, ignore_extra_fields=True)\n", + " # -------------- Insert publications + keywords --------------\n", + " with open(pubs_csv_path, newline= '') as f:\n", + " input_pubs = list(csv.DictReader(f, delimiter=','))\n", + " with open(keyw_csv_path, newline= '') as f:\n", + " input_keyw = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert entry(s) into publication/keyword tables ----')\n", + " lab.Project.Publication.insert(input_pubs, skip_duplicates=True, ignore_extra_fields=True)\n", + " lab.Project.Keywords.insert(input_keyw, skip_duplicates=True, ignore_extra_fields=True)\n", + "\n", + " # -------------- Insert new \"Protocol\" --------------\n", + " with open(protocol_csv_path, newline= '') as f:\n", + " input_prots = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert {len(input_prots)} entry(s) into protocol tables ----')\n", + " lab.Protocol.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True)\n", + " lab.ProtocolType.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True)\n", + "def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'):\n", + " # -------------- Insert new \"Subject\" --------------\n", + " with open(subject_csv_path, newline= '') as f:\n", + " input_subjects = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert {len(input_subjects)} entry(s) into subject tables ----')\n", + " subject.Subject.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", + " subject.SubjectDeath.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", + " subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", + " ## Skipped allele info\n", + "def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'):\n", + " with open(session_csv_path, newline= '') as f:\n", + " input_sessions = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Insert {len(input_sessions)} entry(s) into session.Session ----')\n", + " session.Session.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", + " session.SessionDirectory.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", + " session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "0fc98f1f-ca72-4586-bec2-a36ff68ced8e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

lab

\n", + " Abbreviated lab name\n", + "
\n", + "

lab_name

\n", + " full lab name\n", + "
\n", + "

institution

\n", + " \n", + "
\n", + "

address

\n", + " \n", + "
\n", + "

time_zone

\n", + " UTC offset suggested e.g., UTC+1\n", + "
LabAThe Example LabExample Uni221B Baker St,London NW1 6XE,UKUTC+0
\n", + " \n", + "

Total: 1

\n", + " " + ], + "text/plain": [ + "*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +-----------+\n", + "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", + " (Total: 1)" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Lab & 'lab=\"LabA\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "id": "8d20561d-e4a9-47cd-8eca-0c8295d48507", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Project's resulting publications\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "
\n", + "

project

\n", + " \n", + "
\n", + "

publication

\n", + " \n", + "
ProjAarXiv:1807.11104
ProjAarXiv:1807.11104v1
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*project *publication \n", + "+---------+ +------------+\n", + "ProjA arXiv:1807.111\n", + "ProjA arXiv:1807.111\n", + " (Total: 2)" + ] + }, + "execution_count": 162, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Project & 'project=\"ProjA\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "4a8b2aa9-dbd1-4c1f-ac02-39373c9bc293", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " protocol approved by some institutions like IACUC, IRB\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "
\n", + "

protocol

\n", + " \n", + "
\n", + "

protocol_type

\n", + " \n", + "
\n", + "

protocol_description

\n", + " \n", + "
ProtAIRB expedited reviewProtocol for managing data ingestion
\n", + " \n", + "

Total: 1

\n", + " " + ], + "text/plain": [ + "*protocol protocol_type protocol_descr\n", + "+----------+ +------------+ +------------+\n", + "ProtA IRB expedited Protocol for m\n", + " (Total: 1)" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Protocol() & 'protocol=\"ProtA\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "e306d9dc-ce9a-4485-b8da-eea7d852e5aa", + "metadata": {}, + "outputs": [], + "source": [ + "lab_info = (lab.Lab & 'lab=\"LabA\"').fetch1()\n", + "proj_info = (lab.Project & 'project=\"ProjA\"').fetch1()\n", + "prot_info = (lab.Protocol() & 'protocol=\"ProtA\"').fetch1()" + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "id": "68fc25cc-d69e-4836-9e95-7f4fa727cbf4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "

lab

\n", + " Abbreviated lab name\n", + "
\n", + "

lab_name

\n", + " full lab name\n", + "
\n", + "

institution

\n", + " \n", + "
\n", + "

address

\n", + " \n", + "
\n", + "

time_zone

\n", + " UTC offset suggested e.g., UTC+1\n", + "
\n", + " \n", + "

Total: 0

\n", + " " + ], + "text/plain": [ + "*lab lab_name institution address time_zone \n", + "+-----+ +----------+ +------------+ +---------+ +-----------+\n", + "\n", + " (Total: 0)" + ] + }, + "execution_count": 131, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Lab & 'lab=\"None\"'" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "99a87d22-a369-45a2-96a7-5c67bf27accb", + "metadata": {}, + "outputs": [], + "source": [ + "from pynwb import NWBFile" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "id": "bb35e7ae-f76c-4d1c-b1d2-6ddd68cdeef3", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "NWBFile.__init__: missing argument 'session_description', missing argument 'identifier', missing argument 'session_start_time', incorrect type for 'stimulus' (got 'str', expected 'list or tuple'), unrecognized argument: 'file_name'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24827/1508549328.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mproj_keyw\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mKeywords\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mproject_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'keyword'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtolist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mproj_pubs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPublication\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mproject_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'publication'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtolist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m NWBFile(\n\u001b[0m\u001b[1;32m 6\u001b[0m \u001b[0;31m# data_collection='',\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mexperiment_description\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mproj_info\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'project_description'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/utils.py\u001b[0m in \u001b[0;36mfunc_call\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 577\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_method\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 578\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 579\u001b[0;31m \u001b[0mpargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_check_args\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 580\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mpargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 581\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/utils.py\u001b[0m in \u001b[0;36m_check_args\u001b[0;34m(args, kwargs)\u001b[0m\n\u001b[1;32m 570\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mparse_err\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 571\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'%s: %s'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__qualname__\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m', '\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparse_err\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 572\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mExceptionType\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 573\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 574\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mparsed\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'args'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: NWBFile.__init__: missing argument 'session_description', missing argument 'identifier', missing argument 'session_start_time', incorrect type for 'stimulus' (got 'str', expected 'list or tuple'), unrecognized argument: 'file_name'" + ] + } + ], + "source": [ + "project_key = 'project=\"ProjA\"'\n", + "proj_info = (lab.Project & project_key).fetch1()\n", + "proj_keyw = (lab.Project.Keywords() & project_key).fetch('keyword').tolist()\n", + "proj_pubs = (lab.Project.Publication() & project_key).fetch('publication').tolist()\n", + "NWBFile(\n", + " # data_collection='',\n", + " experiment_description=proj_info['project_description'],\n", + " keywords=proj_keyw,\n", + " notes=prot_info['protocol_description'],\n", + " pharmacology=proj_info['pharmacology'],\n", + " related_publications=proj_pubs,\n", + " slices=proj_info['slices'],\n", + " source_script=proj_info['repositoryurl'],\n", + " file_name=proj_info['repositoryname'],\n", + " stimulus=proj_info['stimulus'],\n", + " surgery=proj_info['surgery']\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "id": "0c69fff1-3cef-4d99-9ad8-3525dedf3fec", + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid syntax (255707863.py, line 1)", + "output_type": "error", + "traceback": [ + "\u001b[0;36m File \u001b[0;32m\"/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24827/255707863.py\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m tmp := proj_info['project']\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + ] + } + ], + "source": [ + "if tmp := proj_info['virus']: virus=proj_info['virus']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1540c2e4-05dc-4543-a239-c82d1f4445f0", + "metadata": {}, + "outputs": [], + "source": [ + " NWBFile(identifier='_'.join(session_identifier.values()),\n", + " session_description=session_info['session_note'] if session_info['session_note'] else '',\n", + " session_start_time=session_info['session_datetime'],\n", + " file_create_date=datetime.now(tzlocal()),\n", + " experimenter=list(experimenters)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "927430ff-2224-49ab-973a-c4c666c7688d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/user_data/animal/_Other_Genotyping_Data_TBD.csv b/user_data/animal/_Other_Genotyping_Data_TBD.csv new file mode 100644 index 0000000..e69de29 diff --git a/user_data/animal/allele.csv b/user_data/animal/allele.csv new file mode 100644 index 0000000..ee4c9ea --- /dev/null +++ b/user_data/animal/allele.csv @@ -0,0 +1,2 @@ +allele, allele_standard_name,zygosity +Ex,Example,Absent \ No newline at end of file diff --git a/user_data/animal/line.csv b/user_data/animal/line.csv new file mode 100644 index 0000000..5e53f5c --- /dev/null +++ b/user_data/animal/line.csv @@ -0,0 +1 @@ +line, line_description, target_phenotype, is_active \ No newline at end of file diff --git a/user_data/animal/source.csv b/user_data/animal/source.csv new file mode 100644 index 0000000..c3cc2ac --- /dev/null +++ b/user_data/animal/source.csv @@ -0,0 +1 @@ +allele, source_identifier, source_url, expression_data_url \ No newline at end of file diff --git a/user_data/animal/strain.csv b/user_data/animal/strain.csv new file mode 100644 index 0000000..88341fd --- /dev/null +++ b/user_data/animal/strain.csv @@ -0,0 +1 @@ +strain, strain_standard_name, strain_desc \ No newline at end of file diff --git a/user_data/animal/subjects.csv b/user_data/animal/subjects.csv new file mode 100644 index 0000000..3ce4aaa --- /dev/null +++ b/user_data/animal/subjects.csv @@ -0,0 +1,3 @@ +subject,sex,subject_birth_date,subject_description,death_date,cull_method +subject5,F,2020-01-03,lmash_E105,2020-10-02,natural causes +subject6,M,2020-01-03,hneih_E105,2020-10-03,natural causes \ No newline at end of file diff --git a/user_data/lab/keywords.csv b/user_data/lab/keywords.csv new file mode 100644 index 0000000..547039b --- /dev/null +++ b/user_data/lab/keywords.csv @@ -0,0 +1,4 @@ +project,keyword +ProjA,Study +ProjA,Example +ProjB,Alternate \ No newline at end of file diff --git a/user_data/lab/labs.csv b/user_data/lab/labs.csv new file mode 100644 index 0000000..aa1621e --- /dev/null +++ b/user_data/lab/labs.csv @@ -0,0 +1,3 @@ +lab,lab_name,institution,address,time_zone,location,location_description +LabA,The Example Lab,Example Uni,"221B Baker St,London NW1 6XE,UK",UTC+0,Example Building,"2nd floor lab dedicated to all fictional experiments." +LabB,The Other Lab,Other Uni,"Oxford OX1 2JD, United Kingdom",UTC+0,Other Building,"fictional campus dedicated to imaginary experiments." \ No newline at end of file diff --git a/user_data/lab/projects.csv b/user_data/lab/projects.csv new file mode 100644 index 0000000..aed6d38 --- /dev/null +++ b/user_data/lab/projects.csv @@ -0,0 +1,3 @@ +project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl +ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab +ProjB,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,,Exemplarvirus administered 10d before experimental session,,,,https://github.com/datajoint/element-lab/tree/main/element_lab diff --git a/user_data/lab/protocols.csv b/user_data/lab/protocols.csv new file mode 100644 index 0000000..dc3fd9a --- /dev/null +++ b/user_data/lab/protocols.csv @@ -0,0 +1,3 @@ +protocol,protocol_type,protocol_description +ProtA,IRB expedited review,Protocol for managing data ingestion +ProtB,Alternative Method,Limited protocol for piloting only \ No newline at end of file diff --git a/user_data/lab/publications.csv b/user_data/lab/publications.csv new file mode 100644 index 0000000..420668e --- /dev/null +++ b/user_data/lab/publications.csv @@ -0,0 +1,3 @@ +project,publication +ProjA,arXiv:1807.11104 +ProjA,arXiv:1807.11104v1 \ No newline at end of file diff --git a/user_data/lab/sources.csv b/user_data/lab/sources.csv new file mode 100644 index 0000000..43c5cee --- /dev/null +++ b/user_data/lab/sources.csv @@ -0,0 +1,2 @@ +source, source_name, contact_details, source_description +Provider1, Example Provider, +44 1632 960663 / Example@Provider.com, UK-based supplier of lab subjcts mus musculus \ No newline at end of file diff --git a/user_data/lab/users.csv b/user_data/lab/users.csv new file mode 100644 index 0000000..8092b42 --- /dev/null +++ b/user_data/lab/users.csv @@ -0,0 +1,2 @@ +user, user_role, user_email, user_cellphone +Sherlock, PI, Sherlock@BakerSt.com, +44 20 7946 0344 \ No newline at end of file diff --git a/user_data/session/sessions.csv b/user_data/session/sessions.csv new file mode 100644 index 0000000..d3ad137 --- /dev/null +++ b/user_data/session/sessions.csv @@ -0,0 +1,3 @@ +subject,session_datetime,session_dir,session_note +subject5,2020-04-15 11:16:38,/subject5/session1,"Successful data collection, no notes" +subject6,2021-06-02 14:04:22,/subject6/session1,"Ambient temp abnormally low" \ No newline at end of file diff --git a/workflow_animal/pipeline.py b/workflow_animal/pipeline.py deleted file mode 100644 index f82d367..0000000 --- a/workflow_animal/pipeline.py +++ /dev/null @@ -1,23 +0,0 @@ -import datajoint as dj -from elements_lab import lab -from elements_animal import subject, genotyping -from elements_lab.lab import Source, Lab, Protocol, User, Location - - -if 'custom' not in dj.config: - dj.config['custom'] = {} - -db_prefix = dj.config['custom'].get('database.prefix', '') - - -# ---------------------------------- Activate "lab" schema ------------------------------- - -lab.activate(db_prefix + 'lab') - - -# ------------------------- Activate "subject" and "genotyping" schema ------------------- - -subject.activate(db_prefix + 'subject', linking_module=__name__) - -# Omit this schema if genotying is not needed -genotyping.activate(db_prefix + 'genotyping', db_prefix + 'subject', linking_module=__name__) diff --git a/workflow_animal/__init__.py b/workflow_session/__init__.py similarity index 100% rename from workflow_animal/__init__.py rename to workflow_session/__init__.py diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py new file mode 100644 index 0000000..0f96b20 --- /dev/null +++ b/workflow_session/ingest.py @@ -0,0 +1,65 @@ +import pathlib +import csv +import re + +from workflow_session.pipeline import lab, Source, Lab, Protocol, User, Location, subject, genotyping, session + +from workflow_session.paths import get_root_data_dir +import element_data_loader.utils + +def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', + project_csv_path='./user_data/lab/projects.csv', + pubs_csv_path='./user_data/lab/publications.csv', + keyw_csv_path='./user_data/lab/keywords.csv', + protocol_csv_path='./user_data/lab/protocols.csv' + ): + # -------------- Insert new "Lab" -------------- + with open(lab_csv_path, newline= '') as f: + input_labs = list(csv.DictReader(f, delimiter=',')) + print(f'\n---- Insert {len(input_labs)} entry(s) into lab tables ----') + lab.Lab.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True) + lab.Location.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True) + # -------------- Insert new "Project" -------------- + with open(project_csv_path, newline= '') as f: + input_projs = list(csv.DictReader(f, delimiter=',')) + print(f'\n---- Insert {len(input_projs)} entry(s) into project table ----') + lab.Project.insert(input_projs, skip_duplicates=True, ignore_extra_fields=True) + # -------------- Insert publications + keywords -------------- + with open(pubs_csv_path, newline= '') as f: + input_pubs = list(csv.DictReader(f, delimiter=',')) + with open(keyw_csv_path, newline= '') as f: + input_keyw = list(csv.DictReader(f, delimiter=',')) + print(f'\n---- Insert entry(s) into publication/keyword tables ----') + lab.Project.Publication.insert(input_pubs, skip_duplicates=True, ignore_extra_fields=True) + lab.Project.Keywords.insert(input_keyw, skip_duplicates=True, ignore_extra_fields=True) + + # -------------- Insert new "Protocol" -------------- + with open(protocol_csv_path, newline= '') as f: + input_prots = list(csv.DictReader(f, delimiter=',')) + print(f'\n---- Insert {len(input_prots)} entry(s) into protocol tables ----') + lab.Protocol.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True) + lab.ProtocolType.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True) + +def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'): + # -------------- Insert new "Subject" -------------- + with open(subject_csv_path, newline= '') as f: + input_subjects = list(csv.DictReader(f, delimiter=',')) + print(f'\n---- Insert {len(input_subjects)} entry(s) into subject tables ----') + subject.Subject.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) + subject.SubjectDeath.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) + subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) + ## Skipped allele info + +def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'): + with open(session_csv_path, newline= '') as f: + input_sessions = list(csv.DictReader(f, delimiter=',')) + + print(f'\n---- Insert {len(input_sessions)} entry(s) into session.Session ----') + session.Session.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + session.SessionDirectory.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + +if __name__ == '__main__': + ingest_lab() + ingest_subjects() + ingest_sessions() diff --git a/workflow_session/paths.py b/workflow_session/paths.py new file mode 100644 index 0000000..1333a04 --- /dev/null +++ b/workflow_session/paths.py @@ -0,0 +1,11 @@ +import datajoint as dj +import pathlib + +def get_root_data_dir(): + root_data_dirs = dj.config.get('custom', {}).get('root_data_dir', None) + return root_data_dirs if root_data_dirs else None + +def get_session_directory(session_key: dict) -> str: + from .pipeline import session + session_dir = (session.SessionDirectory & session_key).fetch1('session_dir') + return session_dir \ No newline at end of file diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py new file mode 100644 index 0000000..a068352 --- /dev/null +++ b/workflow_session/pipeline.py @@ -0,0 +1,22 @@ +import datajoint as dj +from element_lab import lab +from element_animal import subject +from element_session import session + +from element_animal.subject import Subject +from element_lab.lab import Source, Lab, Protocol, User, Project +from element_session.session import Session + +if 'custom' not in dj.config: + dj.config['custom'] = {} + +db_prefix = dj.config['custom'].get('database.prefix', '') + + +# Activate "lab", "subject", "session" schema ---------------------------------- + +lab.activate(db_prefix + 'lab') + +subject.activate(db_prefix + 'subject', linking_module=__name__) + +session.activate(db_prefix + 'session', linking_module=__name__) From 8ab4908b819caf6ad19936edb35d14e8b21bc230 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 1 Dec 2021 15:07:03 -0600 Subject: [PATCH 03/46] partial revision, fake data, ingest functs --- .gitignore | 6 +- README.md | 15 +- notebooks/explore_workflow.ipynb | 1278 +++++++++++++++++++++--------- temp_test.ipynb | 1065 +++---------------------- workflow_session/ingest.py | 2 +- 5 files changed, 1030 insertions(+), 1336 deletions(-) diff --git a/.gitignore b/.gitignore index 4d1496e..1711db3 100644 --- a/.gitignore +++ b/.gitignore @@ -109,8 +109,10 @@ ENV/ .mypy_cache/ # datajoint -dj_local_conf.json -dj_local_conf_old.json +dj_local_c*.json + +# notes +temp* # emacs **/*~ diff --git a/README.md b/README.md index 5d4824e..67241e8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -# Workflow for lab management and animal management +# Workflow for lab, subject and session management -This workflow builds a workflow to save the information of lab management and animal management, using the following datajoint elements +This directory provides an example workflow to save the information related to lab, subject, and session metadata data management, using the following datajoint elements + [element-lab](https://github.com/datajoint/element-lab) + [element-animal](https://github.com/datajoint/element-animal) ++ [element-session](https://github.com/datajoint/element-session) This repository provides demonstrations for: Set up a workflow using different elements (see [workflow_animal/pipeline.py](workflow_animal/pipeline.py)) ## Workflow architecture -The lab and animal management workflow presented here uses components from two DataJoint elements, element-lab, and element-animal assembled together to a functional workflow. +The lab and animal management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together to a functional workflow. ### element-lab @@ -25,7 +26,9 @@ element-animal contains two modules, `subject` and `genotyping`. `genotyping` is designed for labs that handle animal care and genotyping themselves, which is optional. ![genotyping](images/genotyping_diagram.svg) -This workflow serves as an example of the upstream part of a typical data workflow, for examples using these two elements more intact workflows, refer to: +`session` is designed to handle metadata related to data collection, including collection datetime, file paths, and notes. + +This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements more intact workflows, refer to: + [workflow-array-ephys](https://github.com/datajoint/workflow-array-ephys) + [workflow-calcium-imaging](https://github.com/datajoint/workflow-calcium-imaging) @@ -79,8 +82,8 @@ From the root of the cloned repository directory: ``` Note: the `-e` flag will install this repository in editable mode, -in case there's a need to modify the code (e.g. the `pipeline.py` or `paths.py` scripts). -If no such modification required, using `pip install .` is sufficient +in case you'd like to to modify the code (e.g. the `pipeline.py` or `paths.py` scripts). +If no such modification required, using `pip install .` is sufficient. ### Step 4 - Jupyter Notebook diff --git a/notebooks/explore_workflow.ipynb b/notebooks/explore_workflow.ipynb index e41fa30..c2aadfd 100644 --- a/notebooks/explore_workflow.ipynb +++ b/notebooks/explore_workflow.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# DataJoint U24 Workflow Animal" + "# DataJoint U24 Workflow Session" ] }, { @@ -19,37 +19,68 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n" + ] + }, + { + "data": { + "text/plain": [ + "DataJoint connection (connected) root@localhost:3306" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# change to the upper level folder to detect dj_local_conf.json\n", "import os\n", + "os.chdir('..')\n", "import datajoint as dj\n", - "os.chdir('..')" + "dj.conn()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Importing the module `workflow_animal.pipeline` is sufficient to create tables inside the elements" + "Importing the module `workflow_session.pipeline` is sufficient to create tables inside the elements. This workflow comes prepackaged with example data and ingestion functions to populate lab, subject, and session tables." ] }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_session import * #lab, subject, genotyping" - ] - }, - { - "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], "source": [ - "from element_lab import lab" + "from workflow_session.pipeline import * \n", + "from workflow_session.ingest import *\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" ] }, { @@ -61,37 +92,106 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { - "ename": "AttributeError", - "evalue": "'NoneType' object has no attribute 'non_blobs'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 700\u001b[0m \u001b[0mtype_pprinters\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtype_printers\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 701\u001b[0m deferred_pprinters=self.deferred_printers)\n\u001b[0;32m--> 702\u001b[0;31m \u001b[0mprinter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpretty\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 703\u001b[0m \u001b[0mprinter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mflush\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 704\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mstream\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetvalue\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/lib/pretty.py\u001b[0m in \u001b[0;36mpretty\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 392\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcls\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mobject\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m\\\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 393\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__dict__\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'__repr__'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 394\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_repr_pprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcycle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 395\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 396\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0m_default_pprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcycle\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/lib/pretty.py\u001b[0m in \u001b[0;36m_repr_pprint\u001b[0;34m(obj, p, cycle)\u001b[0m\n\u001b[1;32m 698\u001b[0m \u001b[0;34m\"\"\"A pprint that just redirects to the normal repr function.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 699\u001b[0m \u001b[0;31m# Find newlines and replace them with p.break_()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 700\u001b[0;31m \u001b[0moutput\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrepr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 701\u001b[0m \u001b[0mlines\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0moutput\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msplitlines\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 702\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroup\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36m__repr__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 527\u001b[0m \u001b[0;34m:\u001b[0m\u001b[0mrtype\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 528\u001b[0m \"\"\"\n\u001b[0;32m--> 529\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'loglevel'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'debug'\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 530\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36mpreview\u001b[0;34m(self, limit, width)\u001b[0m\n\u001b[1;32m 531\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 532\u001b[0m \u001b[0;34m\"\"\" :return: a string of preview of the contents of the query. \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 533\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 534\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 535\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_html_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/preview.py\u001b[0m in \u001b[0;36mpreview\u001b[0;34m(query_expression, limit, width)\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpreview\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_expression\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlimit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwidth\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mheading\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mrel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnon_blobs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlimit\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mlimit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'display.limit'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'non_blobs'" - ] - }, - { - "ename": "AttributeError", - "evalue": "'NoneType' object has no attribute 'non_blobs'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/IPython/core/formatters.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, obj)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_real_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprint_method\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 344\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 345\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 346\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 347\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/expression.py\u001b[0m in \u001b[0;36m_repr_html_\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 535\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_repr_html_\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 536\u001b[0m \u001b[0;34m\"\"\" :return: HTML to display table in Jupyter notebook. \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 537\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mrepr_html\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 538\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 539\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/preview.py\u001b[0m in \u001b[0;36mrepr_html\u001b[0;34m(query_expression)\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mrepr_html\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mquery_expression\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0mheading\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 32\u001b[0;31m \u001b[0mrel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mquery_expression\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mproj\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnon_blobs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 33\u001b[0m \u001b[0minfo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mheading\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtable_status\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 34\u001b[0m \u001b[0mtuples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlimit\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mconfig\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'display.limit'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mformat\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'array'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'non_blobs'" - ] + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

lab

\n", + " Abbreviated lab name\n", + "
\n", + "

lab_name

\n", + " full lab name\n", + "
\n", + "

institution

\n", + " \n", + "
\n", + "

address

\n", + " \n", + "
\n", + "

time_zone

\n", + " UTC offset suggested e.g., UTC+1\n", + "
LabAThe Example LabExample Uni221B Baker St,London NW1 6XE,UKUTC+0
LabBThe Other LabOther UniOxford OX1 2JD, United KingdomUTC+0
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +-----------+\n", + "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", + "LabB The Other Lab Other Uni Oxford OX1 2JD UTC+0 \n", + " (Total: 2)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -100,20 +200,245 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "metadata": {}, "outputs": [ { - "ename": "AttributeError", - "evalue": "'NoneType' object has no attribute 'dependencies'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24597/1338749693.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/diagram.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, source, context)\u001b[0m\n\u001b[1;32m 109\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 110\u001b[0m \u001b[0;31m# initialize graph from dependencies\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 111\u001b[0;31m \u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdependencies\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 112\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconnection\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdependencies\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 113\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'dependencies'" - ] + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "Project.Keywords\n", + "\n", + "\n", + "Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Lab\n", + "\n", + "\n", + "Lab\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Lab->lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "Lab->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "Project.Sourcecode\n", + "\n", + "\n", + "Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Protocol\n", + "\n", + "\n", + "Protocol\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType->Protocol\n", + "\n", + "\n", + "\n", + "\n", + "Project\n", + "\n", + "\n", + "Project\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Project->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "Project->Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "Project->Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "Project.Publication\n", + "\n", + "\n", + "Project.Publication\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Project->Project.Publication\n", + "\n", + "\n", + "\n", + "\n", + "Source\n", + "\n", + "\n", + "Source\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "User\n", + "\n", + "\n", + "User\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "User->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "User->lab.LabMembership\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -122,278 +447,603 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "metadata": {}, "outputs": [ { - "ename": "NameError", - "evalue": "name 'subject' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_36962/3159175528.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'subject' is not defined" - ] + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Animal Subject\n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

sex

\n", + " \n", + "
\n", + "

subject_birth_date

\n", + " \n", + "
\n", + "

subject_description

\n", + " \n", + "
subject5F2020-01-03lmash_E105
subject6M2020-01-03hneih_E105
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject sex subject_birth_ subject_descri\n", + "+----------+ +-----+ +------------+ +------------+\n", + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", + " (Total: 2)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "dj.Diagram(subject)" + "subject.Subject()" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "`genotyping`.`breeding_pair__father`\n", - "\n", - "`genotyping`.`breeding_pair__father`\n", - "\n", - "\n", - "\n", - "`genotyping`.`breeding_pair__mother`\n", - "\n", - "`genotyping`.`breeding_pair__mother`\n", - "\n", - "\n", - "\n", - "subject.Subject\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "Subject.Strain\n", + "\n", + "\n", + "Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "Subject.Line\n", + "\n", + "\n", + "Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Subject.Protocol\n", + "\n", + "\n", + "Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Subject\n", + "\n", - "\n", - "subject.Subject\n", + "\n", + "Subject\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject->`genotyping`.`breeding_pair__father`\n", - "\n", + "\n", + "\n", + "Subject->Subject.Strain\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject->`genotyping`.`breeding_pair__mother`\n", - "\n", + "\n", + "\n", + "Subject->subject.SubjectDeath\n", + "\n", "\n", - "\n", - "\n", - "genotyping.SubjectLitter\n", - "\n", - "\n", - "genotyping.SubjectLitter\n", - "\n", + "\n", + "\n", + "Subject->subject.SubjectCullMethod\n", + "\n", "\n", + "\n", + "\n", + "Subject->subject.Zygosity\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject->genotyping.SubjectLitter\n", - "\n", + "\n", + "\n", + "Subject->Subject.Protocol\n", + "\n", "\n", - "\n", - "\n", - "genotyping.SubjectCaging\n", - "\n", + "\n", + "Subject->Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "Subject.User\n", + "\n", - "\n", - "genotyping.SubjectCaging\n", + "\n", + "Subject.User\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject->genotyping.SubjectCaging\n", - "\n", + "\n", + "\n", + "Subject->Subject.User\n", + "\n", "\n", - "\n", - "\n", - "genotyping.GenotypeTest\n", - "\n", + "\n", + "Subject.Source\n", + "\n", - "\n", - "genotyping.GenotypeTest\n", + "\n", + "Subject.Source\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject->genotyping.GenotypeTest\n", - "\n", + "\n", + "\n", + "Subject->Subject.Source\n", + "\n", "\n", - "\n", - "\n", - "genotyping.BreedingPair\n", - "\n", + "\n", + "Subject.Lab\n", + "\n", - "\n", - "genotyping.BreedingPair\n", + "\n", + "Subject.Lab\n", "\n", "\n", "\n", - "\n", - "\n", - "genotyping.BreedingPair->`genotyping`.`breeding_pair__father`\n", - "\n", + "\n", + "\n", + "Subject->Subject.Lab\n", + "\n", "\n", - "\n", - "\n", - "genotyping.BreedingPair->`genotyping`.`breeding_pair__mother`\n", - "\n", - "\n", - "\n", - "\n", - "genotyping.Litter\n", - "\n", + "\n", + "subject.Allele\n", + "\n", - "\n", - "genotyping.Litter\n", + "\n", + "subject.Allele\n", "\n", "\n", "\n", - "\n", - "\n", - "genotyping.BreedingPair->genotyping.Litter\n", - "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", "\n", - "\n", - "\n", - "genotyping.AlleleSequence\n", - "\n", - "\n", - "genotyping.AlleleSequence\n", - "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", "\n", - "\n", - "\n", - "genotyping.Weaning\n", - "\n", + "\n", + "subject.Strain\n", + "\n", - "\n", - "genotyping.Weaning\n", + "\n", + "subject.Strain\n", "\n", "\n", "\n", - "\n", - "\n", - "genotyping.Litter->genotyping.SubjectLitter\n", - "\n", + "\n", + "\n", + "subject.Strain->Subject.Strain\n", + "\n", "\n", - "\n", - "\n", - "genotyping.Litter->genotyping.Weaning\n", - "\n", "\n", - "\n", - "\n", - "subject.Allele\n", - "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.Diagram(subject)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "
\n", + "

subject

\n", + " \n", + "
\n", + "

session_datetime

\n", + " \n", + "
subject52020-04-15 11:16:38
subject62021-06-02 14:04:22
\n", + " \n", + "

Total: 2

\n", + " " + ], + "text/plain": [ + "*subject *session_datet\n", + "+----------+ +------------+\n", + "subject5 2020-04-15 11:\n", + "subject6 2021-06-02 14:\n", + " (Total: 2)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session.Session()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Session\n", + "\n", - "\n", - "subject.Allele\n", + "\n", + "Session\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Allele->genotyping.AlleleSequence\n", - "\n", + "\n", + "\n", + "Session->session.ProjectSession\n", + "\n", "\n", - "\n", - "\n", - "genotyping.Sequence\n", - "\n", + "\n", + "session.SessionNote\n", + "\n", - "\n", - "genotyping.Sequence\n", + "\n", + "session.SessionNote\n", "\n", "\n", "\n", - "\n", - "\n", - "genotyping.Sequence->genotyping.AlleleSequence\n", - "\n", + "\n", + "\n", + "Session->session.SessionNote\n", + "\n", "\n", - "\n", - "\n", - "genotyping.Sequence->genotyping.GenotypeTest\n", - "\n", - "\n", - "\n", - "\n", - "genotyping.Cage\n", - "\n", + "\n", + "session.SessionDirectory\n", + "\n", - "\n", - "genotyping.Cage\n", + "\n", + "session.SessionDirectory\n", "\n", "\n", "\n", - "\n", - "\n", - "genotyping.Cage->genotyping.SubjectCaging\n", - "\n", + "\n", + "\n", + "Session->session.SessionDirectory\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 8, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)" + "dj.Diagram(session)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(Workflow needs continued development to import geotyping tables)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'genotyping' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_30119/3562745389.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenotyping\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubject\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAllele\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'genotyping' is not defined" + ] + } + ], + "source": [ + "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)" ] }, { @@ -412,7 +1062,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -436,14 +1086,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-> subject.Subject\n", + "-> Subject\n", "-> subject.Allele\n", "---\n", "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\n", @@ -458,7 +1108,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -471,7 +1121,7 @@ "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity" ] }, - "execution_count": 14, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -482,103 +1132,6 @@ "subject.Zygosity.heading" ] }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Animal Subject\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

sex

\n", - " \n", - "
\n", - "

subject_birth_date

\n", - " \n", - "
\n", - "

subject_description

\n", - " \n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], - "text/plain": [ - "*subject sex subject_birth_ subject_descri\n", - "+---------+ +-----+ +------------+ +------------+\n", - "\n", - " (Total: 0)" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# check table contents\n", - "subject.Subject()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -595,7 +1148,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -609,7 +1162,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -687,10 +1240,16 @@ "test animalsubject2\n", "F\n", "2020-11-30\n", - "test animal \n", + "test animalsubject5\n", + "F\n", + "2020-01-03\n", + "lmash_E105subject6\n", + "M\n", + "2020-01-03\n", + "hneih_E105 \n", " \n", " \n", - "

Total: 2

\n", + "

Total: 4

\n", " " ], "text/plain": [ @@ -698,10 +1257,12 @@ "+----------+ +-----+ +------------+ +------------+\n", "subject1 M 2020-12-30 test animal \n", "subject2 F 2020-11-30 test animal \n", - " (Total: 2)" + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", + " (Total: 4)" ] }, - "execution_count": 24, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -712,7 +1273,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -721,14 +1282,11 @@ " [dict(subject='subject3', sex='F', subject_birth_date='2020-12-30', \n", " subject_description='test animal'),\n", " dict(subject='subject4', sex='M', subject_birth_date='2021-02-12', \n", - " subject_description='test animal'),\n", - " dict(subject='subject5', sex='U', subject_birth_date='2020-12-30', \n", - " subject_description='test animal'),\n", + " subject_description='test animal')\n", " ]\n", ")\n", "subject.Subject.insert(\n", " [\n", - " ('subject6', 'M', '2020-07-30', 'test animal'),\n", " ('subject7', 'U', '2020-08-30', 'test animal'),\n", " ('subject8', 'F', '2020-09-30', 'test animal')\n", " ]\n", @@ -737,7 +1295,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -822,12 +1380,12 @@ "M\n", "2021-02-12\n", "test animalsubject5\n", - "U\n", - "2020-12-30\n", - "test animalsubject6\n", + "F\n", + "2020-01-03\n", + "lmash_E105subject6\n", "M\n", - "2020-07-30\n", - "test animalsubject7\n", + "2020-01-03\n", + "hneih_E105subject7\n", "U\n", "2020-08-30\n", "test animalsubject8\n", @@ -846,14 +1404,14 @@ "subject2 F 2020-11-30 test animal \n", "subject3 F 2020-12-30 test animal \n", "subject4 M 2021-02-12 test animal \n", - "subject5 U 2020-12-30 test animal \n", - "subject6 M 2020-07-30 test animal \n", + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", "subject7 U 2020-08-30 test animal \n", "subject8 F 2020-09-30 test animal \n", " (Total: 8)" ] }, - "execution_count": 27, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } diff --git a/temp_test.ipynb b/temp_test.ipynb index 236734f..dd681a4 100644 --- a/temp_test.ipynb +++ b/temp_test.ipynb @@ -2,507 +2,39 @@ "cells": [ { "cell_type": "code", - "execution_count": 10, + "execution_count": 1, "id": "f13c2734-23db-435f-bc9e-68064cdcc82b", "metadata": {}, - "outputs": [], - "source": [ - "import datajoint as dj\n", - "dj.conn()\n", - "import csv" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cb32b7a1-37b4-45c1-8d03-02c8bbd09b04", - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_session.pipeline import lab, subject, session" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "b4a7e312-63ba-4380-9e91-02ee518ab77e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

lab

\n", - " Abbreviated lab name\n", - "
\n", - "

lab_name

\n", - " full lab name\n", - "
\n", - "

institution

\n", - " \n", - "
\n", - "

address

\n", - " \n", - "
\n", - "

time_zone

\n", - " UTC offset suggested e.g., UTC+1\n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], - "text/plain": [ - "*lab lab_name institution address time_zone \n", - "+-----+ +----------+ +------------+ +---------+ +-----------+\n", - "\n", - " (Total: 0)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lab.Lab()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "ac97aba9-6451-41e6-a645-4e75b7dc5c50", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Animal Subject\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

sex

\n", - " \n", - "
\n", - "

subject_birth_date

\n", - " \n", - "
\n", - "

subject_description

\n", - " \n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], - "text/plain": [ - "*subject sex subject_birth_ subject_descri\n", - "+---------+ +-----+ +------------+ +------------+\n", - "\n", - " (Total: 0)" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "subject.Subject()" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "142729e3-94c9-4d1b-8748-4c14e7da37fb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

subject

\n", - " \n", - "
\n", - "

session_datetime

\n", - " \n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], - "text/plain": [ - "*subject *session_datet\n", - "+---------+ +------------+\n", - "\n", - " (Total: 0)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "session.Session()" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "id": "ac95397f-87e9-47eb-a6f6-198581885230", - "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Deleting 2 rows from `neuro_lab`.`#location`\n", - "Deleting 2 rows from `neuro_lab`.`#lab`\n" - ] - }, - { - "name": "stdin", - "output_type": "stream", - "text": [ - "Commit deletes? [yes, No]: yes\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deletes committed.\n", - "Deleting 2 rows from `neuro_lab`.`#project`\n" - ] - }, - { - "name": "stdin", - "output_type": "stream", - "text": [ - "Commit deletes? [yes, No]: yes\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deletes committed.\n", - "Deleting 2 rows from `neuro_lab`.`#protocol`\n" + "Connecting root@localhost:3306\n" ] - }, - { - "name": "stdin", - "output_type": "stream", - "text": [ - "Commit deletes? [yes, No]: yes\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Deletes committed.\n" - ] - }, - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 103, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "lab.Lab.delete()\n", - "lab.Project.delete()\n", - "lab.Protocol.delete()" - ] - }, - { - "cell_type": "code", - "execution_count": 142, - "id": "f5676e0b-3908-4dc0-b9f9-dbabb6ed5f2f", - "metadata": {}, - "outputs": [], - "source": [ - "import element_data_loader.utils\n", - "from workflow_session.paths import *" + "import datajoint as dj\n", + "dj.conn()\n", + "import csv" ] }, { "cell_type": "code", - "execution_count": 144, - "id": "e31cb826-30ba-459c-9722-e0dc531ef919", + "execution_count": 2, + "id": "cb32b7a1-37b4-45c1-8d03-02c8bbd09b04", "metadata": {}, "outputs": [], "source": [ - "def get_root_data_dir():\n", - " root_data_dirs = dj.config.get('custom', {}).get('root_data_dir', None)\n", - " return root_data_dirs if root_data_dirs else None" - ] - }, - { - "cell_type": "code", - "execution_count": 159, - "id": "6eccb1c1-64f2-4361-92b1-7f59e3d015a3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Project's resulting publications\n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

project

\n", - " \n", - "
\n", - "

publication

\n", - " \n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], - "text/plain": [ - "*project *publication \n", - "+---------+ +------------+\n", - "\n", - " (Total: 0)" - ] - }, - "execution_count": 159, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lab.Project.Publication()" + "from workflow_session.pipeline import lab, subject, session\n", + "from workflow_session.ingest import *" ] }, { "cell_type": "code", - "execution_count": 160, - "id": "c1c0ec34-5e7b-4c9d-b78a-832470411d7d", + "execution_count": 3, + "id": "e94beab4-02f3-464d-989f-24651a8bef8a", "metadata": {}, "outputs": [ { @@ -525,550 +57,149 @@ } ], "source": [ - "def ingest_lab(lab_csv_path='./user_data/lab/labs.csv',\n", - " project_csv_path='./user_data/lab/projects.csv',\n", - " pubs_csv_path='./user_data/lab/publications.csv',\n", - " keyw_csv_path='./user_data/lab/keywords.csv',\n", - " protocol_csv_path='./user_data/lab/protocols.csv'\n", - " ):\n", - " # -------------- Insert new \"Lab\" --------------\n", - " with open(lab_csv_path, newline= '') as f:\n", - " input_labs = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert {len(input_labs)} entry(s) into lab tables ----')\n", - " lab.Lab.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True)\n", - " lab.Location.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True)\n", - " # -------------- Insert new \"Project\" --------------\n", - " with open(project_csv_path, newline= '') as f:\n", - " input_projs = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert {len(input_projs)} entry(s) into project table ----')\n", - " lab.Project.insert(input_projs, skip_duplicates=True, ignore_extra_fields=True)\n", - " # -------------- Insert publications + keywords --------------\n", - " with open(pubs_csv_path, newline= '') as f:\n", - " input_pubs = list(csv.DictReader(f, delimiter=','))\n", - " with open(keyw_csv_path, newline= '') as f:\n", - " input_keyw = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert entry(s) into publication/keyword tables ----')\n", - " lab.Project.Publication.insert(input_pubs, skip_duplicates=True, ignore_extra_fields=True)\n", - " lab.Project.Keywords.insert(input_keyw, skip_duplicates=True, ignore_extra_fields=True)\n", - "\n", - " # -------------- Insert new \"Protocol\" --------------\n", - " with open(protocol_csv_path, newline= '') as f:\n", - " input_prots = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert {len(input_prots)} entry(s) into protocol tables ----')\n", - " lab.Protocol.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True)\n", - " lab.ProtocolType.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True)\n", - "def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'):\n", - " # -------------- Insert new \"Subject\" --------------\n", - " with open(subject_csv_path, newline= '') as f:\n", - " input_subjects = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert {len(input_subjects)} entry(s) into subject tables ----')\n", - " subject.Subject.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", - " subject.SubjectDeath.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", - " subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True)\n", - " ## Skipped allele info\n", - "def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'):\n", - " with open(session_csv_path, newline= '') as f:\n", - " input_sessions = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Insert {len(input_sessions)} entry(s) into session.Session ----')\n", - " session.Session.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", - " session.SessionDirectory.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", - " session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True)\n", "ingest_lab(); ingest_subjects();ingest_sessions()" ] }, { "cell_type": "code", - "execution_count": 77, - "id": "0fc98f1f-ca72-4586-bec2-a36ff68ced8e", + "execution_count": 4, + "id": "e306d9dc-ce9a-4485-b8da-eea7d852e5aa", "metadata": {}, "outputs": [ { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

lab

\n", - " Abbreviated lab name\n", - "
\n", - "

lab_name

\n", - " full lab name\n", - "
\n", - "

institution

\n", - " \n", - "
\n", - "

address

\n", - " \n", - "
\n", - "

time_zone

\n", - " UTC offset suggested e.g., UTC+1\n", - "
LabAThe Example LabExample Uni221B Baker St,London NW1 6XE,UKUTC+0
\n", - " \n", - "

Total: 1

\n", - " " - ], - "text/plain": [ - "*lab lab_name institution address time_zone \n", - "+------+ +------------+ +------------+ +------------+ +-----------+\n", - "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", - " (Total: 1)" - ] - }, - "execution_count": 77, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +-----------+\n", + "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", + " (Total: 1)\n", + "\n", + "*project project_descri repositoryurl repositoryname pharmacology viruses slices stimulus surgery \n", + "+---------+ +------------+ +------------+ +------------+ +------------+ +---------+ +--------+ +------------+ +---------+\n", + "ProjA Example projec https://github element-lab videos generat \n", + " (Total: 1)\n", + "\n", + "*protocol protocol_type protocol_descr\n", + "+----------+ +------------+ +------------+\n", + "ProtA IRB expedited Protocol for m\n", + " (Total: 1)\n", + "\n" + ] } ], "source": [ - "lab.Lab & 'lab=\"LabA\"'" + "print(lab.Lab & 'lab=\"LabA\"')\n", + "print(lab.Project & 'project=\"ProjA\"')\n", + "print(lab.Protocol() & 'protocol=\"ProtA\"')\n", + "\n", + "lab_info = (lab.Lab & 'lab=\"LabA\"').fetch1()\n", + "proj_info = (lab.Project & 'project=\"ProjA\"').fetch1()\n", + "prot_info = (lab.Protocol() & 'protocol=\"ProtA\"').fetch1()" ] }, { "cell_type": "code", - "execution_count": 78, - "id": "8d20561d-e4a9-47cd-8eca-0c8295d48507", + "execution_count": 5, + "id": "fd053496-eec1-4035-9369-fdf96e53fa95", "metadata": {}, "outputs": [ { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " Project's resulting publications\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "
\n", - "

project

\n", - " \n", - "
\n", - "

publication

\n", - " \n", - "
ProjAarXiv:1807.11104
ProjAarXiv:1807.11104v1
\n", - " \n", - "

Total: 2

\n", - " " - ], - "text/plain": [ - "*project *publication \n", - "+---------+ +------------+\n", - "ProjA arXiv:1807.111\n", - "ProjA arXiv:1807.111\n", - " (Total: 2)" - ] - }, - "execution_count": 162, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lab.Project & 'project=\"ProjA\"'" - ] - }, - { - "cell_type": "code", - "execution_count": 88, - "id": "4a8b2aa9-dbd1-4c1f-ac02-39373c9bc293", - "metadata": {}, - "outputs": [ + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + }, { "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " protocol approved by some institutions like IACUC, IRB\n", - "
\n", - " \n", - " \n", - " \n", - "\n", - "\n", - "
\n", - "

protocol

\n", - " \n", - "
\n", - "

protocol_type

\n", - " \n", - "
\n", - "

protocol_description

\n", - " \n", - "
ProtAIRB expedited reviewProtocol for managing data ingestion
\n", - " \n", - "

Total: 1

\n", - " " - ], "text/plain": [ - "*protocol protocol_type protocol_descr\n", - "+----------+ +------------+ +------------+\n", - "ProtA IRB expedited Protocol for m\n", - " (Total: 1)" + "root pynwb.file.NWBFile at 0x140690194467040\n", + "Fields:\n", + " file_create_date: [datetime.datetime(2021, 12, 1, 14, 22, 4, 992693, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00" ] }, - "execution_count": 88, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lab.Protocol() & 'protocol=\"ProtA\"'" + "import pynwb\n", + "from element_session.export import *\n", + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", + "mylab_key = (lab.Lab & 'lab=\"LabA\"').fetch1('KEY')\n", + "myproj_key= (lab.Project & 'project=\"ProjA\"').fetch1('KEY')\n", + "myprot_key= (lab.Protocol() & 'protocol=\"ProtA\"').fetch1('KEY')\n", + "session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key)" ] }, { "cell_type": "code", - "execution_count": 105, - "id": "e306d9dc-ce9a-4485-b8da-eea7d852e5aa", - "metadata": {}, - "outputs": [], - "source": [ - "lab_info = (lab.Lab & 'lab=\"LabA\"').fetch1()\n", - "proj_info = (lab.Project & 'project=\"ProjA\"').fetch1()\n", - "prot_info = (lab.Protocol() & 'protocol=\"ProtA\"').fetch1()" - ] - }, - { - "cell_type": "code", - "execution_count": 131, - "id": "68fc25cc-d69e-4836-9e95-7f4fa727cbf4", + "execution_count": 32, + "id": "a3e1f490-7510-4f08-af66-c106f640928b", "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \n", - " \n", - " \n", - "
\n", - "

lab

\n", - " Abbreviated lab name\n", - "
\n", - "

lab_name

\n", - " full lab name\n", - "
\n", - "

institution

\n", - " \n", - "
\n", - "

address

\n", - " \n", - "
\n", - "

time_zone

\n", - " UTC offset suggested e.g., UTC+1\n", - "
\n", - " \n", - "

Total: 0

\n", - " " - ], "text/plain": [ - "*lab lab_name institution address time_zone \n", - "+-----+ +----------+ +------------+ +---------+ +-----------+\n", - "\n", - " (Total: 0)" + "root pynwb.file.NWBFile at 0x140337549119696\n", + "Fields:\n", + " file_create_date: [datetime.datetime(2021, 12, 1, 14, 21, 26, 638467, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " protocol: ProtA\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00" ] }, - "execution_count": 131, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "lab.Lab & 'lab=\"None\"'" - ] - }, - { - "cell_type": "code", - "execution_count": 92, - "id": "99a87d22-a369-45a2-96a7-5c67bf27accb", - "metadata": {}, - "outputs": [], - "source": [ - "from pynwb import NWBFile" - ] - }, - { - "cell_type": "code", - "execution_count": 178, - "id": "bb35e7ae-f76c-4d1c-b1d2-6ddd68cdeef3", - "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "NWBFile.__init__: missing argument 'session_description', missing argument 'identifier', missing argument 'session_start_time', incorrect type for 'stimulus' (got 'str', expected 'list or tuple'), unrecognized argument: 'file_name'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24827/1508549328.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mproj_keyw\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mKeywords\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mproject_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'keyword'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtolist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mproj_pubs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlab\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mProject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mPublication\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0mproject_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'publication'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtolist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m NWBFile(\n\u001b[0m\u001b[1;32m 6\u001b[0m \u001b[0;31m# data_collection='',\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mexperiment_description\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mproj_info\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'project_description'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/utils.py\u001b[0m in \u001b[0;36mfunc_call\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 577\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_method\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 578\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfunc_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 579\u001b[0;31m \u001b[0mpargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_check_args\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 580\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mpargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 581\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/utils.py\u001b[0m in \u001b[0;36m_check_args\u001b[0;34m(args, kwargs)\u001b[0m\n\u001b[1;32m 570\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mparse_err\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 571\u001b[0m \u001b[0mmsg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'%s: %s'\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mfunc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__qualname__\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m', '\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparse_err\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 572\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mExceptionType\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 573\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 574\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mparsed\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'args'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: NWBFile.__init__: missing argument 'session_description', missing argument 'identifier', missing argument 'session_start_time', incorrect type for 'stimulus' (got 'str', expected 'list or tuple'), unrecognized argument: 'file_name'" - ] - } - ], - "source": [ - "project_key = 'project=\"ProjA\"'\n", - "proj_info = (lab.Project & project_key).fetch1()\n", - "proj_keyw = (lab.Project.Keywords() & project_key).fetch('keyword').tolist()\n", - "proj_pubs = (lab.Project.Publication() & project_key).fetch('publication').tolist()\n", - "NWBFile(\n", - " # data_collection='',\n", - " experiment_description=proj_info['project_description'],\n", - " keywords=proj_keyw,\n", - " notes=prot_info['protocol_description'],\n", - " pharmacology=proj_info['pharmacology'],\n", - " related_publications=proj_pubs,\n", - " slices=proj_info['slices'],\n", - " source_script=proj_info['repositoryurl'],\n", - " file_name=proj_info['repositoryname'],\n", - " stimulus=proj_info['stimulus'],\n", - " surgery=proj_info['surgery']\n", - ")" + "mynwbfile" ] }, { "cell_type": "code", - "execution_count": 130, - "id": "0c69fff1-3cef-4d99-9ad8-3525dedf3fec", - "metadata": {}, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid syntax (255707863.py, line 1)", - "output_type": "error", - "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_24827/255707863.py\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m tmp := proj_info['project']\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" - ] - } - ], - "source": [ - "if tmp := proj_info['virus']: virus=proj_info['virus']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1540c2e4-05dc-4543-a239-c82d1f4445f0", + "execution_count": 100, + "id": "d982c732-8a8f-435d-ab16-aa37a0e99904", "metadata": {}, "outputs": [], "source": [ - " NWBFile(identifier='_'.join(session_identifier.values()),\n", - " session_description=session_info['session_note'] if session_info['session_note'] else '',\n", - " session_start_time=session_info['session_datetime'],\n", - " file_create_date=datetime.now(tzlocal()),\n", - " experimenter=list(experimenters)" + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", + "session_identifier = {}\n", + "for k, v in session_key.items():\n", + " session_identifier[k] = v.strftime('%Y%m%d_%H%M%S') if isinstance(v, datetime) else v\n", + "\n", + "session_info = (session.Session & session_key).join(session.SessionNote, left=True).fetch1()\n", + "\n", + "def mytuple():\n", + " identifier='_'.join(session_identifier.values()),\n", + " session_description=session_info['session_note'] if session_info['session_note'] else '',\n", + " session_start_time=session_info['session_datetime']\n", + " return identifier,session_description,session_start_time\n", + "info=dict(identifier='_'.join(session_identifier.values()),\n", + " session_description='Note',\n", + " session_start_time=session_info['session_datetime'],\n", + " institution='')\n", + "info={k: v for k, v in info.items() if v} #drop empty\n", + "asstring = ','.join('='.join((str(key),val)) for (key,val) in info.items())" ] }, { "cell_type": "code", "execution_count": null, - "id": "927430ff-2224-49ab-973a-c4c666c7688d", + "id": "ef8b5fb3-8b19-403c-a954-150c1ec8cf41", "metadata": {}, "outputs": [], "source": [] diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 0f96b20..eca6d91 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -2,7 +2,7 @@ import csv import re -from workflow_session.pipeline import lab, Source, Lab, Protocol, User, Location, subject, genotyping, session +from workflow_session.pipeline import lab, subject, session from workflow_session.paths import get_root_data_dir import element_data_loader.utils From 6cbecd3e498f852866b3ea8e768d2e7e00454f4e Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 1 Dec 2021 15:19:56 -0600 Subject: [PATCH 04/46] Adding NB for export example --- LICENSE | 2 +- ...orkflow.ipynb => 1_Explore_Workflow.ipynb} | 0 notebooks/2_Explore_Export.ipynb | 152 ++++++++++++++++++ setup.py | 2 +- 4 files changed, 154 insertions(+), 2 deletions(-) rename notebooks/{explore_workflow.ipynb => 1_Explore_Workflow.ipynb} (100%) create mode 100644 notebooks/2_Explore_Export.ipynb diff --git a/LICENSE b/LICENSE index a9f8903..6bf141b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 DataJoint NEURO +Copyright (c) 2021 DataJoint Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/notebooks/explore_workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb similarity index 100% rename from notebooks/explore_workflow.ipynb rename to notebooks/1_Explore_Workflow.ipynb diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb new file mode 100644 index 0000000..c114077 --- /dev/null +++ b/notebooks/2_Explore_Export.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3054518f-87bc-42ff-a3e7-84bf3d2a37f6", + "metadata": {}, + "source": [ + "# DataJoint U24 - Export Session" + ] + }, + { + "cell_type": "markdown", + "id": "79c15f36-039d-4304-96be-f56ba0d6b10a", + "metadata": {}, + "source": [ + "Same as before, import data." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "39dda42b-a81b-4f04-8ec4-eef559168379", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n", + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "import os\n", + "os.chdir('..')\n", + "import datajoint as dj\n", + "dj.conn()\n", + "from workflow_session.pipeline import * \n", + "from workflow_session.ingest import *\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "markdown", + "id": "ab2a3f10-b96e-4f0d-9e54-0015bb9b8622", + "metadata": {}, + "source": [ + "Identify items for export with keys." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "76c040c8-15cc-4d61-ae5d-bc7646a6a0be", + "metadata": {}, + "outputs": [], + "source": [ + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", + "mylab_key = (lab.Lab & 'lab=\"LabA\"').fetch1('KEY')\n", + "myproj_key= (lab.Project & 'project=\"ProjA\"').fetch1('KEY')\n", + "myprot_key= (lab.Protocol() & 'protocol=\"ProtA\"').fetch1('KEY')" + ] + }, + { + "cell_type": "markdown", + "id": "902e050c-3133-4fb7-850d-f0b954e1b634", + "metadata": {}, + "source": [ + "Get export function and related pynwb dependency, then export with keys from prev step." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e76fa25d-700b-4f9a-9bb4-62d2217288b6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + }, + { + "data": { + "text/plain": [ + "root pynwb.file.NWBFile at 0x140227470306368\n", + "Fields:\n", + " file_create_date: [datetime.datetime(2021, 12, 1, 15, 14, 12, 86034, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pynwb\n", + "from element_session.export import *\n", + "session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key)" + ] + }, + { + "cell_type": "markdown", + "id": "1bccad09-d5e4-4200-bb73-08ddf98cfb80", + "metadata": {}, + "source": [ + "Learn more about using NWB formats [here](https://www.nwb.org/how-to-use/)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/setup.py b/setup.py index 30ecffe..edf9b88 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ requirements = f.read().splitlines() setup( - name='workflow-animal', + name='workflow-session', version='0.0.1', description="DataJoint Elements for Animal Management", long_description=long_description, From 544db358ba951f39c05e1558744da2ab7ffd3a0b Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 1 Dec 2021 17:46:18 -0600 Subject: [PATCH 05/46] more detail in NB output --- .gitignore | 3 +- notebooks/2_Explore_Export.ipynb | 84 +++++++++++++++++++++++--------- requirements.txt | 1 + temp_test.ipynb | 13 ++++- user_data/lab/projects.csv | 2 +- 5 files changed, 78 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 1711db3..77113c7 100644 --- a/.gitignore +++ b/.gitignore @@ -111,8 +111,9 @@ ENV/ # datajoint dj_local_c*.json -# notes +# notes, output nwb file from export NB temp* +*nwb # emacs **/*~ diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index c114077..de64b60 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -16,10 +16,21 @@ "Same as before, import data." ] }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0e7fa407-d67b-403e-975c-bd0bd499d88c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "if os.path.basename(os.getcwd())!='workflow-session': os.chdir('..')" + ] + }, { "cell_type": "code", "execution_count": 2, - "id": "39dda42b-a81b-4f04-8ec4-eef559168379", + "id": "6f11ca71-5e4f-460c-ad94-2037ef0f6448", "metadata": {}, "outputs": [ { @@ -43,12 +54,12 @@ } ], "source": [ - "import os\n", - "os.chdir('..')\n", "import datajoint as dj\n", "dj.conn()\n", "from workflow_session.pipeline import * \n", "from workflow_session.ingest import *\n", + "## delete if needed, to update/repopulate\n", + "# lab.Lab.delete();lab.Project.delete();lab.Protocol.delete()\n", "ingest_lab(); ingest_subjects();ingest_sessions()" ] }, @@ -94,29 +105,11 @@ "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" ] - }, - { - "data": { - "text/plain": [ - "root pynwb.file.NWBFile at 0x140227470306368\n", - "Fields:\n", - " file_create_date: [datetime.datetime(2021, 12, 1, 15, 14, 12, 86034, tzinfo=tzlocal())]\n", - " identifier: subject5_20200415_111638\n", - " institution: Example Uni\n", - " session_description: Successful data collection, no notes\n", - " session_start_time: 2020-04-15 11:16:38-05:00\n", - " timestamps_reference_time: 2020-04-15 11:16:38-05:00" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "import pynwb\n", "from element_session.export import *\n", - "session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key)" + "mynwbfile=session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)" ] }, { @@ -126,6 +119,53 @@ "source": [ "Learn more about using NWB formats [here](https://www.nwb.org/how-to-use/)." ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b914d8db-2584-403c-9f24-12a64103f1cb", + "metadata": {}, + "outputs": [], + "source": [ + "from pynwb import NWBHDF5IO\n", + "with NWBHDF5IO('session_metadata.nwb', mode='w') as io:\n", + " io.write(mynwbfile)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c81b1c85-9623-4cc1-9438-79b0c5287756", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root pynwb.file.NWBFile at 0x140188285966704\n", + "Fields:\n", + " experiment_description: Example project to populate element-lab\n", + " file_create_date: [datetime.datetime(2021, 12, 1, 17, 43, 52, 279976, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " keywords: ['Example' 'Study']\n", + " lab: The Example Lab\n", + " pharmacology: Subjects were administered 10ul sedative prior to surgery\n", + " protocol: ProtA\n", + " related_publications: ['arXiv:1807.11104' 'arXiv:1807.11104v1']\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " source_script: https://github.com/datajoint/element-lab/\n", + " source_script_file_name: DataJoint element-session/export/nwb.py\n", + " surgery: Craniotomy performed by session experimenter\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00\n", + "\n" + ] + } + ], + "source": [ + "print(mynwbfile)" + ] } ], "metadata": { diff --git a/requirements.txt b/requirements.txt index 147ef16..b6227cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ element-lab element-animal element-session ipykernel +pynwb \ No newline at end of file diff --git a/temp_test.ipynb b/temp_test.ipynb index dd681a4..5a691f8 100644 --- a/temp_test.ipynb +++ b/temp_test.ipynb @@ -198,10 +198,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "ef8b5fb3-8b19-403c-a954-150c1ec8cf41", "metadata": {}, "outputs": [], + "source": [ + "a=None;b=None;c=None\n", + "if [x for x in (a, b,c) if x is not None]: print('g')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91347a4f-9024-4a33-a1b1-42d903d9b132", + "metadata": {}, + "outputs": [], "source": [] } ], diff --git a/user_data/lab/projects.csv b/user_data/lab/projects.csv index aed6d38..e4ad602 100644 --- a/user_data/lab/projects.csv +++ b/user_data/lab/projects.csv @@ -1,3 +1,3 @@ project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl -ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab +ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,Exemplarvirus administered 10d before experimental session,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab ProjB,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,,Exemplarvirus administered 10d before experimental session,,,,https://github.com/datajoint/element-lab/tree/main/element_lab From 7fc3a3a380a85e2107518c6523d3af281bc9469e Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 6 Dec 2021 17:04:24 -0600 Subject: [PATCH 06/46] Update to reflect sess not depend on lab --- notebooks/1_Explore_Workflow.ipynb | 874 ++++++++++++++++------------- notebooks/2_Explore_Export.ipynb | 131 ++++- workflow_session/ingest.py | 5 +- workflow_session/pipeline.py | 3 +- 4 files changed, 617 insertions(+), 396 deletions(-) diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index c2aadfd..050cb9b 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -41,7 +41,7 @@ "source": [ "# change to the upper level folder to detect dj_local_conf.json\n", "import os\n", - "os.chdir('..')\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", "import datajoint as dj\n", "dj.conn()" ] @@ -55,7 +55,19 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from element_lab import lab\n", + "from element_animal import subject\n", + "from element_session import session\n", + "from workflow_session.ingest import ingest_lab, ingest_subjects, ingest_sessions" + ] + }, + { + "cell_type": "code", + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -78,8 +90,6 @@ } ], "source": [ - "from workflow_session.pipeline import * \n", - "from workflow_session.ingest import *\n", "ingest_lab(); ingest_subjects();ingest_sessions()" ] }, @@ -92,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -189,7 +199,7 @@ " (Total: 2)" ] }, - "execution_count": 8, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -200,152 +210,209 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", "`neuro_lab`.`#skull_reference`\n", "\n", "`neuro_lab`.`#skull_reference`\n", "\n", - "\n", + "\n", "\n", - "lab.Location\n", - "lab.Equipment.CaImgEquipment\n", + "\n", - "\n", - "lab.Location\n", + "\n", + "lab.Equipment.CaImgEquipment\n", "\n", "\n", "\n", "\n", "\n", "lab.ProjectUser\n", - "\n", - "\n", - "lab.ProjectUser\n", + "\n", + "lab.ProjectUser\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "lab.UserRole\n", - "lab.Project.Keywords\n", + "\n", + "\n", + "lab.Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Protocol\n", + "\n", - "\n", - "lab.UserRole\n", + "\n", + "lab.Protocol\n", "\n", "\n", "\n", + "\n", + "\n", + "lab.ProtocolType->lab.Protocol\n", + "\n", + "\n", "\n", "\n", "lab.LabMembership\n", - "\n", - "\n", - "lab.LabMembership\n", + "\n", + "lab.LabMembership\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.UserRole->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "lab.Location\n", + "\n", "\n", - "\n", - "\n", - "Project.Keywords\n", - "\n", + "\n", + "\n", + "lab.Project.Publication\n", + "\n", - "\n", - "Project.Keywords\n", + "\n", + "lab.Project.Publication\n", "\n", "\n", "\n", - "\n", - "\n", - "Lab\n", - "\n", + "\n", + "lab.UserRole\n", + "\n", - "\n", - "Lab\n", + "\n", + "lab.UserRole\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Lab->lab.Location\n", - "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", "\n", - "\n", - "\n", - "Lab->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "lab.Source\n", + "\n", "\n", - "\n", - "\n", - "Project.Sourcecode\n", - "\n", + "\n", + "\n", + "lab.Equipment\n", + "\n", - "\n", - "Project.Sourcecode\n", + "\n", + "lab.Equipment\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.ProtocolType\n", - "\n", + "\n", + "lab.Equipment->lab.Equipment.CaImgEquipment\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment.EphysEquipment\n", + "\n", - "\n", - "lab.ProtocolType\n", + "\n", + "lab.Equipment.EphysEquipment\n", "\n", "\n", "\n", - "\n", - "\n", - "Protocol\n", - "\n", + "\n", + "lab.Equipment->lab.Equipment.EphysEquipment\n", + "\n", + "\n", + "\n", + "\n", + "lab.User\n", + "\n", - "\n", - "Protocol\n", + "\n", + "lab.User\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.ProtocolType->Protocol\n", - "\n", + "\n", + "\n", + "lab.User->lab.ProjectUser\n", + "\n", "\n", - "\n", - "\n", - "Project\n", - "\n", + "\n", + "lab.User->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project\n", + "\n", - "\n", - "Project\n", + "\n", + "lab.Project\n", "\n", "\n", "\n", - "\n", - "\n", - "Project->lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "Project->Project.Keywords\n", - "\n", - "\n", - "\n", + "\n", "\n", - "Project->Project.Sourcecode\n", - "\n", + "lab.Project->lab.ProjectUser\n", + "\n", "\n", - "\n", - "\n", - "Project.Publication\n", - "\n", - "\n", - "Project.Publication\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", - "Project->Project.Publication\n", - "\n", + "lab.Project->lab.Project.Keywords\n", + "\n", "\n", - "\n", - "\n", - "Source\n", - "\n", + "\n", + "lab.Project->lab.Project.Publication\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Sourcecode\n", + "\n", - "\n", - "Source\n", + "\n", + "lab.Project.Sourcecode\n", "\n", "\n", "\n", - "\n", - "\n", - "User\n", - "\n", + "\n", + "lab.Project->lab.Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "lab.Lab\n", + "\n", - "\n", - "User\n", + "\n", + "lab.Lab\n", "\n", "\n", "\n", - "\n", - "\n", - "User->lab.ProjectUser\n", - "\n", + "\n", + "\n", + "lab.Lab->lab.LabMembership\n", + "\n", "\n", - "\n", - "\n", - "User->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Lab->lab.Location\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -447,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -519,27 +574,51 @@ "

subject_description

\n", " \n", " \n", - " subject5\n", + " subject1\n", + "M\n", + "2020-12-30\n", + "test animalsubject2\n", + "F\n", + "2020-11-30\n", + "test animalsubject3\n", + "F\n", + "2020-12-30\n", + "test animalsubject4\n", + "M\n", + "2021-02-12\n", + "test animalsubject5\n", "F\n", "2020-01-03\n", "lmash_E105subject6\n", "M\n", "2020-01-03\n", - "hneih_E105 \n", + "hneih_E105subject7\n", + "U\n", + "2020-08-30\n", + "test animalsubject8\n", + "F\n", + "2020-09-30\n", + "test animal \n", " \n", " \n", - "

Total: 2

\n", + "

Total: 8

\n", " " ], "text/plain": [ "*subject sex subject_birth_ subject_descri\n", "+----------+ +-----+ +------------+ +------------+\n", + "subject1 M 2020-12-30 test animal \n", + "subject2 F 2020-11-30 test animal \n", + "subject3 F 2020-12-30 test animal \n", + "subject4 M 2021-02-12 test animal \n", "subject5 F 2020-01-03 lmash_E105 \n", "subject6 M 2020-01-03 hneih_E105 \n", - " (Total: 2)" + "subject7 U 2020-08-30 test animal \n", + "subject8 F 2020-09-30 test animal \n", + " (Total: 8)" ] }, - "execution_count": 10, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -550,285 +629,285 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "Subject.Strain\n", - "subject.Subject.Protocol\n", + "\n", - "\n", - "Subject.Strain\n", + "\n", + "subject.Subject.Protocol\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.SubjectDeath\n", - "subject.Subject.Source\n", + "\n", - "\n", - "subject.SubjectDeath\n", + "\n", + "subject.Subject.Source\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.SubjectCullMethod\n", - "subject.Allele.Source\n", + "\n", - "\n", - "subject.SubjectCullMethod\n", + "\n", + "subject.Allele.Source\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Zygosity\n", - "subject.Line.Allele\n", + "\n", - "\n", - "subject.Zygosity\n", + "\n", + "subject.Line.Allele\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Line\n", - "subject.Subject.Lab\n", + "\n", - "\n", - "subject.Line\n", + "\n", + "subject.Subject.Lab\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Line.Allele\n", - "subject.Strain\n", + "\n", - "\n", - "subject.Line.Allele\n", + "\n", + "subject.Strain\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Line->subject.Line.Allele\n", - "\n", - "\n", - "\n", - "\n", - "Subject.Line\n", - "\n", + "\n", + "subject.Subject.Strain\n", + "\n", - "\n", - "Subject.Line\n", + "\n", + "subject.Subject.Strain\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Line->Subject.Line\n", - "\n", + "\n", + "\n", + "subject.Strain->subject.Subject.Strain\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Allele.Source\n", - "subject.Allele\n", + "\n", - "\n", - "subject.Allele.Source\n", + "\n", + "subject.Allele\n", "\n", "\n", "\n", - "\n", - "\n", - "Subject.Protocol\n", - "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", - "\n", - "Subject.Protocol\n", + "\n", + "subject.Zygosity\n", "\n", "\n", "\n", - "\n", - "\n", - "Subject\n", - "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", - "\n", - "Subject\n", + "\n", + "subject.Subject\n", "\n", "\n", "\n", - "\n", - "\n", - "Subject->Subject.Strain\n", - "\n", - "\n", - "\n", - "\n", - "Subject->subject.SubjectDeath\n", - "\n", - "\n", - "\n", + "\n", "\n", - "Subject->subject.SubjectCullMethod\n", - "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", "\n", - "\n", + "\n", "\n", - "Subject->subject.Zygosity\n", - "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", "\n", - "\n", + "\n", "\n", - "Subject->Subject.Protocol\n", - "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", "\n", - "\n", + "\n", "\n", - "Subject->Subject.Line\n", - "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", "\n", - "\n", - "\n", - "Subject.User\n", - "\n", + "\n", + "subject.Subject.Line\n", + "\n", - "\n", - "Subject.User\n", + "\n", + "subject.Subject.Line\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Subject->Subject.User\n", - "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", "\n", - "\n", - "\n", - "Subject.Source\n", - "\n", + "\n", + "subject.Subject->subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", - "\n", - "Subject.Source\n", + "\n", + "subject.Subject.User\n", "\n", "\n", "\n", - "\n", - "\n", - "Subject->Subject.Source\n", - "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", "\n", - "\n", - "\n", - "Subject.Lab\n", - "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", - "\n", - "Subject.Lab\n", + "\n", + "subject.SubjectCullMethod\n", "\n", "\n", "\n", - "\n", - "\n", - "Subject->Subject.Lab\n", - "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", "\n", - "\n", - "\n", - "subject.Allele\n", - "\n", + "\n", + "subject.SubjectDeath\n", + "\n", - "\n", - "subject.Allele\n", + "\n", + "subject.SubjectDeath\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Allele->subject.Zygosity\n", - "\n", - "\n", - "\n", + "\n", "\n", - "subject.Allele->subject.Line.Allele\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->subject.Allele.Source\n", - "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", "\n", - "\n", - "\n", - "subject.Strain\n", - "\n", + "\n", + "subject.Line\n", + "\n", - "\n", - "subject.Strain\n", + "\n", + "subject.Line\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "subject.Line->subject.Line.Allele\n", + "\n", + "\n", + "\n", "\n", - "subject.Strain->Subject.Strain\n", - "\n", + "subject.Line->subject.Subject.Line\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 11, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -839,7 +918,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -905,23 +984,27 @@ "

session_datetime

\n", " \n", " \n", - " subject5\n", + " subject3\n", + "2021-04-30 12:22:15.032000subject5\n", "2020-04-15 11:16:38subject6\n", + "2021-01-15 11:16:38subject6\n", "2021-06-02 14:04:22 \n", " \n", " \n", - "

Total: 2

\n", + "

Total: 4

\n", " " ], "text/plain": [ "*subject *session_datet\n", "+----------+ +------------+\n", + "subject3 2021-04-30 12:\n", "subject5 2020-04-15 11:\n", + "subject6 2021-01-15 11:\n", "subject6 2021-06-02 14:\n", - " (Total: 2)" + " (Total: 4)" ] }, - "execution_count": 12, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -932,84 +1015,100 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "session.ProjectSession\n", - "session.SessionDirectory\n", + "\n", - "\n", - "session.ProjectSession\n", + "\n", + "session.SessionDirectory\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Session\n", - "session.Session\n", + "\n", - "\n", - "Session\n", + "\n", + "session.Session\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Session->session.ProjectSession\n", - "\n", + "session.Session->session.SessionDirectory\n", + "\n", "\n", "\n", "\n", "session.SessionNote\n", - "\n", - "\n", - "session.SessionNote\n", + "\n", + "session.SessionNote\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Session->session.SessionNote\n", - "\n", + "session.Session->session.SessionNote\n", + "\n", "\n", - "\n", + "\n", "\n", - "session.SessionDirectory\n", - "session.SessionExperimenter\n", + "\n", - "\n", - "session.SessionDirectory\n", + "\n", + "session.SessionExperimenter\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "Session->session.SessionDirectory\n", - "\n", + "session.Session->session.SessionExperimenter\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 13, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -1027,23 +1126,12 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'genotyping' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_30119/3562745389.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenotyping\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubject\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdj\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDiagram\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mAllele\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'genotyping' is not defined" - ] - } - ], + "outputs": [], "source": [ - "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)" + "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)\n", + "## Not yet defined" ] }, { @@ -1062,7 +1150,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -1086,14 +1174,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "-> Subject\n", + "-> subject.Subject\n", "-> subject.Allele\n", "---\n", "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\n", @@ -1108,7 +1196,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -1121,7 +1209,7 @@ "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity" ] }, - "execution_count": 17, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1148,9 +1236,29 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], + "execution_count": 14, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "tags": [] + }, + "outputs": [ + { + "ename": "DuplicateError", + "evalue": "(\"Duplicate entry 'subject1' for key 'PRIMARY'\", 'To ignore duplicate entries in insert, set skip_duplicates=True')", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDuplicateError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_46675/2258948651.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# `insert1()` takes a dict or a tuple\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m subject.Subject.insert1(\n\u001b[0m\u001b[1;32m 3\u001b[0m dict(subject='subject1', sex='M', subject_birth_date='2020-12-30', \n\u001b[1;32m 4\u001b[0m subject_description='test animal'))\n\u001b[1;32m 5\u001b[0m subject.Subject.insert1(\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36minsert1\u001b[0;34m(self, row, **kwargs)\u001b[0m\n\u001b[1;32m 264\u001b[0m \u001b[0mFor\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msee\u001b[0m \u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 265\u001b[0m \"\"\"\n\u001b[0;32m--> 266\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrow\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrows\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_duplicates\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mignore_extra_fields\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mallow_direct_insert\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36minsert\u001b[0;34m(self, rows, replace, skip_duplicates, ignore_extra_fields, allow_direct_insert)\u001b[0m\n\u001b[1;32m 335\u001b[0m 'To ignore extra fields in insert, set ignore_extra_fields=True')\n\u001b[1;32m 336\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mDuplicateError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 337\u001b[0;31m raise err.suggest(\n\u001b[0m\u001b[1;32m 338\u001b[0m 'To ignore duplicate entries in insert, set skip_duplicates=True')\n\u001b[1;32m 339\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mDuplicateError\u001b[0m: (\"Duplicate entry 'subject1' for key 'PRIMARY'\", 'To ignore duplicate entries in insert, set skip_duplicates=True')" + ] + } + ], "source": [ "# `insert1()` takes a dict or a tuple\n", "subject.Subject.insert1(\n", diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index de64b60..9cea320 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -24,7 +24,7 @@ "outputs": [], "source": [ "import os\n", - "if os.path.basename(os.getcwd())!='workflow-session': os.chdir('..')" + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')" ] }, { @@ -56,8 +56,10 @@ "source": [ "import datajoint as dj\n", "dj.conn()\n", - "from workflow_session.pipeline import * \n", - "from workflow_session.ingest import *\n", + "from element_lab import lab\n", + "from element_animal import subject\n", + "from element_session import session\n", + "from workflow_session.ingest import ingest_lab, ingest_subjects, ingest_sessions\n", "## delete if needed, to update/repopulate\n", "# lab.Lab.delete();lab.Project.delete();lab.Protocol.delete()\n", "ingest_lab(); ingest_subjects();ingest_sessions()" @@ -95,6 +97,33 @@ { "cell_type": "code", "execution_count": 4, + "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function session_to_nwb in module element_session.export.nwb:\n", + "\n", + "session_to_nwb(session_key)\n", + " Generate one NWBFile object representing all session-level information,\n", + " including session identifier, description, start time, etc.\n", + " \n", + " :param session_key: entry in session.Session table\n", + " :return: NWBFile object\n", + "\n" + ] + } + ], + "source": [ + "from element_session.export import session_to_nwb_dict, session_to_nwb\n", + "help(session_to_nwb)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "id": "e76fa25d-700b-4f9a-9bb4-62d2217288b6", "metadata": {}, "outputs": [ @@ -108,8 +137,72 @@ } ], "source": [ - "from element_session.export import *\n", - "mynwbfile=session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)" + "mynwbfile=session_to_nwb(session_key)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "56ebdd79-f0dc-4925-aeb1-109887df361d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function elemlab_to_nwb_dict in module element_lab.export.nwb:\n", + "\n", + "elemlab_to_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", + " Generate a dictionary object containing all relevant lab information used when\n", + " generating an NWB file at the session level. All parameters optional.\n", + " Use: mynwbfile = NWBfile(identifier=\"your identifier\",\n", + " session_description=\"your description\",\n", + " session_start_time=session_datetime,\n", + " elemlab_to_nwb_dict(lab_key=key1,project_key=key2,protocol_key=key3))\n", + " Note: The lab, project and protocol keys should specify one of their respective types.\n", + " \n", + " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", + " :param project_key: Key specifying one entry in element_lab.lab.Project\n", + " :param protocol_key: Key specifying one entry in element_lab.lab.PRotocol\n", + " :return: dictionary with NWB parameters\n", + "\n" + ] + } + ], + "source": [ + "from element_lab.export import elemlab_to_nwb_dict\n", + "help(elemlab_to_nwb_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "afc6555d-cd25-4d55-93e2-51c72ddba9ae", + "metadata": {}, + "outputs": [], + "source": [ + "from pynwb import NWBFile\n", + "lab_info = elemlab_to_nwb_dict(lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)\n", + "sess_info = session_to_nwb_dict(session_key)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "97e01cbf-b225-45d3-a030-0f7400e3526b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + } + ], + "source": [ + "mynwbfile = NWBFile(**sess_info,**lab_info)" ] }, { @@ -122,10 +215,19 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "id": "b914d8db-2584-403c-9f24-12a64103f1cb", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/build/objectmapper.py:653: MissingRequiredBuildWarning: NWBFile 'root' is missing required value for attribute 'source_script_file_name'.\n", + " warnings.warn(msg, MissingRequiredBuildWarning)\n" + ] + } + ], "source": [ "from pynwb import NWBHDF5IO\n", "with NWBHDF5IO('session_metadata.nwb', mode='w') as io:\n", @@ -134,7 +236,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "id": "c81b1c85-9623-4cc1-9438-79b0c5287756", "metadata": {}, "outputs": [ @@ -142,10 +244,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "root pynwb.file.NWBFile at 0x140188285966704\n", + "root pynwb.file.NWBFile at 0x140190306285216\n", "Fields:\n", " experiment_description: Example project to populate element-lab\n", - " file_create_date: [datetime.datetime(2021, 12, 1, 17, 43, 52, 279976, tzinfo=tzlocal())]\n", + " file_create_date: [datetime.datetime(2021, 12, 6, 17, 1, 11, 974467, tzinfo=tzlocal())]\n", " identifier: subject5_20200415_111638\n", " institution: Example Uni\n", " keywords: ['Example' 'Study']\n", @@ -156,7 +258,6 @@ " session_description: Successful data collection, no notes\n", " session_start_time: 2020-04-15 11:16:38-05:00\n", " source_script: https://github.com/datajoint/element-lab/\n", - " source_script_file_name: DataJoint element-session/export/nwb.py\n", " surgery: Craniotomy performed by session experimenter\n", " timestamps_reference_time: 2020-04-15 11:16:38-05:00\n", "\n" @@ -166,6 +267,14 @@ "source": [ "print(mynwbfile)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e75c3795-96d6-4ed1-889e-3da58d9e8533", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index eca6d91..849318b 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -57,7 +57,10 @@ def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'): print(f'\n---- Insert {len(input_sessions)} entry(s) into session.Session ----') session.Session.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) session.SessionDirectory.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) - session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + + ## Type Error: 'NoneType' object is not iterable + # session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + if __name__ == '__main__': ingest_lab() diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index a068352..389d941 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -13,10 +13,11 @@ db_prefix = dj.config['custom'].get('database.prefix', '') -# Activate "lab", "subject", "session" schema ---------------------------------- +# Activate "lab", "subject", "session" schema ------------- lab.activate(db_prefix + 'lab') subject.activate(db_prefix + 'subject', linking_module=__name__) +Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) From cb59750578de20f37ede2d0ea1ec942816643090 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 14 Dec 2021 16:50:57 -0600 Subject: [PATCH 07/46] update readme/diagrams, add activate(genotyping) --- README.md | 83 ++- images/DataJoint_Labbook.png | Bin 423684 -> 84080 bytes images/genotyping_diagram2.svg | 151 +++++ images/lab_diagram_PendingUpdate.svg | 222 +++++++ images/session_diagram.svg | 77 +++ images/subject_diagram2.svg | 222 +++++++ notebooks/1_Explore_Workflow.ipynb | 915 ++++++++++++++------------- requirements.txt | 2 +- workflow_session/pipeline.py | 6 +- 9 files changed, 1209 insertions(+), 469 deletions(-) create mode 100644 images/genotyping_diagram2.svg create mode 100644 images/lab_diagram_PendingUpdate.svg create mode 100644 images/session_diagram.svg create mode 100644 images/subject_diagram2.svg diff --git a/README.md b/README.md index 67241e8..0bd03b5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This directory provides an example workflow to save the information related to l + [element-session](https://github.com/datajoint/element-session) This repository provides demonstrations for: -Set up a workflow using different elements (see [workflow_animal/pipeline.py](workflow_animal/pipeline.py)) +Set up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) ## Workflow architecture The lab and animal management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together to a functional workflow. @@ -20,14 +20,16 @@ The lab and animal management workflow presented here uses components from two D element-animal contains two modules, `subject` and `genotyping`. `subject` contains basic information of subjects. -![subject](images/subject_diagram.svg) - +![subject](images/subject_diagram2.svg) `genotyping` is designed for labs that handle animal care and genotyping themselves, which is optional. -![genotyping](images/genotyping_diagram.svg) +![genotyping](images/genotyping_diagram2.svg) -`session` is designed to handle metadata related to data collection, including collection datetime, file paths, and notes. +### element-session +`session` is designed to handle metadata related to data collection, including collection datetime, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. +![session](images/session_diagram2.png) +### This workflow This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements more intact workflows, refer to: + [workflow-array-ephys](https://github.com/datajoint/workflow-array-ephys) @@ -44,36 +46,54 @@ This workflow serves as an example of the upstream part of a typical data workfl ``` + Clone the repository ``` - git clone https://github.com/datajoint/workflow-animal + git clone https://github.com/datajoint/workflow-session ``` -+ Change directory to `workflow-animal` ++ Change directory to `workflow-session` ``` - cd workflow-animal + cd workflow-session ``` ### Step 2 - Setup a virtual environment -It is highly recommended (though not strictly required) to create a virtual environment to run the pipeline. +It is highly recommended (though not strictly required) to create a virtual environment to run the pipeline. This can be done with either `virtualenv` or `conda` -+ You can install with `virtualenv` or `conda`. Below are the commands for `virtualenv`. ++ For `virtualenv`: -+ If `virtualenv` not yet installed, run `pip install --user virtualenv` + + If not yet installed, run `pip install --user virtualenv` -+ To create a new virtual environment named `venv`: - ``` - virtualenv venv - ``` - -+ To activated the virtual environment: - + On Windows: + + To create a new virtual environment named `venv`: ``` - .\venv\Scripts\activate + virtualenv venv ``` - + On Linux/macOS: + + To activated the virtual environment: + + On Windows: + ``` + .\venv\Scripts\activate + ``` + + + On Linux/macOS: + ``` + source venv/bin/activate + ``` ++ For `conda`: + + If not yet installed, run `pip install --user conda` + + + To create a new virtual environment named `venv`: ``` - source venv/bin/activate + conda create --name venv python=3.8 ``` + + To activated the virtual environment: + + On Windows: + ``` + activate venv + ``` + + + On Linux/macOS: + ``` + source activate venv + ``` + ### Step 3 - Install this repository From the root of the cloned repository directory: @@ -89,7 +109,7 @@ If no such modification required, using `pip install .` is sufficient. ### Step 4 - Jupyter Notebook + Register an IPython kernel with Jupyter ``` - ipython kernel install --name=workflow-animal + ipython kernel install --name=workflow-session ``` ### Step 5 - Configure the `dj_local_conf.json` @@ -126,7 +146,7 @@ create a new file `dj_local_conf.json` with the following template: + Connect to database and import tables ``` - from workflow_animal.pipeline import * + from workflow_session.pipeline import * ``` This will create all tables defined in the elements in the database server. @@ -134,25 +154,26 @@ create a new file `dj_local_conf.json` with the following template: ``` lab.Lab() subject.Subject() + session.Session() genotyping.GenotypingTest() ``` + If required to drop all schemas, the following is the dependency order. ``` - from workflow_animal.pipeline import * + from workflow_session.pipeline import * genotyping.schema.drop() + session.schema.drop() subject.schema.drop() lab.schema.drop() ``` -+ For a more in-depth exploration of the tables created, please refer to the example [notebook](notebooks/explore_workflow.ipynb). - ++ For a more in-depth exploration of the tables created, please refer to the example [notebooks](notebooks/1_Explore_Workflow.ipynb). ## Insert into Manual and Lookup tables with Graphical User Interface DataJoint Labbook -DataJoint Neuro also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. +DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](images/DataJoint_Labbook.png) @@ -160,7 +181,7 @@ Please refer to the [DataJoint Labbook page](https://github.com/datajoint/datajo ## Development mode installation -This method allows you to modify the source code for `workflow-calcium-imaging`, `element-calcium-imaging`, `element-animal`, and `element-lab`. +This method allows you to modify the source code for `workflow-calcium-imaging`, `element-calcium-imaging`, `element-session`, `element-animal`, and `element-lab`. + Launch a new terminal and change directory to where you want to clone the repositories ``` @@ -170,11 +191,13 @@ This method allows you to modify the source code for `workflow-calcium-imaging`, ``` git clone https://github.com/datajoint/element-lab git clone https://github.com/datajoint/element-animal - git clone https://github.com/datajoint/workflow-animal + git clone https://github.com/datajoint/element-session + git clone https://github.com/datajoint/workflow-session ``` + Install each package with the `-e` option ``` - pip install -e ./workflow-animal + pip install -e ./workflow-session + pip install -e ./element-session pip install -e ./element-lab pip install -e ./element-animal ``` diff --git a/images/DataJoint_Labbook.png b/images/DataJoint_Labbook.png index 0d96d1d6ffe7c13f95a873589823f57d5c7228b9..2d8b83a1667c6ef112a13c8d070770053baea11d 100644 GIT binary patch literal 84080 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 -fft_qjC983obxcG!cl_e=v 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 literal 423684 zcmbTe1ymf(+AWN0f=iGD5AGJ+1}Es?9xMb)7#s#mfWh4@!JPzm3GTri5;W-GF1K^! z`_B6RwcdN~JF9zj)7@2FUGmhk_p^&obyfMNSmam;2nbIV6+oH@2v}wa2x#;eXuy?A z4$V6R1VnjT85wm&85wGIXD4f0dn*J4rdSIzGXh06rhapCGqe5?R%R?`cTE^9LetD| zuw$6IyQ7bK^mAN_k(I_vG8L{Q&UarBpUhYF#OteMdB>vnsi^xeLH zQzDOO#9Z7nAbEp}SI9t0l*q-!Re&%*xXFM@?S>x1u8HD86eEV-ts3%G(0@)5?*jj8 zS`4)f^(|TqdrU?w2H(eH)R^-4PxM()-N6gkNTM7S;^J6eA_yn=g=2QttC?YKk79m0$IJC~<^J)p8Vwow z7K9euPWbrv*n0K&*d89RwQ6nlN*xoS@TsZo2U*Q5pk~9Y^c1aMzeZpI&M^>B5Xlix zfipy47e}P{pL01xCIsYvTt`Ac2(d*#`PVb5!12#72H5|k`R5TiE*JqF_=^D8Jw7A- z`)Mq*&&dBiN23SsAxLS-C@KO+EemHWD@Vv%Czr&!0$tz&=6eNw2m%5L!=D{dQIq}% z$bZfjtmmTlT1D8x$$`V%(&>#Ahlj)aKlvbtdI$rj4puJa)E*A@ju2rFG1`ATAq<@V zxy(sR{f|dn?8IpGUaM2fI5}HU3vh68aM6lmQBzZkI$K%`Yl7tdl^pm>jP|XI%X?u? zPIq^A4tHJ-CubW@ZXqEdPA(o!9v*h!33iC5ql>u*yCa0|pPBqSACMKq!rAt{i>;F* z^`Csr-#EFth|$vidC~v;{#i~d58MBJlOyC`p9Op%=bt;A+#Fn-|C2Y6RP@hPVRc&% zD|>yAtpiYJz&pgb`2~4I|B>MTck6#&`5#I3{x>O)0KdS0CjF0F|KFtA5G!XHCkNoA zF5>^&VE;<|pEv)NP?Yn}xBo|7{IjC}aTTa(aV$~J|7kUGtUe?EDWD-KZ9!^a;0ScH zKR@I@U5M$QN8lX!q3095BsBtpB!VJH3haTnpNX15tW6PO+7x?uct`|_Q3tDQN~ud~ zNK1kc5fk~;iBRy>n_|Iv&3Vnu6iK5&*Q1a7f?Z2d%ZaEDeXEC=LSkdXt^T+Bw{pB- zd=dg`#J|0@V}f%1cLZ78=>L8mulM0;YP$6TD~5@qha(I(XU5OxmIH){YA-!R88)Z-N+X1Fw8& z|Jf*62uPVC?laB9nIiRk4hv_Pb04M_);>#pwCs%_I~f&$D^y#LExV*&=HGd^R2 z%Ao(Y`m#Kc#>)H~EoV(SlUozUGG$CS5=?Vs!>uc~xG_J4OQyS(Ta7OHZA|Z04s;;4 z2isNHxE}RJQdmvoDI%JRcemVKpQ4K%>OVUg5nS!x97;xRo2qPo5LUtfZEc?oakjIa zZjGGY3j5ydFbY9$Y7SRx?Q|8>_=;C8#y`xI=~p>#Dv52C*RRCIF({+)iL8XHP$_X=h%(JrS%qU6-l|<5&6@fRj7dDQHM_%;PPfPQY-rT7L?>E2A)fHoOInq; zPo6#{#;9ae%bJ>=t}v9u zzS+2)bt;Nz_}>q)9MJ$9)s7|ao3i2gDjAN|mYs{O?g)Gt*)BHvo@#5i#Ze^!j0DN9 zUZHuLM8+!5m(}l>vJp6Hfq#aGz&kE z*ZUZtH)n{E(O8*+_H)$vN{LluO!Gbn{hGhePJ?^>*3i}#bmzFsNhsF*D1A1TRFWm*VnS)B=I@2VTy~YviLo>D8Kb zBDMF))keYm!!G65dm~Xbm#Z}oSKB%H-%Z6_pKk9dt7g3VMnaDti&wQVRjgHxQ5>_b zQ>KUS^65L!F3P~D-dL!$!|~{%Go#(z)HQ|XZ+C5*WeU62s8A3P7bY?n zQC0Gu_$GbgzV)W&0a~-Y)p;`vn##ELN)M5cR5u5NIbUO|)uF+5FwWZ-^_*3KVdTUT z-`^u_>Xa#FEhbphqBmkjR?-sGt;Aanoh*25)eXbfX_i2rtu&oo+idu-?&6+@F26A~ zk}kM+YKA(@Z#SdR=)8GG>9Gn7a>K!7PA7+*&X4E@quu;D)C@wPgJ&#N?jT~Ulr}|^ zHXr>e>yeTkLm$Bd0S!Bs5wi@s-xofQpjZu^S^c!ItZnH7L#yY zW(zSYyB#FPC(C??^0I7<NuzY4i#6Lr`bWi|_5cE(DHesO<< zkUiDz_sDLM!cV}W-@Gts za#55IkgMt6pRaDVC-yUu9u;-lPoWmxZ-d_6soJ#y*0fl=G>KB&M{f-Km;j&b@p_x; zdM%vO#rP}I7GUh{WsNg=tiq;Bb;qpWMUr0U`qkDub>q46L(TBEy*g8_L?SMz*4|=$ zrR0p<&U|&D&-G@qBl<5-(A3amVHa(6rD46QJg@6wYT|$IfoLRkXfXp5E<`>&nF8l| zr1}x2Hc{!CKCj|*;5Dtw%8@Dw%*7`9X0A%s?s|W6tgE@D@0f^7EGM)kSTZX}VG%^H za%YN>5@WX36TYpbB<}an62+7(>Ur|slzD>2wAD*nGf&~ytdUxpKiThdA(@UVz$_}H z@!1+pjE2Z}bZ6{L7LpE|ws?HVx#<{9?aXAS=NB2!)zMCEt)&8$oU72^BgIhS2Z1wD(Ue#0rQ+xGb2 zy|dI^wY0R4%BpK?(kt0Xv7w&BB9oDclw)%+Qdh6fc#N*kTD8Re!(rK*xRI(ja^m!i7qM zjemN`)JcI#YgDY6pJz3cq}lA|z}_bGjIvdo$*6I)xk$9rc(s0k{3Rn^%T)_B=MDl+b;+AP55b z)P7VzjSD{)f|Is`m%{1y+2n6z zb9UVdqm`z;go*b1i}b^f+xeRLN*wnzF0@TF7dSglX9JNw5R56@UL1%G>MhneROg9e z_+3e3?G5r7PEFzrF_r?)CD^9DzcKS(BocX3O+>JChkd99*vYCHvmr>~MRu|GE!! zS%IO2P7UFiABzWmzSt?u9u$GM$bS?~`r^O5d3Ht%{nUWj|iP(oT8H)mnJfn;xv?O<+=u<1iL>TE2IL zFq%Ew!rO( zfXIV>dcar2gq$~E70L-rv0b$fXOn6tyQP&)5wa*6E$wcHOU!^9PK?p%u}Z{l0A|sx zC|ecb_;^>=(RO6gZK6|Ya^OmYVk~;ggpBs&D~fsL$2o=n+PT5Y#}G|K+A8Vlvz-35 z9{0-8KGahJ9K||7C(58)lHj~?Av6DBCzwbfQ>CrgKv;BA_G(TNMTPB9%}>FHPChy& zD4gVK$!SM3-|BnAfKGeWrt(g|wN6kvGDz&!ecCITL?N;L$u1{g!eZhJeXwf?dsQc@ zz6^nU!B?zi((f`VH0zreal0o=v`ZB0IZTE6<1aQ1eeYtGC`3$4w7vCxDfp&V=wsOI zTJT?n)M$UF-`c%@t;Vh)>;h)=%B(O|3_gpZwL2w&vkJZY260b@V5I7;E3)A;n9;IO z(YHK^2P(Int_AB8#9WYur82=$HBa^S1 zDXimVwmr)Iru&EUOOROgaE8#N)CSMqK_%V*bpom;ty0Gm`1$-LB>@2~06m&WEDp{u z)`2FsqnU~50DLt7cep?~xh??!!M@z=UeCGyKH@e>z`>x=<2Wa-h}YJ9h)8lI>TRih zb%CVZDVUl=df0BM;hQpPe*-Y6r_blq9`3GDXWwxG#r2j3$!fi?x#W%hCf{$j>U&!b zxWRp8GnFuwFuvWGIADD5{s<;iaEQ4(S~=CBUbsJ>H8qHFJw*R9r_%hi6DO{S8DIFa zM`(q@gG#7cmLg9k&n*}hPIS_M8$blY@3|!;VD1*zIyn_Ur+w|X(xUHvto!CuQ5vs} z>Ocy2Zr9W=VygY#kD@Pq1Q)TJ%pcb-AE9G1M{t%Z*f zlA!s1wyQGuYJ0oMoADf3gNKOW^H+lZwXY-DP3TdAszRu{0K(kZzs7bpmd(P>%Tpf@ zf6*JtIixB#HB1GO zH|Aclo3hgKhmYrJqy2LF5!N{I0TUh@z8KM-7mMCDTpvP7uLAK^vl#ZmO39gAlkNab zNnrQ@Fma|2z>6(WzG%loDuhp@u&YROa=%EfNL%uOh(2Z10nYk!;vYnf)W|eQl=LFr z->}18rwdFMmvXE1gb}0e1aM2_d`n$>kz;VUP^;{`(f_4T>K4Ge=iGT3T4+IB7@;_* z%zntPZ7Io+thr_jIVGVVsdd|YJkfE8S->&7)1`YgM*w=7rX~F}4|L<5gwbadW&WYw z7{w$D_eEvtT|~(Od8S>YJlqm*~ix4`S6I>L&Q8x^E!vR zguzk?SbrCQP$$O{$ndMxvc)Iz zl}VRp@3xdXx*w>Eb0k+l-72EDusjTnCz{XmKt!OMkKLgdffsK`xkm2SQu{<3UhWgW zc}Y8*W_9*^{N%71o>q86Ts%m_x0dxq5(%`?AsHsdF1Vos5|ep zm>S%#RJtH*Mn|dUN^MuCPqQNd$7wnnJp~#5{FJv2XbXcj{_T!ij4`~16eRwY5GtRe zfO%Q@(eB$`HkfLtcPNquiCrRxv4Q-X9^SyWm0OTVSQ#viOdmDd6V?!Vc5-!!Azf2> zORn$ztL3jf6?~bWyP0+bVg@dXM2zO$Dpy2fRflsgg?x5Wz{47<6E9WoiAy>1l{K1rD_|ZbC8t#0xd-N;pl+FTb_;g`mgrdRL(;&r{Q)J;3l@-7>mP$jlG}f)#*pjyGzZ)3g9`M>IeANgHMO2O zOcljOJQtZUbZ=kWM|z1ylg6JVA!>0Es1WZV@kg+NN$@g@J?~6_t|#X?DxBf;`V-8N)T_(u6Ex&KF54sk<(AX`6r0pje>0u0h* zY|ODI?yp7tlKL^SEDN8DpS&2Tz=U^W=TWe(_m{kPKp*JmQGPCu=N?<)7fAV1o1Z+K zwhLm*;IwOWA{Ve5$NBoOuco@F)qa)Xqvgs<&&l$hsbaB;Z0?11W3vL|(ec@6)$P7` z>JN{iJjDc-iP`mkI5c?UC|S&ighvF$H5=cgsro+}-6*O-Zilbil zYtXSZeuKM9C}l3VE;~zFb;&su!y@;LP*flYpyA-i?>V)vpA~8bdE*IlTfp`vkK2YA^tF{v7H?X&Wmt+IMKUBq; z?9?kitc&QHsS5&)S@hy#0H1li!vJT|deO>?UW9`un111N&6XQ7S`B`}+=@9^%0F#$ z-Al?-%`{MU`fVr2G96y(6f>h$pi-R6ON&HL&y&&23X=|X`N>D989cDG-5)D*h8k(N(ppj>{o9Eh8NB2&aO z-pu98U^oK^;)91%%IQzy35o>`>d2R`)4){etJD{7+5%Crcc*@2twdYI@;hD`G?vJC%Dq!(5B^81{Q@Wa7G8hN+4Uq$Nb%uZb0R16KSx} ztTwSKB;xE4YlIxnW1BB?d|TJ=^v26hj&a8ioih$7!n7q>%MJ(E6SpRYa_#c?aL+O3!*%NXMb~3TsUxAi6E(n-!1onuW9*WV;`^KUW4Iqc<645kSu{2WH;}gP(sGCHtiDdH$gHblbVHul&v3IJ~<$s(n_Umha!xUQDdJh z)?)D37kaUfHYJqmE#KoF(&r0HnM|oab}Q+1LO7HJY8SvaZ$kvO32V~FzQmnmq@Oo7 z->%o?rYjK#Or=rXo)i`l?aw@8Xu9F;wxANh=EBY^f_}&!1A;~8@?lnS>IF{*8gs)4Z8S+LL7U< z20c6W7drrfYPN0x{P|rZbI2o+XIb63wiZp?LhrAs{VY)=L)AkrlbhNkjh$J+NU73b z3dYKeI*zq3$3IexfZtkDc-?Pm;JAJ>k(Ab}n^`{fYNahhrg1!`1T+x}O1#YF(rbh&X>ry;r!J+cdDdpD0Ni{Y^{Xjj>4J(4@|q}e6%2_hj+N!Xj6 zSyFm|Z36t+!kj>Z*>c)3AsM$FB9_a${*E|4VW6(NH573 z;-W#8hdAc>1cx86}UUV?0Hz0{G7)Urkn=ZzNZxP0{@}VOB4N>6*MUAHDa$^N}QG` zh|AT$XIge8F)?_VVGk><-au64`QD1&^C?8l06K_u_(A>#;=`=3U5M1TO|2OSaLVKREJ__U;^}3B z)VN2!l>kLCoGoEGhVVj3oRk=oub)gP;xBvV&ky)>UhfKhg!hyc6S;ySJiPt8qo1#? z>xkd(*-1T$SGvVcq=xVLrmH%}OUy*YCp))MpJUCskcs$Cg7sZ3Giz98Rj>+1N*8fW zg;^xn?zO}6Iq6-oW@CQs_3n7a?)q3)bNgOO^)-LFwA|@Tta`X2;qSvyjAyD%=-1O@ z*R8x zWbRGax~Vln9-}C+g^K4bRpM0ZYdzXRiBywKshX9g9?o0fn|G1nC+9=irDFM2PEu5y zbM;{B{S>U#_G=&`zG14<|0_ zqF%l~_%^b8EyT2wt;|}vUHP(dvy{cb#wB*y=5pXuugvm_cZ3ZMp!x_XQt?MBXc8gf zq7us~0Ed!erI-gsh!mgTb88^=83bIx)+#CmnI~rT)b>P&khcEB!(;pdnIEuHfG}CuCog{pzzD<32Y0^~o6+}?-i+rk^J)?&CQ}2W zfS}7Pak(u3_jWTkDcB#^klY{$a7^FOWI~cHNv=NpaQYh3&tdjL)I%No?!9|gnXp=RWQ@=8KAIMoPYWGW0uND3b|^t`)ko_B zz6zh607y!4pDT3<3BWTS-vj7+!}pYQa!=YXI#vIgKF|p7_&H5ywOiMoXK<)A(dfMB zFId0-^W9G4Px)f4->$aC576Y}`=qByer}xL32R;E^!!hbtFCU9k_%nek-tOeELbo>-A(HAJkGkJ36pws9VE!tz7PukdiWQt~MP!r5$J` zT5P)Ul;N3b%)ZjZr>o)#**#x8a=-G^)(4^9^Jc-k(|r@?y%h%_&hrCet(qovdHZ$0 z*#vcXbv7z7G`(<0-fA_zKCEr$D|$waR@{pU@3;59alEnu$!^%-Q7;U0)9h-3PY)E@N(S50&FQLK!ULo>V=4mtyBG@aBko zQgWYJ;x|fev68%9oe+oKi#(r0-)B@@H8eiNcm|d3yOCW1EGL@`qUokT34!r6&}#n^;d zM9T@kph2R}d7T6L7o+D~byk1>Hub0ylW)k^JN z1YGUcTNE0$v945pho}|kYYcfE5oEkrv|(w$coBC5VC++T8lqcSOCwl78y2lXTC6io zGw>l3vv|=5ZX}%;DF9&*4-gu^ukS+#!}~L>iL_x+7qDbf4x^*Jn{RrZfv5~#XTN7$ z=TTK!1c#Z@b#Bw6~u5|t+N^IV~W29 zb%e4%!~ML5nk>?5!03$}KL}nR650`~q^mXQI8@TTUzioJl5~Owmyxk+)s2E_Z4`HO z&b3&b87`*m%q0|4se_&rU*;y^jx^F>2aEfB&XvFLtB&^N?~ch-3cj<}@Dp;s4~#On zesi%sLSvBdiA$eNhUWU4HE!vO=zCH`LVVtIRr_4ofz{n$R449R(--RK9&D*7R& zF6}7|Q`GOPjJ(aeZwZX6Q6VtUr} zv1V>umoaEB4Zd|&8%W6oTMj82TK{ehK-`l4#><&lTy$3U5R@cF^PWTEtkS36!ylw8 zQJFsHkwUK)GrZu&rSh@S9a-l|yuQfxt_|8ON-@!9M^BeQLQsUzXC10#M@O8v(t2;w zVeuQbJAL_I-KR&|wPW-jmOx&OnKl0D%A2TBFZ)F%EV^o%mEZUiHNE}o7Ihaqp5;}eb1nB_Ch9VX6 zpoCYmKuNxo!9+8*TS`xp{KBdAJZZXuwz07WUGoddOR;1nsTciR$!+)_pl-fOK7UZF zbpBE8E3^9}lCL^zd0ASrWYd$(zb4ezQY*A<;bI(L@_6hxf%wMB+?Z|`pzN|6$wg5y zU2d&|FVqfQU9Cr#->znaeCT-IfzWKzA5 z;J*W)T%PwoB}*$M;6mHS4;iF@P_6Py9(a9gBwaj&=?nN$H?a821fYq9mw(I*BH(Iu zbzLmzmJg3sx<9CkZk>1_E10lfO#kk>RADS)M+F23kA#v7FYCfM>_#S=-JP;eJ@)*^ zq)*AZZSo3py1kawFn{!(?M#Zf@`8y>?8KMqk4PB!3{RPh7aIUMhDYIs$Nz=-Q~BJJ zolTbL3!T*SvxlgQ=zEzZzL0pkN#)r0*l?!1+_u$^R%$#D)pVHn<@{;l%3?`9)zvQK zcEw>mzq2+1%|e{-{k~Cf1zGuQO0{>ajMnt(?(dsO#ya7t^^LLeYL~^Y{1%i8b=fQB zdm_U(gI`ueum6E_M?=31=*Z6UHSf;uEZXcn#0!kQFS>N8y7(=(+srHbHd@O z>A3ecUYuKp%JU48Kw~Gj!@Vx>uromg`DyTeRgiM^O>O#c>C@uvB->fPwsDJ$N?!GX z&Lt6sxZPW0yYA-R&2*ZXtdWn}A+xqNIF>oWCtrVhk(vj}{0+ zHuK`qpyLkU3E0egsSx`(7>dC?I6rBn)%h5G3{X`P50}@TDh}#u>JnQ|%aik3CB+@QAX#ti2UpQ{rjn?7yVcClb* zlB}U&R^EDjdJT8rx!qO(bOd(Ms*YZ2zU%I-{Mi73rX-|#RLB3y>3kGEDJVyh*KG;IL}DTZ)w83bUZ@VX6;p9+O1 zITX5cv*1=Z7`1rLn?F}$%$-r2I2~U2CGs^$NZz}LWY$k~BTs+x=-uH6$J?}F!-wle z8Pn751Opqmftk+EtYm7MC~~8_;q+6NxuIM2|lZ$KMEkfRgtQAf>1!Mi}zrfbw;!?53w58ov0XL40QRqGRO(2 zLJ-b_oB_0b|IXI}%Q+r2<#AuB6_fqygLGkocJ{A0X04ZiG%Y?)A@)1_lRR$@la>Y} zKrdDpFXy%T36szOO2>nG4M-a3l#Zq#bhS=^(xBWGNlen<93WUj!vHuuMrj`k+e5a= ztCUNF10s{%npMu0qrgRW;}#E%m?*MK3|YlM9;X%2dMI)+$m>lWp*)#{Z_62c)imK5 z3!o!R2Eevg2G%7NbA(3Bs0T1P6L6Y{M&ASU2#fpS()do4so%XTfOWO2D~UGjX3ECL zC?>*L&8HpSxu}%oiDf#I^_lu!=b!D*6NWT-C`m>2QVKcue~}3}Yf*S^0*KtcN~x~G zmzx<`(#p)>OaN5|=#fhFTK8@1sob%g?Ue@dZ(74FmRR=}vrH(?tu*1R2;_Psq2tiKN| zt|_F})v!Pg~ z;@o!D)lg^NF4Qpljw=Fw8yz+Y8y-XV4Q~SUubID`R{>*>ha5^Q@ovQ834&N)T#x!+ zic%&thWa}XO7it~X**xV&Qt3SoUUEZF~kHM-ppq6GBs+%2#fp)wAf|B%lTtCM11$J zc&g{0m5z$e`FbTQoiD#;*<|80)!8%CK~D^%{8+i%$l)r_AHUCB1xpRSn<&Ri^u2>J z$9{wPJl^Qekgwo074aS0X{TKU)V3$y*`9dpl`O_+w)Rp*(u0j3GjJJ;YkrLz_u6aC zme9Uay0*8 z_fVF4kp-oG>O+TnE4k~L=Wm1MZ0a6z03aYhYDY*NidNHOR}X)tCE#!>a{o#*@iyI; z@?Ln<*ukjb1M6ooSvMOeeD>w?gYtH=={S6@kIJE=L>q@$4M(&0v-n)!EI?LTR7*F? zidK$Vcyx*OfWV;KSINt{aSj&YKVPiHXjcwsg&NC<6=cCc$Aq#9{b&+kKqt2u&G^tr znHSL8$_2+ZxeuNA%@dm*2FS6V{n3L#Sh9COeD{!^Emy zOb{%Fi>>7VW5G@5FiQxs68x0H;5H4=D(aV;iph3>=x9CDA^;KT-i68M?6})9UsP-o z7tiRqTyYjeLK#sKQ|A81yHmAX`}wL~c`B$S6Xy83Xd8LL*}H>Apldlc(@E!h-Yapq zG-fMs2YgdPnHei@t|1;JT-5gn^WWJcKN#1+XP#Mx)cHO7B5QDddjtgO6iQWYD@qNc z2SBaJ0TSuRQe9&$*;jr7Yx*u6S5J6_06u9V++_lU1vMO=(AS^WUb~^Q6N|mtd-;pa{av_%I}gZ zLo1igV4b3iqPbNEgxKRFwk;wj$VbP}6rKYM70suXUrdIyPeRhh@oelL<+C{#+9^kT z6UQEI45H_~5B*K=WvD)pE6_bGz$+vsxftglj_p~er|nJY+QIF^j|)O$>>f*Fp$`P8P6BiQlC zevn+W{%J&~vM!kfr~YO#Y$w`(-eXWY_k5V4cgRmg@Lg1zmCE9;RjR4Sp2*9W;3mU% zqaYqMl%Z$?0e9Pnvh~8I5h}#$_ur73Dhr7=n%&?vTjV06-c7TFjB0w&WyOe#$f##R zOS^#Dq5a}zuEKF()4{3gbIRT2%(LA|naiGM+XVf-U(t`g&raF;39DIoG)r83YpYmX znZ0frk^P|dEqb@|R(0Jz%9Fr^D#YLO=FFDPb|TN$PXRyIu->tEFp>2SXHM~4 z#ElOyvlg{7hmCFrY1pGMA}(zZznxg#q^y#2zY&X9?od-3iweMN#PL$Q-Q2p~-?)Yp z)9v1X!3r|6Mb#C!aZ`O%e)e|&VHq5D^|lBjN)C|ixi*W{x`8lL8km}YhIv=y(2=vS z6n33Ai3oX~*9TvEKM`8-0rt(z6V4hU~m7ZacWJM!gb0G$YKU#I9_O`h56Xl z_QCMG-X|9!0tuaQz^HL{rO%%hfsd#k)2}qtD1tAH)|2#*~wUx zioVeP+;xK`T1&Viz08uRem=%OniiI&7n`->KC`)hJ`6X~`Zzo**g zt899Q+TCX_&1SM+tzK=wHw{`o`Pe?*Y*bqHCTSf$9^G>~wF!2Pc~Yg<+gDk3zG!W8 za}=OPOyl`gvD|F5FoM@KnLNqudiM>~_UNT!buB(PAx>w`RlJ3e}3z0VwFz(XXoz-Q;yud)=M{Rl=gpKSCX zd>}Eb%MggGG zMWF*)zMHir-EY{l#vm>ten_jL$w zr5&1Rdwr0}KXnsKtms-L#G9U2*7JT2`hd}qH3+PeSQ<6AiL@clrn>OVjHapO)UWE^ zU03bsKDCJgG$L3nTPqp4XK7GqD=iii_FqpP@lpu1W+M{hw&wN#uzMj`K4A+GlF!CW z+uxq^(lM9t&Ff^|f8Ob?^E!l)aB;3a89$|6_$hOq4GB>3I$dFB5MFV8izU~w^ua6I zr1ZQ-6$g^axPmf#%R;`H-nGhaaCe>hLGj=kK&CZcZKKY&rxPrj$M*K>MCNb|_>IDI ztV00su`4#V+T2~{>=D$Dfe6Vbct3JtikSj{Mp3^x_@YHxbE0mviMaV))4NaH?qhFG zo&N-K}8M8dQX2SqRGNWL?PJ?aZ>cbYMNwy;dx*Cr@Q3( zh4h~`hsd$j0uC#0TkhTwD~Dx>TE)%Vw=M)p!a@(}RnCglzh&@8Ga?ekr+I%QdL=oK z8G=JqHRCM)H9V0{zA=mwc4Q@2&bO&xgLqncxLgL6x>@>W(R|9&`+$6VZZa$+3_WgC zrULuS@u>U&Nx0uiWuszm8)^uODMr6UZV@IGfy3~)`XZ_J-roar7I;U`Nr>~dJ5z!K zk??cO;Z-*$>1E zTVuJ*rT1<+p3{;}1jtCsa>HdoSScy>piqLDrI;op<`^xB$2$m}=gGzl<(4>&4Pa1b zfle2^$c#+P#f_O21Vkk%6bKzFKI-;=0_M2cJuKRyh6a7_o%8?4E836$E^(3^`kf*{2p;Bi?Qc z%M3Ecs);_mB**_y3-FXUnk9|yppI@a8JB^6rcuB&S8C+Skpb&B+|5GI76BQuKn%gM zuGHD$!~HDW>#LBjBsq=3aMC3-9Ll0> zPtnpX4|k-k(M*wZ2+URdcAq!dLA%0ep4@44eT)@Vg*2h;uU4f7FugWA%=-Ek#(8fr zq}Sq?uWW{*U~^#OTijFSMDnozo*j>p6}O^X%Vd$xB4={7dCQQ;0EC^A@`prQ^TB7zzH z9JGM9bHO*AM})dM&tI_(UA!PV_a^g&ilE={POZ=9inV*IO+&)gq1<=R0eY-7LZ#`-;w%d7!;~^$yln zKr9L*D^AFOp!hGo1GHSq*CmdG6-5=oPoP9_~;(Wwlh< zz+WtjnwO>Jp5+Ru>zCMnXmr_$EBwWAE>eFG>v6o+IAtNBBu$*?JfA;e+!NE;mG>_3 z9mngf`%dw1rrw=U84;yLfouhl^UdP9+oonm$PzYjHfiGFv}mn~Y57Ci6Uw7f*U^t1 z7)3S1Io*sxz*?-LsqP%jsPH%skgO!OeGbNcc{iuVj+5*_N>0vC*`LGFek=?IgTJ~$ zWECTGCa}Ym)Yz0fyzeMZEzp&G2^k_nmHf&i44t=znORaD13rqbd|e6*zRU56J{ipe>Afc^+K#< zbxg^cBr$NI^3v6EpD{-0_(5I6%Lh{kv%7!2xn-wd zQ+kr{cDMce&Z%-1Sjg%zNXGeS4Syh&Hr-yh@%2s1g7%;_^OwE@;bz?hl}Bk7OIFrp z)1BX3$)0O!*?aCzMV`V#$1z%Q%(h<4ac!Shx{|aOU5!T*<~Ozly_N6uqJ4c$92AU0 zsYKd_?FD*`2EDWcV|;k3)4_upCdM}CrQ&DiMSRarzZf+U(<)BaCim-sc1cQTQmFFb zaZ)@^@Hf|o=aqTjf}ELGrwD9d6cuQiu!hnf73NCe{NdPM>`-5R`I~|quU*&h5ANhKIo`i7)fX`k1HB3VL@VGtj3Z2oWBW6gFSFk8{2URF6O$#ei{RX{ z5KxWP*ke?>B{futyyG-n4>Ttq2A20c*7@+{mikt7d{fyXaK1luo%eALVY=1`vteR` z+W8zDojwDEEGbYUAXc)fCW=dzrOe7OB__XI5WfiMN;7i0MF2_O*S0ZRg8Zsi$3N$3 z-(TE#+P=D=gN|=jCB>Ocw|mqf+&#WK-tM?m_KlSxjlOAlNWhF&PCubixVDd4Nf--| za%;H!+4jJT(?!_#n%7f!5;8Ca>1yXMMZo`G_q9BXDNrSkQ3*o3TM*PR{zmzTE=oNco@ zX$Epjz%^bxpXX+fVh_&A;$xjwkH$V*JgmO8yos8>F{oT>E0FC{QJfeO>Dg0EG$LM7 zMdt>~@+W30c?A`v(wn?tupo&MR-$H)5s2! zK*3Pkxs3^)?Adp4x^u8%?!$Ha>b(YLQhI@H<5ll+y4UiqC+k0=*Q1YT1WT;2IwCHq zt9dpeF4-AMW6oC62R0VxujhD{+I|8*$Ix4xWD;IMm0F`OGZv#GW}9$954eK@acHu3 zh8DDY2q(Vrp;UW2nLW4t2B(Utm!bGvoXZUiLl@6uj0;EQo-J;K5)Rhh;%=-$1R3@q zm{U|Mx>Ho2acM0d7Eqnv;`2tu3DY%8+KYM|`Hxv)5iv2Lo{%Phi|=kD5kc+?xhNn7 z*la~<|Nr6at)rsw+J0|A3=EKxA0Q&a&;pXu(hM~)bfa`jcd39#4-7*i-5}keATe|| zC_SWf!@GH&^R9E&`*mBw7No94_lHL%VhU( zQa;_9H0zic*63gV5*^8Nz2R`xE=j4yA^Fo7OFHhrwS=j$c1E2es(b=tbs??7zt$O+ zF@LHit#B|puOsj#6`rKh24k{ETCc-IX*>7F*<`0y;nL(3zDIxZVC=`+Kf~k*1x#5) z+thu(iX5{JcpLT&kN3%1l3+MoLN%|fKY6DRVE5Pg)h_ltCsD2`fT%x}Pt*`*7}or| zuB)iam({k;Sa#C#pdhdJA(c$g$6`gqC^a@7gP}*@iNyW5kfUdjz0xp-*5jsCX52D} z-VkjW^HS_)?FUFFnIOJg)Qgmu?gUZoSk~XQME?!u!+#Evx%`nqNt44SGd�{V?Wz3qSGR+ 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
    xrxNT*I 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 diff --git a/images/genotyping_diagram2.svg b/images/genotyping_diagram2.svg new file mode 100644 index 0000000..6c59d23 --- /dev/null +++ b/images/genotyping_diagram2.svg @@ -0,0 +1,151 @@ + + + + + + + + + +SubjectCaging + + +SubjectCaging + + + + + +genotyping.Weaning + + +genotyping.Weaning + + + + + +GenotypeTest + + +GenotypeTest + + + + + +Cage + + +Cage + + + + + +Cage->SubjectCaging + + + + +BreedingPair.Mother + + +BreedingPair.Mother + + + + + +genotyping.SubjectLitter + + +genotyping.SubjectLitter + + + + + +BreedingPair.Father + + +BreedingPair.Father + + + + + +BreedingPair + + +BreedingPair + + + + + +BreedingPair->BreedingPair.Mother + + + + +BreedingPair->BreedingPair.Father + + + + +genotyping.Litter + + +genotyping.Litter + + + + + +BreedingPair->genotyping.Litter + + + + +genotyping.Litter->genotyping.Weaning + + + + +genotyping.Litter->genotyping.SubjectLitter + + + + +Sequence + + +Sequence + + + + + +Sequence->GenotypeTest + + + + +genotyping.AlleleSequence + + +genotyping.AlleleSequence + + + + + +Sequence->genotyping.AlleleSequence + + + + diff --git a/images/lab_diagram_PendingUpdate.svg b/images/lab_diagram_PendingUpdate.svg new file mode 100644 index 0000000..2805db4 --- /dev/null +++ b/images/lab_diagram_PendingUpdate.svg @@ -0,0 +1,222 @@ + + + + + + + + + +`neuro_lab`.`#skull_reference` + +`neuro_lab`.`#skull_reference` + + + +lab.Protocol + + +lab.Protocol + + + + + +lab.Equipment.EphysEquipment + + +lab.Equipment.EphysEquipment + + + + + +lab.ProtocolType + + +lab.ProtocolType + + + + + +lab.ProtocolType->lab.Protocol + + + + +lab.Project.Sourcecode + + +lab.Project.Sourcecode + + + + + +lab.Project + + +lab.Project + + + + + +lab.Project->lab.Project.Sourcecode + + + + +lab.ProjectUser + + +lab.ProjectUser + + + + + +lab.Project->lab.ProjectUser + + + + +lab.Project.Publication + + +lab.Project.Publication + + + + + +lab.Project->lab.Project.Publication + + + + +lab.Project.Keywords + + +lab.Project.Keywords + + + + + +lab.Project->lab.Project.Keywords + + + + +lab.User + + +lab.User + + + + + +lab.User->lab.ProjectUser + + + + +lab.LabMembership + + +lab.LabMembership + + + + + +lab.User->lab.LabMembership + + + + +lab.Lab + + +lab.Lab + + + + + +lab.Lab->lab.LabMembership + + + + +lab.Location + + +lab.Location + + + + + +lab.Lab->lab.Location + + + + +lab.Equipment + + +lab.Equipment + + + + + +lab.Equipment->lab.Equipment.EphysEquipment + + + + +lab.Equipment.CaImgEquipment + + +lab.Equipment.CaImgEquipment + + + + + +lab.Equipment->lab.Equipment.CaImgEquipment + + + + +lab.Source + + +lab.Source + + + + + +lab.UserRole + + +lab.UserRole + + + + + +lab.UserRole->lab.LabMembership + + + + diff --git a/images/session_diagram.svg b/images/session_diagram.svg new file mode 100644 index 0000000..a11a66e --- /dev/null +++ b/images/session_diagram.svg @@ -0,0 +1,77 @@ + + + + + + + + + +session.SessionNote + + +session.SessionNote + + + + + +session.SessionDirectory + + +session.SessionDirectory + + + + + +session.Session + + +session.Session + + + + + +session.Session->session.SessionNote + + + + +session.Session->session.SessionDirectory + + + + +session.ProjectSession + + +session.ProjectSession + + + + + +session.Session->session.ProjectSession + + + + +session.SessionExperimenter + + +session.SessionExperimenter + + + + + +session.Session->session.SessionExperimenter + + + + diff --git a/images/subject_diagram2.svg b/images/subject_diagram2.svg new file mode 100644 index 0000000..9864ba7 --- /dev/null +++ b/images/subject_diagram2.svg @@ -0,0 +1,222 @@ + + + + + + + + + +subject.Subject.Source + + +subject.Subject.Source + + + + + +subject.SubjectDeath + + +subject.SubjectDeath + + + + + +subject.Allele.Source + + +subject.Allele.Source + + + + + +subject.Subject.Lab + + +subject.Subject.Lab + + + + + +subject.Line.Allele + + +subject.Line.Allele + + + + + +subject.Subject.User + + +subject.Subject.User + + + + + +subject.Zygosity + + +subject.Zygosity + + + + + +subject.Subject.Strain + + +subject.Subject.Strain + + + + + +subject.SubjectCullMethod + + +subject.SubjectCullMethod + + + + + +subject.Subject.Line + + +subject.Subject.Line + + + + + +subject.Subject.Protocol + + +subject.Subject.Protocol + + + + + +subject.Line + + +subject.Line + + + + + +subject.Line->subject.Line.Allele + + + + +subject.Line->subject.Subject.Line + + + + +subject.Allele + + +subject.Allele + + + + + +subject.Allele->subject.Allele.Source + + + + +subject.Allele->subject.Line.Allele + + + + +subject.Allele->subject.Zygosity + + + + +subject.Strain + + +subject.Strain + + + + + +subject.Strain->subject.Subject.Strain + + + + +subject.Subject + + +subject.Subject + + + + + +subject.Subject->subject.Subject.Source + + + + +subject.Subject->subject.SubjectDeath + + + + +subject.Subject->subject.Subject.Lab + + + + +subject.Subject->subject.Subject.User + + + + +subject.Subject->subject.Zygosity + + + + +subject.Subject->subject.Subject.Strain + + + + +subject.Subject->subject.SubjectCullMethod + + + + +subject.Subject->subject.Subject.Line + + + + +subject.Subject->subject.Subject.Protocol + + + + diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index 050cb9b..04f11db 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -2,22 +2,25 @@ "cells": [ { "cell_type": "markdown", + "id": "d26010d6-acbc-4c90-8b62-a2448c50452d", "metadata": {}, "source": [ - "# DataJoint U24 Workflow Session" + "# DataJoint U24 - Workflow Session" ] }, { "cell_type": "markdown", + "id": "c5ffe5d2-5b2a-45c3-8d8f-8c20efa8c5eb", "metadata": {}, "source": [ - "This notebook will describe the steps to explore the lab and animal management tables created by the elements. \n", + "This notebook will describe the steps to explore the lab and animal management tables created by the elements.\n", "Prior to using this notebook, please refer to the README for the installation instructions." ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, + "id": "4351c4bb-9763-4d4d-8558-37662adc930e", "metadata": {}, "outputs": [ { @@ -33,7 +36,7 @@ "DataJoint connection (connected) root@localhost:3306" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -48,6 +51,7 @@ }, { "cell_type": "markdown", + "id": "ee820754-bceb-476a-acf9-238fa8b201d9", "metadata": {}, "source": [ "Importing the module `workflow_session.pipeline` is sufficient to create tables inside the elements. This workflow comes prepackaged with example data and ingestion functions to populate lab, subject, and session tables." @@ -55,7 +59,8 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, + "id": "868b79bc-f754-4d51-a327-94a209cde374", "metadata": {}, "outputs": [], "source": [ @@ -67,7 +72,8 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, + "id": "9c211f0c-16fd-4d51-abf3-d67bbe271c26", "metadata": {}, "outputs": [ { @@ -95,6 +101,7 @@ }, { "cell_type": "markdown", + "id": "2e19116d-bc32-4cea-9caf-f3e8eaa9b181", "metadata": {}, "source": [ "## Workflow architecture" @@ -102,7 +109,8 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, + "id": "1e7a0a8b-eaf1-41a1-bf08-1aff2f2812be", "metadata": {}, "outputs": [ { @@ -199,7 +207,7 @@ " (Total: 2)" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -210,288 +218,289 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, + "id": "63679df4-3064-402b-99ce-2f553dff877b", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", "`neuro_lab`.`#skull_reference`\n", - "\n", + "\n", "`neuro_lab`.`#skull_reference`\n", "\n", - "\n", + "\n", "\n", - "lab.Equipment.CaImgEquipment\n", - "lab.Project\n", + "\n", - "\n", - "lab.Equipment.CaImgEquipment\n", + "\n", + "lab.Project\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.ProjectUser\n", - "\n", - "\n", - "lab.ProjectUser\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Publication\n", + "\n", + "\n", + "lab.Project.Publication\n", "\n", "\n", "\n", + "\n", + "\n", + "lab.Project->lab.Project.Publication\n", + "\n", + "\n", "\n", - "\n", + "\n", "lab.Project.Keywords\n", - "\n", - "\n", - "lab.Project.Keywords\n", + "\n", + "lab.Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Sourcecode\n", + "\n", + "\n", + "lab.Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment.EphysEquipment\n", + "\n", + "\n", + "lab.Equipment.EphysEquipment\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.ProtocolType\n", - "\n", - "\n", - "lab.ProtocolType\n", + "\n", + "lab.ProtocolType\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.Protocol\n", - "\n", - "\n", - "lab.Protocol\n", + "\n", + "lab.Protocol\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.ProtocolType->lab.Protocol\n", - "\n", - "\n", - "\n", - "\n", - "lab.LabMembership\n", - "\n", - "\n", - "lab.LabMembership\n", - "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "lab.Location\n", - "\n", + "\n", + "lab.Equipment\n", + "\n", - "\n", - "lab.Location\n", + "\n", + "lab.Equipment\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Project.Publication\n", - "\n", - "\n", - "lab.Project.Publication\n", - "\n", - "\n", + "\n", + "\n", + "lab.Equipment->lab.Equipment.EphysEquipment\n", + "\n", "\n", - "\n", - "\n", - "lab.UserRole\n", - "\n", + "\n", + "lab.Equipment.CaImgEquipment\n", + "\n", - "\n", - "lab.UserRole\n", + "\n", + "lab.Equipment.CaImgEquipment\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.UserRole->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Equipment->lab.Equipment.CaImgEquipment\n", + "\n", "\n", - "\n", - "\n", - "lab.Source\n", - "\n", + "\n", + "lab.Lab\n", + "\n", - "\n", - "lab.Source\n", + "\n", + "lab.Lab\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Equipment\n", - "\n", + "\n", + "lab.Location\n", + "\n", - "\n", - "lab.Equipment\n", + "\n", + "lab.Location\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Equipment->lab.Equipment.CaImgEquipment\n", - "\n", + "\n", + "\n", + "lab.Lab->lab.Location\n", + "\n", "\n", - "\n", - "\n", - "lab.Equipment.EphysEquipment\n", - "\n", + "\n", + "lab.LabMembership\n", + "\n", - "\n", - "lab.Equipment.EphysEquipment\n", + "\n", + "lab.LabMembership\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Equipment->lab.Equipment.EphysEquipment\n", - "\n", + "\n", + "\n", + "lab.Lab->lab.LabMembership\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.User\n", - "\n", - "\n", - "lab.User\n", + "\n", + "lab.User\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.User->lab.ProjectUser\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.User->lab.LabMembership\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project\n", - "\n", - "\n", - "lab.Project\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.Project.Keywords\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.Project.Publication\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project.Sourcecode\n", - "\n", + "\n", + "lab.UserRole\n", + "\n", - "\n", - "lab.Project.Sourcecode\n", + "\n", + "lab.UserRole\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Project->lab.Project.Sourcecode\n", - "\n", + "\n", + "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", "\n", - "\n", + "\n", "\n", - "lab.Lab\n", - "lab.Source\n", + "\n", - "\n", - "lab.Lab\n", + "\n", + "lab.Source\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Lab->lab.LabMembership\n", - "\n", - "\n", - "\n", - "\n", - "lab.Lab->lab.Location\n", - "\n", - "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -502,7 +511,8 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, + "id": "8cf0f64b-e523-4a94-9a43-fca4ed793f82", "metadata": {}, "outputs": [ { @@ -618,7 +628,7 @@ " (Total: 8)" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -629,285 +639,286 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, + "id": "75576be2-2984-451f-a86b-f05f9ddec6b7", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "subject.Subject.Protocol\n", - "subject.Allele\n", + "\n", - "\n", - "subject.Subject.Protocol\n", + "\n", + "subject.Allele\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject.Source\n", - "\n", + "\n", + "subject.Zygosity\n", + "\n", - "\n", - "subject.Subject.Source\n", + "\n", + "subject.Zygosity\n", "\n", "\n", "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", + "\n", "\n", - "\n", + "\n", "subject.Allele.Source\n", - "\n", - "\n", - "subject.Allele.Source\n", + "\n", + "subject.Allele.Source\n", "\n", "\n", "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", + "\n", "\n", - "\n", + "\n", "subject.Line.Allele\n", - "\n", - "\n", - "subject.Line.Allele\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "subject.Subject.Line\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject.Lab\n", - "\n", + "\n", + "subject.Subject.User\n", + "\n", - "\n", - "subject.Subject.Lab\n", + "\n", + "subject.Subject.User\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "subject.Strain\n", - "\n", - "\n", - "subject.Strain\n", + "\n", + "subject.Strain\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "subject.Subject.Strain\n", - "\n", - "\n", - "subject.Subject.Strain\n", + "\n", + "subject.Subject.Strain\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "subject.Strain->subject.Subject.Strain\n", - "\n", + "\n", "\n", - "\n", - "\n", - "subject.Allele\n", - "\n", + "\n", + "subject.SubjectDeath\n", + "\n", - "\n", - "subject.Allele\n", + "\n", + "subject.SubjectDeath\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Allele->subject.Allele.Source\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->subject.Line.Allele\n", - "\n", - "\n", - "\n", - "\n", - "subject.Zygosity\n", - "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", - "\n", - "subject.Zygosity\n", + "\n", + "subject.Subject.Protocol\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Allele->subject.Zygosity\n", - "\n", - "\n", "\n", - "\n", + "\n", "subject.Subject\n", - "\n", - "\n", - "subject.Subject\n", + "\n", + "subject.Subject\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Protocol\n", - "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Source\n", - "\n", + "subject.Subject->subject.Zygosity\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Lab\n", - "\n", + "subject.Subject->subject.Subject.User\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Strain\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject.Line\n", - "\n", - "\n", - "subject.Subject.Line\n", - "\n", - "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Line\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->subject.Zygosity\n", - "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject.User\n", - "\n", + "\n", + "subject.Subject.Source\n", + "\n", - "\n", - "subject.Subject.User\n", + "\n", + "subject.Subject.Source\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject->subject.Subject.User\n", - "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", "\n", "\n", - "\n", + "\n", "subject.SubjectCullMethod\n", - "\n", - "\n", - "subject.SubjectCullMethod\n", + "\n", + "subject.SubjectCullMethod\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "subject.Subject->subject.SubjectCullMethod\n", - "\n", + "\n", "\n", - "\n", - "\n", - "subject.SubjectDeath\n", - "\n", + "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", - "\n", - "subject.SubjectDeath\n", + "\n", + "subject.Subject.Lab\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.SubjectDeath\n", - "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", "\n", "\n", - "\n", + "\n", "subject.Line\n", - "\n", - "\n", - "subject.Line\n", + "\n", + "subject.Line\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Line->subject.Line.Allele\n", - "\n", + "subject.Line->subject.Subject.Line\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Line->subject.Subject.Line\n", - "\n", + "subject.Line->subject.Line.Allele\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -918,7 +929,8 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, + "id": "5243a782-93da-40fa-b243-03ddcb230c1d", "metadata": {}, "outputs": [ { @@ -1004,7 +1016,7 @@ " (Total: 4)" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -1015,59 +1027,59 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, + "id": "7e48d7c0-b7bd-4f0b-abcb-1aedc69d5310", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "session.SessionDirectory\n", - "session.Session\n", + "\n", - "\n", - "session.SessionDirectory\n", + "\n", + "session.Session\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session\n", - "session.ProjectSession\n", + "\n", - "\n", - "session.Session\n", + "\n", + "session.ProjectSession\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session->session.SessionDirectory\n", - "\n", + "session.Session->session.ProjectSession\n", + "\n", "\n", - "\n", + "\n", "\n", - "session.SessionNote\n", + "session.SessionDirectory\n", "\n", - "\n", - "session.SessionNote\n", + "\n", + "session.SessionDirectory\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session->session.SessionNote\n", - "\n", + "session.Session->session.SessionDirectory\n", + "\n", "\n", "\n", "\n", @@ -1075,40 +1087,41 @@ "\n", - "\n", - "session.SessionExperimenter\n", + "\n", + "session.SessionExperimenter\n", "\n", "\n", "\n", "\n", "\n", "session.Session->session.SessionExperimenter\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "session.ProjectSession\n", - "session.SessionNote\n", + "\n", - "\n", - "session.ProjectSession\n", + "\n", + "session.SessionNote\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session->session.ProjectSession\n", - "\n", + "session.Session->session.SessionNote\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -1119,6 +1132,7 @@ }, { "cell_type": "markdown", + "id": "c510fe4d-09ed-472f-830f-4401bd6830d0", "metadata": {}, "source": [ "(Workflow needs continued development to import geotyping tables)" @@ -1126,31 +1140,26 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, + "id": "1984077d-a9f2-4b17-8034-ee2af8f105f4", "metadata": {}, "outputs": [], "source": [ - "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)\n", - "## Not yet defined" + "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)" ] }, { "cell_type": "markdown", + "id": "b60f5f4c-d366-4034-a40d-2d2095cb2a14", "metadata": {}, "source": [ "## Explore each table" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "DataJoint provide tools to explore table definitions and table contents." - ] - }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, + "id": "9c0821e1-9125-4c41-bc9c-567f53d0a5e5", "metadata": {}, "outputs": [ { @@ -1165,16 +1174,27 @@ "subject_description=\"\" : varchar(1024) \n", "\n" ] + }, + { + "data": { + "text/plain": [ + "'# Animal Subject\\nsubject : varchar(32) \\n---\\nsex : enum(\\'M\\',\\'F\\',\\'U\\') \\nsubject_birth_date : date \\nsubject_description=\"\" : varchar(1024) \\n'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "# check table definition with describe()\n", - "subject.Subject.describe();" + "subject.Subject.describe()" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, + "id": "504d55ed-25bd-4bb1-bce9-c516bd3595df", "metadata": {}, "outputs": [ { @@ -1187,16 +1207,27 @@ "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\n", "\n" ] + }, + { + "data": { + "text/plain": [ + "\"-> subject.Subject\\n-> subject.Allele\\n---\\nzygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\\n\"" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "# check table definition with dependencies with describe()\n", - "subject.Zygosity.describe();" + "subject.Zygosity.describe()" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, + "id": "1edded31-bdef-42fb-8f58-b42b9b186cf1", "metadata": {}, "outputs": [ { @@ -1209,7 +1240,7 @@ "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -1222,6 +1253,7 @@ }, { "cell_type": "markdown", + "id": "f6c110c0-0966-4283-a0ba-a7de2ce69e25", "metadata": {}, "source": [ "## Insert data into Manual and Lookup tables" @@ -1229,6 +1261,7 @@ }, { "cell_type": "markdown", + "id": "54cf050e-882e-4672-be31-1ca3df52fa58", "metadata": {}, "source": [ "Tables in this workflow are either manual tables or lookup tables. To insert into these tables, DataJoint provide method `.insert1()` and `insert()`." @@ -1236,41 +1269,30 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true, - "jupyter": { - "outputs_hidden": true - }, - "tags": [] - }, - "outputs": [ - { - "ename": "DuplicateError", - "evalue": "(\"Duplicate entry 'subject1' for key 'PRIMARY'\", 'To ignore duplicate entries in insert, set skip_duplicates=True')", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDuplicateError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_46675/2258948651.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# `insert1()` takes a dict or a tuple\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m subject.Subject.insert1(\n\u001b[0m\u001b[1;32m 3\u001b[0m dict(subject='subject1', sex='M', subject_birth_date='2020-12-30', \n\u001b[1;32m 4\u001b[0m subject_description='test animal'))\n\u001b[1;32m 5\u001b[0m subject.Subject.insert1(\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36minsert1\u001b[0;34m(self, row, **kwargs)\u001b[0m\n\u001b[1;32m 264\u001b[0m \u001b[0mFor\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msee\u001b[0m \u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 265\u001b[0m \"\"\"\n\u001b[0;32m--> 266\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrow\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 267\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0minsert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrows\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreplace\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_duplicates\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mignore_extra_fields\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mallow_direct_insert\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/table.py\u001b[0m in \u001b[0;36minsert\u001b[0;34m(self, rows, replace, skip_duplicates, ignore_extra_fields, allow_direct_insert)\u001b[0m\n\u001b[1;32m 335\u001b[0m 'To ignore extra fields in insert, set ignore_extra_fields=True')\n\u001b[1;32m 336\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mDuplicateError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 337\u001b[0;31m raise err.suggest(\n\u001b[0m\u001b[1;32m 338\u001b[0m 'To ignore duplicate entries in insert, set skip_duplicates=True')\n\u001b[1;32m 339\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDuplicateError\u001b[0m: (\"Duplicate entry 'subject1' for key 'PRIMARY'\", 'To ignore duplicate entries in insert, set skip_duplicates=True')" - ] - } - ], + "execution_count": 15, + "id": "d5b43904-9711-4bce-8ae5-d0d797118dec", + "metadata": {}, + "outputs": [], "source": [ - "# `insert1()` takes a dict or a tuple\n", "subject.Subject.insert1(\n", " dict(subject='subject1', sex='M', subject_birth_date='2020-12-30', \n", - " subject_description='test animal'))\n", + " subject_description='test animal'), skip_duplicates=True)\n", "subject.Subject.insert1(\n", - " ('subject2', 'F', '2020-11-30', 'test animal'))" + " ('subject2', 'F', '2020-11-30', 'test animal'), skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "49d43ca2-2cd3-4659-849f-5bcc09c1367e", + "metadata": {}, + "source": [ + "`skip_duplicates=True` will prevent an error if you already have data for the primary keys in a given entry." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 16, + "id": "9bf2c953-7b4c-4a70-99fd-124a4d28171b", "metadata": {}, "outputs": [ { @@ -1348,16 +1370,28 @@ "test animal
    subject2\n", "F\n", "2020-11-30\n", + "test animal
    subject3\n", + "F\n", + "2020-12-30\n", + "test animal
    subject4\n", + "M\n", + "2021-02-12\n", "test animal
    subject5\n", "F\n", "2020-01-03\n", "lmash_E105
    subject6\n", "M\n", "2020-01-03\n", - "hneih_E105
    \n", + "hneih_E105
    subject7\n", + "U\n", + "2020-08-30\n", + "test animal
    subject8\n", + "F\n", + "2020-09-30\n", + "test animal
    \n", " \n", " \n", - "

    Total: 4

    \n", + "

    Total: 8

    \n", " " ], "text/plain": [ @@ -1365,12 +1399,16 @@ "+----------+ +-----+ +------------+ +------------+\n", "subject1 M 2020-12-30 test animal \n", "subject2 F 2020-11-30 test animal \n", + "subject3 F 2020-12-30 test animal \n", + "subject4 M 2021-02-12 test animal \n", "subject5 F 2020-01-03 lmash_E105 \n", "subject6 M 2020-01-03 hneih_E105 \n", - " (Total: 4)" + "subject7 U 2020-08-30 test animal \n", + "subject8 F 2020-09-30 test animal \n", + " (Total: 8)" ] }, - "execution_count": 19, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -1381,29 +1419,31 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 17, + "id": "7a10ddab-d0fd-45a0-8183-09c1b1933e0a", "metadata": {}, "outputs": [], "source": [ "# `insert()` takes a list of dicts or tuples\n", "subject.Subject.insert(\n", " [dict(subject='subject3', sex='F', subject_birth_date='2020-12-30', \n", - " subject_description='test animal'),\n", + " subject_description='test animal'),\n", " dict(subject='subject4', sex='M', subject_birth_date='2021-02-12', \n", " subject_description='test animal')\n", - " ]\n", - ")\n", + " ],\n", + " skip_duplicates=True)\n", "subject.Subject.insert(\n", " [\n", " ('subject7', 'U', '2020-08-30', 'test animal'),\n", " ('subject8', 'F', '2020-09-30', 'test animal')\n", - " ]\n", - ")" + " ],\n", + " skip_duplicates=True)" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, + "id": "064ddaae-3410-47fc-be22-671d2afe7fb6", "metadata": {}, "outputs": [ { @@ -1519,7 +1559,7 @@ " (Total: 8)" ] }, - "execution_count": 22, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -1530,6 +1570,7 @@ }, { "cell_type": "markdown", + "id": "c47691a0-b016-4092-a5ad-fefff93c54dd", "metadata": {}, "source": [ "For more documentation of insert, please refer to [DataJoint Docs](https://docs.datajoint.io/python/manipulation/1-Insert.html) and [DataJoint playground](https://playground.datajoint.io/)" @@ -1537,6 +1578,7 @@ }, { "cell_type": "markdown", + "id": "13f8a8ed-2656-46d8-82ba-cdf130c4873e", "metadata": {}, "source": [ "## Insert into Manual and Lookup tables with Graphical User Interface" @@ -1544,11 +1586,10 @@ }, { "cell_type": "markdown", + "id": "4775dd80-8a54-47b7-a9ba-99995db9ff1a", "metadata": {}, "source": [ - "DataJoint Neuro also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows.\n", - " \n", - "![DataJoint Labbook preview](../images/DataJoint_Labbook.png)" + "DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](../images/DataJoint_Labbook.png)" ] } ], @@ -1572,5 +1613,5 @@ } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/requirements.txt b/requirements.txt index b6227cd..eeb25c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ element-lab element-animal element-session ipykernel -pynwb \ No newline at end of file +pynwb==1.4.0 \ No newline at end of file diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index 389d941..93b8aae 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -1,9 +1,11 @@ import datajoint as dj + from element_lab import lab -from element_animal import subject +from element_animal import subject, genotyping from element_session import session from element_animal.subject import Subject +from element_animal.genotyping import Sequence, BreedingPair, Cage, SubjectCaging, GenotypeTest from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session import Session @@ -21,3 +23,5 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) + +genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) \ No newline at end of file From fd6ff4f33f29dadc07a1e6e1873ce8ba054183ec Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 16 Dec 2021 16:27:53 -0600 Subject: [PATCH 08/46] fixing readme typos --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0bd03b5..6654adc 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,15 @@ This directory provides an example workflow to save the information related to l + [element-session](https://github.com/datajoint/element-session) This repository provides demonstrations for: -Set up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) +Setting up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) ## Workflow architecture -The lab and animal management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together to a functional workflow. +The lab and animal management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. ### element-lab +element-lab is used for all lab-general metadata, including personnel and projects. Many fields are optional, but may help for keeping track of lab members for multi-site projects or exporting data for publication. + ![lab](images/lab_diagram.svg) ### element-animal @@ -22,15 +24,15 @@ element-animal contains two modules, `subject` and `genotyping`. `subject` contains basic information of subjects. ![subject](images/subject_diagram2.svg) -`genotyping` is designed for labs that handle animal care and genotyping themselves, which is optional. +`genotyping` is designed for labs that handle animal care and genetic information themselves, which is optional. ![genotyping](images/genotyping_diagram2.svg) ### element-session -`session` is designed to handle metadata related to data collection, including collection datetime, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. +`session` is designed to handle metadata related to data collection, including collection date-time, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. ![session](images/session_diagram2.png) ### This workflow -This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements more intact workflows, refer to: +This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements in tandem with other data collection modalities, refer to: + [workflow-array-ephys](https://github.com/datajoint/workflow-array-ephys) + [workflow-calcium-imaging](https://github.com/datajoint/workflow-calcium-imaging) @@ -132,7 +134,7 @@ create a new file `dj_local_conf.json` with the following template: } ``` -+ Specify database's `hostname`, `username`, and `password` properly. ++ Specify database's `hostname`, `username`, and `password` according to the database you plan to use (see [set-up instructions here](https://tutorials.datajoint.io/setting-up/get-database.html)). + Specify a `database.prefix` to create the schemas. @@ -144,7 +146,9 @@ create a new file `dj_local_conf.json` with the following template: ## Interacting with the DataJoint pipeline and exploring data -+ Connect to database and import tables ++ [Connect to database](https://tutorials.datajoint.io/setting-up/get-database.html) + ++ Import tables ``` from workflow_session.pipeline import * ``` From 7688006460e5232e80789a24b88cad2b04ddde9a Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 22 Dec 2021 18:45:31 -0600 Subject: [PATCH 09/46] start integration test add, WIP --- .gitignore | 81 ++--- CONTRIBUTING.md | 3 + apt-requirements.txt | 1 + docker-compose-test.yaml | 36 +++ notebooks/2_Explore_Export.ipynb | 43 +-- requirements_test.txt | 3 + temp_test.ipynb | 281 +++++++++++++++++- tests/__init__.py | 179 +++++++++++ tests/test_ingest.py | 31 ++ tests/test_pipeline_generation.py | 18 ++ tests/test_populate.py | 11 + .../animal/_Other_Genotyping_Data_TBD.csv | 0 user_data/animal/line.csv | 3 +- user_data/animal/source.csv | 2 +- user_data/animal/strain.csv | 2 +- user_data/animal/subjects.csv | 4 +- user_data/lab/projects.csv | 2 +- workflow_session/ingest.py | 110 ++++--- 18 files changed, 680 insertions(+), 130 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 apt-requirements.txt create mode 100644 docker-compose-test.yaml create mode 100644 requirements_test.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_ingest.py create mode 100644 tests/test_pipeline_generation.py create mode 100644 tests/test_populate.py delete mode 100644 user_data/animal/_Other_Genotyping_Data_TBD.csv diff --git a/.gitignore b/.gitignore index 77113c7..5bb6f36 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,9 @@ -# vscode -.vscode - -# User data -.DS_Store - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class -# C extensions -*.so - -# Distribution / packaging +# Distribution, packaging, PyInstaller .Python env/ build/ @@ -30,17 +21,10 @@ wheels/ *.egg-info/ .installed.cfg *.egg -.idea/ - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec - -# Installer logs pip-log.txt -pip-delete-this-directory.txt +pip-delete*.txt # Unit test / coverage reports htmlcov/ @@ -52,71 +36,50 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +.pytest_cache/ +docker-compose.y*ml -# Translations +# C extension, Translations +*.so *.mo *.pot -# Django stuff: +# editors: vscode, emacs, Mac +.vscode +**/*~ +**/#*# +**/.#* +.DS_Store + +# Django, Flask, Scrapy, Sphinx, mkdocs: +# PyBuilder, Jupyter, SageMath, celery beat *.log local_settings.py - -# Flask stuff: instance/ .webassets-cache - -# Scrapy stuff: .scrapy scratchpaper.* - -# Sphinx documentation docs/_build/ - -# PyBuilder +/site target/ - -# Jupyter Notebook .ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file celerybeat-schedule - -# SageMath parsed files *.sage.py -# dotenv +# dotenv, virtualenv, pyenv, mypy ./.env - -# virtualenv .venv venv/ ENV/ +.python-version +.mypy_cache/ -# Spyder project settings +# Spyder/Rope project settings .spyderproject .spyproject - -# Rope project settings .ropeproject -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -# datajoint +# datajoint, notes, nwb export dj_local_c*.json - -# notes, output nwb file from export NB temp* -*nwb - -# emacs -**/*~ -**/#*# -**/.#* -docker-compose.yml +*nwb \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f07cecb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contribution Guidelines + +This project follows the [DataJoint Contribution Guidelines](https://docs.datajoint.io/python/community/02-Contribute.html). Please reference the link for more full details. \ No newline at end of file diff --git a/apt-requirements.txt b/apt-requirements.txt new file mode 100644 index 0000000..3daada4 --- /dev/null +++ b/apt-requirements.txt @@ -0,0 +1 @@ +locales-all \ No newline at end of file diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml new file mode 100644 index 0000000..56407b2 --- /dev/null +++ b/docker-compose-test.yaml @@ -0,0 +1,36 @@ +version: "2.4" +x-net: &net + networks: + - main +services: + db: + <<: *net + image: datajoint/mysql:5.7 + environment: + - MYSQL_ROOT_PASSWORD=simple + workflow_array_ephys: + <<: *net + build: + context: . + image: workflow_session:v0.0.0 + environment: + - DJ_HOST=db + - DJ_USER=root + - DJ_PASS=simple + - EPHYS_ROOT_DATA_DIR=/main/test_data + - DATABASE_PREFIX=test_ + command: + - bash + - -c + - | + echo "------ INTEGRATION TESTS ------" + pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings + tail -f /dev/null + volumes: + - ${TEST_DATA_DIR}:/main/test_data + - ./apt_requirements.txt:/tmp/apt_requirements.txt + depends_on: + db: + condition: service_healthy +networks: + main: \ No newline at end of file diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index 9cea320..c1a1cf6 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -80,10 +80,7 @@ "metadata": {}, "outputs": [], "source": [ - "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", - "mylab_key = (lab.Lab & 'lab=\"LabA\"').fetch1('KEY')\n", - "myproj_key= (lab.Project & 'project=\"ProjA\"').fetch1('KEY')\n", - "myprot_key= (lab.Protocol() & 'protocol=\"ProtA\"').fetch1('KEY')" + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')" ] }, { @@ -91,34 +88,40 @@ "id": "902e050c-3133-4fb7-850d-f0b954e1b634", "metadata": {}, "source": [ - "Get export function and related pynwb dependency, then export with keys from prev step." + "Get export from element-session, then export with keys from prev step." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", "metadata": {}, + "outputs": [], + "source": [ + "from element_session.export import session_to_nwb" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "fdc6d3c7-8f4d-41c8-af6f-5c63ab68e316", + "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function session_to_nwb in module element_session.export.nwb:\n", - "\n", - "session_to_nwb(session_key)\n", - " Generate one NWBFile object representing all session-level information,\n", - " including session identifier, description, start time, etc.\n", - " \n", - " :param session_key: entry in session.Session table\n", - " :return: NWBFile object\n", - "\n" + "ename": "DataJointError", + "evalue": "fetch1 requires exactly one tuple in the input set.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_1373/2627209259.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubject\u001b[0m\u001b[0;34m&\u001b[0m\u001b[0;34m\"subject!='subject5'\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/fetch.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, squeeze, download_path, *attrs)\u001b[0m\n\u001b[1;32m 246\u001b[0m \u001b[0mret\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 247\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mret\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 248\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDataJointError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'fetch1 requires exactly one tuple in the input set.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 249\u001b[0m ret = dict((name, _get(self._expression.connection, heading[name], ret[name],\n\u001b[1;32m 250\u001b[0m squeeze=squeeze, download_path=download_path))\n", + "\u001b[0;31mDataJointError\u001b[0m: fetch1 requires exactly one tuple in the input set." ] } ], "source": [ - "from element_session.export import session_to_nwb_dict, session_to_nwb\n", - "help(session_to_nwb)" + "(subject.Subject&\"subject!='subject5'\").fetch1()" ] }, { diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..e76c2da --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,3 @@ +pytest +pytest-cov +djarchive-client @ git+https://github.com/datajoint/djarchive-client.git \ No newline at end of file diff --git a/temp_test.ipynb b/temp_test.ipynb index 5a691f8..6aff50c 100644 --- a/temp_test.ipynb +++ b/temp_test.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "3f1d9665-2983-425c-b4a6-8e6d45f1ad34", + "metadata": {}, + "source": [ + "## Initial Dev" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -207,11 +215,282 @@ "if [x for x in (a, b,c) if x is not None]: print('g')" ] }, + { + "cell_type": "markdown", + "id": "1764858b-2470-4b81-b02a-dd0121d46210", + "metadata": {}, + "source": [ + "## Orig ingest, before generalize" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, + "id": "e5e9d016-f2a7-4db5-9d8f-c4a6c06f693c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Inserting 0 entry(s) into lab tables ----\n", + "\n", + "---- Inserting 0 entry(s) into project table ----\n", + "\n", + "---- Inserting 0 entry(s) into publication/keyword tables ----\n", + "\n", + "---- Inserting 0 entry(s) into protocol tables ----\n", + "\n", + "---- Inserting 0 entry(s) into subject tables ----\n", + "\n", + "---- Inserting 0 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "def ingest_lab(lab_csv_path='./user_data/lab/labs.csv',\n", + " project_csv_path='./user_data/lab/projects.csv',\n", + " pubs_csv_path='./user_data/lab/publications.csv',\n", + " keyw_csv_path='./user_data/lab/keywords.csv',\n", + " protocol_csv_path='./user_data/lab/protocols.csv',\n", + " skip_duplicates=True, ignore_extra_fields=True):\n", + " # -------------- Insert new \"Lab\" --------------\n", + " with open(lab_csv_path, newline= '') as f:\n", + " input_labs = list(csv.DictReader(f, delimiter=','))\n", + "\n", + " prev_len = len(lab.Lab())\n", + " lab.Lab.insert(input_labs, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " lab.Location.insert(input_labs, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " insert_len = len(lab.Lab()) - prev_len\n", + " \n", + " print(f'\\n---- Inserting {insert_len} entry(s) into lab tables ----')\n", + "\n", + " # -------------- Insert new \"Project\" --------------\n", + " with open(project_csv_path, newline= '') as f:\n", + " input_projs = list(csv.DictReader(f, delimiter=','))\n", + " \n", + " prev_len = len(lab.Project())\n", + " lab.Project.insert(input_projs, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " insert_len = len(lab.Project()) - prev_len\n", + " print(f'\\n---- Inserting {insert_len} entry(s) into project table ----')\n", + "\n", + " # -------------- Insert publications + keywords --------------\n", + " with open(pubs_csv_path, newline= '') as f:\n", + " input_pubs = list(csv.DictReader(f, delimiter=','))\n", + " with open(keyw_csv_path, newline= '') as f:\n", + " input_keyw = list(csv.DictReader(f, delimiter=','))\n", + " \n", + " prev_len = len(lab.Project.Publication()) + len(lab.Project.Keywords())\n", + " lab.Project.Publication.insert(input_pubs, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " lab.Project.Keywords.insert(input_keyw, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " insert_len = len(lab.Project.Publication()) + len(lab.Project.Keywords()) - prev_len\n", + " \n", + " print(f'\\n---- Inserting {insert_len} entry(s) into publication/keyword tables ----')\n", + "\n", + " # -------------- Insert new \"Protocol\" --------------\n", + " with open(protocol_csv_path, newline= '') as f:\n", + " input_prots = list(csv.DictReader(f, delimiter=','))\n", + " prev_len = len(lab.Protocol())\n", + " lab.Protocol.insert(input_prots, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " lab.ProtocolType.insert(input_prots, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " insert_len = len(lab.Protocol()) - prev_len\n", + " print(f'\\n---- Inserting {insert_len} entry(s) into protocol tables ----')\n", + "\n", + "def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'):\n", + " # -------------- Insert new \"Subject\" --------------\n", + " with open(subject_csv_path, newline= '') as f:\n", + " input_subjects = list(csv.DictReader(f, delimiter=','))\n", + " print(f'\\n---- Inserting {insert_len} entry(s) into subject tables ----')\n", + " subject.Subject.insert(input_subjects, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " subject.SubjectDeath.insert(input_subjects, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " ## Skipped allele info\n", + "\n", + "def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'):\n", + " with open(session_csv_path, newline= '') as f:\n", + " input_sessions = list(csv.DictReader(f, delimiter=','))\n", + "\n", + " print(f'\\n---- Inserting {insert_len} entry(s) into session.Session ----')\n", + " session.Session.insert(input_sessions, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + " session.SessionDirectory.insert(input_sessions, skip_duplicates=skip_duplicates,\n", + " ignore_extra_fields=ignore_extra_fields)\n", + "\n", + " ## Type Error: 'NoneType' object is not iterable\n", + " # session.SessionNote.insert(input_sessions, skip_duplicates=skip_duplicates,\n", + " # ignore_extra_fields=ignore_extra_fields)\n", + "\n", + "\n", + "if __name__ == '__main__':\n", + " ingest_lab()\n", + " ingest_subjects()\n", + " ingest_sessions()" + ] + }, + { + "cell_type": "code", + "execution_count": 1, "id": "91347a4f-9024-4a33-a1b1-42d903d9b132", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/Volumes/GoogleDrive/My Drive/NWB/workflow-session'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "08b9898a-d07f-4baa-8bf9-6344fa0a7a82", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n" + ] + } + ], + "source": [ + "import datajoint as dj; dj.conn()\n", + "import pathlib\n", + "import csv\n", + "import re\n", + "from workflow_session.pipeline import lab, subject, session\n", + "from workflow_session.paths import get_root_data_dir\n", + "import element_data_loader.utils" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6bbc3fb1-01bb-42c9-ac99-83398e49a7a6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Inserting 0 entry(s) into #location ----\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "'dict' object has no attribute 'insert'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_7090/3634625444.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mingest\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mingest_lab\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m \u001b[0mingest_subjects\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0mingest_sessions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-session/workflow_session/ingest.py\u001b[0m in \u001b[0;36mingest_lab\u001b[0;34m(lab_csv_path, project_csv_path, pubs_csv_path, keyw_csv_path, protocol_csv_path, skip_duplicates)\u001b[0m\n\u001b[1;32m 55\u001b[0m [lab.Protocol(), lab.ProtocolType()]]\n\u001b[1;32m 56\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 57\u001b[0;31m \u001b[0mingest_general\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcsvs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_duplicates\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mskip_duplicates\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 58\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 59\u001b[0m def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv',\n", + "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-session/workflow_session/ingest.py\u001b[0m in \u001b[0;36mingest_general\u001b[0;34m(csvs, tables, skip_duplicates)\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtable\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# loop multiple tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0mprev_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# measure prev length\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m t.insert(data, skip_duplicates=skip_duplicates, # insert\n\u001b[0m\u001b[1;32m 28\u001b[0m ignore_extra_fields=True) # must be true for csvs w/mult tables\n\u001b[1;32m 29\u001b[0m \u001b[0minsert_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mprev_len\u001b[0m \u001b[0;31m# report length change\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'dict' object has no attribute 'insert'" + ] + } + ], + "source": [ + "from workflow_session.ingest import *\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3b2f2ff2-c6b4-489e-94be-b98dbfdfea12", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "lab : varchar(24) # Abbreviated lab name\n", + "---\n", + "lab_name : varchar(255) # full lab name\n", + "institution : varchar(255) \n", + "address : varchar(255) \n", + "time_zone : varchar(32) # UTC offset suggested e.g., UTC+1\n", + "\n" + ] + } + ], + "source": [ + "table=[lab.Lab()]\n", + "for t in table:\n", + " t.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f9fb0145-30ee-46f6-9f2c-bd0945be2f02", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +-----------+\n", + "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", + "LabB The Other Lab Other Uni Oxford OX1 2JD UTC+0 \n", + " (Total: 2)\n", + "]\n" + ] + } + ], + "source": [ + "if not isinstance(lab.Lab(),list): print([lab.Lab()])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c56a3e3c-76a3-473f-970a-5507dee55d20", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "element_lab.lab.Lab" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(lab.Lab())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfd86c54-b602-46e5-b2ff-b66a1f41a879", + "metadata": {}, "outputs": [], "source": [] } diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..a83b198 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,179 @@ +# dependencies: pip install pytest pytest-cov +# run all tests: pytest -sv --cov-report term-missing --cov=workflow-array-ephys -p no:warnings tests/ +# run one test, debug: pytest [above options] --pdb tests/tests_name.py -k function_name + +import os +import pytest +import pandas as pd +import pathlib +import numpy as np +import datajoint as dj + +import workflow_session +from workflow_session.paths import get_root_data_dir +import element_data_loader.utils + + +# ------------------- SOME CONSTANTS ------------------- + +_tear_down = True + +test_user_data_dir = pathlib.Path('./tests/user_data') +test_user_data_dir.mkdir(exist_ok=True) + +# ------------------- FIXTURES ------------------- + +@pytest.fixture(autouse=True) +def dj_config(): + """ If dj_local_config exists, load""" + if pathlib.Path('./dj_local_conf.json').exists(): + dj.config.load('./dj_local_conf.json') + dj.config['safemode'] = False + dj.config['custom'] = { + 'database.prefix': (os.environ.get('DATABASE_PREFIX') + or dj.config['custom']['database.prefix']), + 'root_data_dir': (os.environ.get('ROOT_DATA_DIR') + or dj.config['custom']['root_data_dir']) + } + return + + +@pytest.fixture +def pipeline(): + from workflow_session import pipeline + + yield {'subject': pipeline.subject, + 'lab': pipeline.lab, + 'session': pipeline.session, + 'get_root_data_dir': pipeline.get_root_data_dir} + + if _tear_down: + pipeline.subject.Subject.delete() + +@pytest.fixture +def lab_csv(): + input_lab = pd.DataFrame(columns=['lab', 'lab_name','institution', + 'address','time_zone','location', + 'location_description']) + input_lab.lab = ['LabA','LabB'] + input_lab.lab_name = ['The Example Lab','The Other Lab'] + input_lab.institution = ['Example Uni','Other Uni'] + input_lab.address = ['221B Baker St,London NW1 6XE,UK','Oxford OX1 2JD, United Kingdom'] + input_lab.time_zone = ['UTC+0','UTC+0'] + input_lab.location = ['Example Building','Other Building'] + input_lab.location_description = ['2nd floor lab dedicated to all fictional experiments.', + 'fictional campus dedicated to imaginary experiments.'] + lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') + input_lab.to_csv(lab_csv_path) # write csv file + yield input_lab, lab_csv_path + lab_csv_path.unlink() # delete csv file after use + +@pytest.fixture +def lab_proj_csv(): + input_lab_proj = pd.DataFrame(columns=['project','project_description','repositoryurl','repositoryname','pharmacology','viruses','slices','stimulus','surgery','codeurl']) + input_lab_proj.project = ['ProjA','ProjB'] + input_lab_proj.project_description = ['Example project to populate element-lab', + 'Other example project to populate element-lab'] + input_lab_proj.repositoryurl = ['https://github.com/datajoint/element-lab/', + 'https://github.com/datajoint/element-session/'] + input_lab_proj.repositoryname = ['element-lab','element-session'] + input_lab_proj.pharmacology = ['Subjects were administered 10ul sedative prior to surgery',''] + input_lab_proj.viruses = ['Exemplarvirus administered 10d before experimental session', + 'Exemplarvirus administered 8d study'] + input_lab_proj.slices = ['',''] + input_lab_proj.stimulus = ['videos generated programmatically see repository',''] + input_lab_proj.surgery = ['Craniotomy performed by session experimenter',''] + input_lab_proj.codeurl = ['https://github.com/datajoint/element-lab/tree/main/element_lab', + 'https://github.com/datajoint/element-session/tree/main/element_session'] + lab_proj_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') + input_lab_proj.to_csv(lab_proj_csv_path) # write csv file + yield input_lab_proj, lab_proj_csv_path + lab_proj_csv_path.unlink() + +@pytest.fixture +def lab_pubs_csv(): + input_lab_pubs = pd.DataFrame(columns=['project','publication']) + input_lab_pubs.project = ['ProjA','ProjA'] + input_lab_pubs.publication = ['arXiv:1807.11104','arXiv:1807.11104v1'] + lab_pubs_csv_path = pathlib.Path('./tests/user_data/lab/publications.csv') + input_lab_X.to_csv(lab_X_csv_path) # write csv file + yield input_lab_pubs, lab_pubs_csv_path + lab_pubs_csv_path.unlink() + +@pytest.fixture +def lab_keyw_csv(): + input_lab_keyw = pd.DataFrame(columns=['project','keyword']) + input_lab_keyw.project = ['ProjA','ProjA','ProjB'] + input_lab_keyw.keyword = ['Study','Example','Alternate'] + lab_keyw_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') + input_lab_keyw.to_csv(lab_keyw_csv_path) # write csv file + yield input_lab_keyw, lab_keyw_csv_path + lab_keyw_csv_path.unlink() + +@pytest.fixture +def lab_prot_csv(): + input_lab_prot = pd.DataFrame(columns=['protocol','protocol_type','protocol_description']) + input_lab_prot.protocol = ['ProtA','ProtB'] + input_lab_prot.protocol_type = ['IRB expedited review','Alternative Method'] + input_lab_prot.protocol_description = ['Protocol for managing data ingestion','Limited protocol for piloting only'] + lab_prot_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') + input_lab_prot.to_csv(lab_prot_csv_path) # write csv file + yield input_lab_prot, lab_prot_csv_path + lab_prot_csv_path.unlink() + +@pytest.fixture +def subjects_csv(): + """ Create a 'subjects.csv' file""" + input_subjects = pd.DataFrame(columns=['subject', 'sex', + 'subject_birth_date', + 'subject_description', + 'death_date','cull_method']) + input_subjects.subject = ['subject5', 'subject6'] + input_subjects.sex = ['F', 'M'] + input_subjects.subject_birth_date = ['2020-01-01 00:00:01', '2020-01-01 00:00:01'] + input_subjects.subject_description = ['rich', 'manuel'] + input_sessions.death_date= ['2020-10-02 00:00:01', '2020-10-03 00:00:01'] + input_sessions.cull_method = ['natural causes', 'natural causes'] + input_subjects = input_subjects.set_index('subject') + + subjects_csv_path = pathlib.Path('./tests/user_data/animal/subjects.csv') + input_subjects.to_csv(subjects_csv_path) # write csv file + + yield input_subjects, subjects_csv_path + + subjects_csv_path.unlink() # delete csv file after use + + +@pytest.fixture +def ingest_subjects(pipeline, subjects_csv): + from workflow_session.ingest import ingest_subjects + _, subjects_csv_path = subjects_csv + ingest_subjects(subject_csv_path=subjects_csv_path) + return + + +@pytest.fixture +def sessions_csv(test_data): + """ Create a 'sessions.csv' file""" + input_sessions = pd.DataFrame(columns=['subject', 'session_dir']) + input_sessions.subject = ['subject1', 'subject2', 'subject2', + 'subject3', 'subject4', 'subject5', + 'subject6'] + input_sessions.session_dir = sessions_dirs + input_sessions = input_sessions.set_index('subject') + + sessions_csv_path = pathlib.Path('./tests/user_data/sessions.csv') + input_sessions.to_csv(sessions_csv_path) # write csv file + + yield input_sessions, sessions_csv_path + + sessions_csv_path.unlink() # delete csv file after use + + +@pytest.fixture +def ingest_sessions(ingest_subjects, sessions_csv): + from workflow_session.ingest import ingest_sessions + _, sessions_csv_path = sessions_csv + ingest_sessions(sessions_csv_path) + return + diff --git a/tests/test_ingest.py b/tests/test_ingest.py new file mode 100644 index 0000000..e333d55 --- /dev/null +++ b/tests/test_ingest.py @@ -0,0 +1,31 @@ +import sys +import pathlib + +from . import (dj_config, pipeline, test_data, + subjects_csv, ingest_subjects, + sessions_csv, ingest_sessions, + testdata_paths) + + +def test_ingest_subjects(pipeline, ingest_subjects): + """Check length of subject.Subject""" + subject = pipeline['subject'] + assert len(subject.Subject()) == 6 + + +def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): + ephys = pipeline['ephys'] + probe = pipeline['probe'] + session = pipeline['session'] + get_ephys_root_data_dir = pipeline['get_ephys_root_data_dir'] + + assert len(session.Session()) == 7 + + sessions, _ = sessions_csv + sess = sessions.iloc[0] + + assert (session.SessionDirectory + & {'subject': sess.name}).fetch1('session_dir') == sess.session_dir + +def test_ingest_lab(): + pass \ No newline at end of file diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py new file mode 100644 index 0000000..9837a68 --- /dev/null +++ b/tests/test_pipeline_generation.py @@ -0,0 +1,18 @@ +from . import dj_config, pipeline + + +def test_generate_pipeline(pipeline): + subject = pipeline['subject'] + ephys = pipeline['ephys'] + probe = pipeline['probe'] + session = pipeline['session'] + + subject_tbl, *_ = session.Session.parents(as_objects=True) + + # test elements connection from lab, subject to Session + assert subject_tbl.full_table_name == subject.Subject.full_table_name + + # test elements connection from Session to probe, ephys + session_tbl, probe_tbl = ephys.ProbeInsertion.parents(as_objects=True) + assert session_tbl.full_table_name == session.Session.full_table_name + assert probe_tbl.full_table_name == probe.Probe.full_table_name diff --git a/tests/test_populate.py b/tests/test_populate.py new file mode 100644 index 0000000..f2b9aa8 --- /dev/null +++ b/tests/test_populate.py @@ -0,0 +1,11 @@ +import numpy as np + +from . import (dj_config, pipeline, test_data, + subjects_csv, ingest_subjects, + sessions_csv, ingest_sessions) + + +def test_nwb_export(): + # test with and without subj insertion + # with and without modules active? + pass diff --git a/user_data/animal/_Other_Genotyping_Data_TBD.csv b/user_data/animal/_Other_Genotyping_Data_TBD.csv deleted file mode 100644 index e69de29..0000000 diff --git a/user_data/animal/line.csv b/user_data/animal/line.csv index 5e53f5c..79b813d 100644 --- a/user_data/animal/line.csv +++ b/user_data/animal/line.csv @@ -1 +1,2 @@ -line, line_description, target_phenotype, is_active \ No newline at end of file +line, line_description, target_phenotype, is_active +C57BL/6J \ No newline at end of file diff --git a/user_data/animal/source.csv b/user_data/animal/source.csv index c3cc2ac..5437003 100644 --- a/user_data/animal/source.csv +++ b/user_data/animal/source.csv @@ -1 +1 @@ -allele, source_identifier, source_url, expression_data_url \ No newline at end of file +allele, source_identifier, source_url, expression_data_url diff --git a/user_data/animal/strain.csv b/user_data/animal/strain.csv index 88341fd..e4b7430 100644 --- a/user_data/animal/strain.csv +++ b/user_data/animal/strain.csv @@ -1 +1 @@ -strain, strain_standard_name, strain_desc \ No newline at end of file +strain, strain_standard_name, strain_desc diff --git a/user_data/animal/subjects.csv b/user_data/animal/subjects.csv index 3ce4aaa..862269b 100644 --- a/user_data/animal/subjects.csv +++ b/user_data/animal/subjects.csv @@ -1,3 +1,3 @@ subject,sex,subject_birth_date,subject_description,death_date,cull_method -subject5,F,2020-01-03,lmash_E105,2020-10-02,natural causes -subject6,M,2020-01-03,hneih_E105,2020-10-03,natural causes \ No newline at end of file +subject5,F,2020-01-01 00:00:01,rich,2020-10-02 00:00:01,natural causes +subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes \ No newline at end of file diff --git a/user_data/lab/projects.csv b/user_data/lab/projects.csv index e4ad602..27a01c6 100644 --- a/user_data/lab/projects.csv +++ b/user_data/lab/projects.csv @@ -1,3 +1,3 @@ project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,Exemplarvirus administered 10d before experimental session,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab -ProjB,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,,Exemplarvirus administered 10d before experimental session,,,,https://github.com/datajoint/element-lab/tree/main/element_lab +ProjB,Other example project to populate element-lab,https://github.com/datajoint/element-session/,element-session,,Exemplarvirus administered 8d study,,,,https://github.com/datajoint/element-session/tree/main/element_session diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 849318b..8b0c189 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -7,60 +7,82 @@ from workflow_session.paths import get_root_data_dir import element_data_loader.utils +def ingest_general(csvs, tables, # take list of csvs/dj tables + skip_duplicates=True): + """ + Inserts data from a series of csvs into their corresponding table: + e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], + [lab.Lab(),lab.Project()] + ingest_general(csvs, tables, skip_duplicates=True, ignore_extra_fields=True) + :param csvs: list of relative paths to CSV files + :param tables: list of datajoint tables with () + """ + for insert, table in zip(csvs, tables): # loop through lists + with open(insert, newline='') as f: # read csvs + data = list(csv.DictReader(f, delimiter=',')) + prev_len = len(table) # measure prev length + table.insert(data, skip_duplicates=skip_duplicates, # insert + ignore_extra_fields=True) # must be true for csvs w/mult tables + insert_len = len(table) - prev_len # report length change + print(f'\n---- Inserting {insert_len} entry(s) into {table.table_name} ----') + + # TODO: permit embedded lists + def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', project_csv_path='./user_data/lab/projects.csv', pubs_csv_path='./user_data/lab/publications.csv', keyw_csv_path='./user_data/lab/keywords.csv', - protocol_csv_path='./user_data/lab/protocols.csv' - ): - # -------------- Insert new "Lab" -------------- - with open(lab_csv_path, newline= '') as f: - input_labs = list(csv.DictReader(f, delimiter=',')) - print(f'\n---- Insert {len(input_labs)} entry(s) into lab tables ----') - lab.Lab.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True) - lab.Location.insert(input_labs, skip_duplicates=True, ignore_extra_fields=True) - # -------------- Insert new "Project" -------------- - with open(project_csv_path, newline= '') as f: - input_projs = list(csv.DictReader(f, delimiter=',')) - print(f'\n---- Insert {len(input_projs)} entry(s) into project table ----') - lab.Project.insert(input_projs, skip_duplicates=True, ignore_extra_fields=True) - # -------------- Insert publications + keywords -------------- - with open(pubs_csv_path, newline= '') as f: - input_pubs = list(csv.DictReader(f, delimiter=',')) - with open(keyw_csv_path, newline= '') as f: - input_keyw = list(csv.DictReader(f, delimiter=',')) - print(f'\n---- Insert entry(s) into publication/keyword tables ----') - lab.Project.Publication.insert(input_pubs, skip_duplicates=True, ignore_extra_fields=True) - lab.Project.Keywords.insert(input_keyw, skip_duplicates=True, ignore_extra_fields=True) + protocol_csv_path='./user_data/lab/protocols.csv', + skip_duplicates=True): + """ + Inserts data from a series of csvs into their corresponding lab schema tables. + By default, uses data from workflow_session/user_data/lab/ + :param lab_csv_path: relative path of lab csv + :param project_csv_path: relative path of project csv + :param pubs_csv_path: relative path of publication csv + :param keyw_csv_path: relative path of keyword csv + :param protocol_csv_path: relative path of protocol csv + :param skip_duplicates=True: datajoint insert function param + """ + + # List with repeats for when mult dj.tables fed by same CSV + csvs = [lab_csv_path, lab_csv_path, + project_csv_path, pubs_csv_path, keyw_csv_path, + protocol_csv_path, protocol_csv_path] + tables=[lab.Lab(), lab.Location(), + lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), + lab.Protocol(), lab.ProtocolType()] - # -------------- Insert new "Protocol" -------------- - with open(protocol_csv_path, newline= '') as f: - input_prots = list(csv.DictReader(f, delimiter=',')) - print(f'\n---- Insert {len(input_prots)} entry(s) into protocol tables ----') - lab.Protocol.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True) - lab.ProtocolType.insert(input_prots, skip_duplicates=True, ignore_extra_fields=True) + ingest_general(csvs, tables, skip_duplicates=skip_duplicates) -def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'): - # -------------- Insert new "Subject" -------------- - with open(subject_csv_path, newline= '') as f: - input_subjects = list(csv.DictReader(f, delimiter=',')) - print(f'\n---- Insert {len(input_subjects)} entry(s) into subject tables ----') - subject.Subject.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) - subject.SubjectDeath.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) - subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=True, ignore_extra_fields=True) - ## Skipped allele info +def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv', + skip_duplicates=True): + """ + Inserts data from a subject csv into corresponding subject schema tables + By default, uses data from workflow_session/user_data/animal/ + :param subject_csv_path: relative path of subject csv + :param skip_duplicates=True: datajoint insert function param + """ + csvs = [subject_csv_path, subject_csv_path, subject_csv_path] + tables=[subject.Subject(),subject.SubjectDeath(), + subject.SubjectCullMethod()] -def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'): - with open(session_csv_path, newline= '') as f: - input_sessions = list(csv.DictReader(f, delimiter=',')) + ingest_general(csvs, tables, skip_duplicates=skip_duplicates) - print(f'\n---- Insert {len(input_sessions)} entry(s) into session.Session ----') - session.Session.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) - session.SessionDirectory.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) + # TODO: add allele and genotyping data - ## Type Error: 'NoneType' object is not iterable - # session.SessionNote.insert(input_sessions, skip_duplicates=True, ignore_extra_fields=True) +def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', + skip_duplicates=True): + """ + Inserts data from a sessions csv into corresponding session schema tables + By default, uses data from workflow_session/user_data/session/ + :param session_csv_path: relative path of subject csv + :param skip_duplicates=True: datajoint insert function param + """ + csvs = [session_csv_path,session_csv_path,session_csv_path] + tables=[session.Session(),session.SessionDirectory(),session.SessionNote()] + ingest_general(csvs, tables, skip_duplicates=skip_duplicates) if __name__ == '__main__': ingest_lab() From c73dcf670615e520323e8f40b57a6d386ee25cd9 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 23 Dec 2021 16:10:30 -0600 Subject: [PATCH 10/46] coverage tests: ingest & pipeline --- CHANGELOG.md | 22 +++++ setup.py | 4 +- tests/__init__.py | 159 ++++++++++++++++++++---------- tests/test_export.py | 11 +++ tests/test_ingest.py | 40 +++++--- tests/test_pipeline_generation.py | 22 ++--- tests/test_populate.py | 11 --- user_data/lab/projusers.csv | 5 + user_data/lab/users.csv | 6 +- workflow_session/__init__.py | 4 +- workflow_session/ingest.py | 11 ++- 11 files changed, 203 insertions(+), 92 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 tests/test_export.py delete mode 100644 tests/test_populate.py create mode 100644 user_data/lab/projusers.csv diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..abad20e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention. + + +## [unreleased: 0.0.1] +### Added ++ Change to include `element-session` ++ Add export notebook + + +## [0.0.0b1] - 2021-03-24 +### Added ++ First beta release + + +## [0.0.0a1] - 2021-03-18 +### Added ++ Added notebooks + +[0.0.0b1]: https://github.com/datajoint/workflow-animal/tree/f6d6a3353aae83ca13ff9fcc536017eb34c18f90 +[0.0.0a1]: https://github.com/datajoint/workflow-animal/tree/0d3d3c970056ff4c243d17cf4f738f48268d80ad diff --git a/setup.py b/setup.py index edf9b88..924014b 100644 --- a/setup.py +++ b/setup.py @@ -20,9 +20,9 @@ setup( name='workflow-session', version='0.0.1', - description="DataJoint Elements for Animal Management", + description="DataJoint Elements for Lab, Animal and Session Management", long_description=long_description, - author='DataJoint NEURO', + author='DataJoint', author_email='info@vathes.com', license='MIT', url='https://github.com/datajoint/workflow-session', diff --git a/tests/__init__.py b/tests/__init__.py index a83b198..0d5daae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,12 @@ -# dependencies: pip install pytest pytest-cov -# run all tests: pytest -sv --cov-report term-missing --cov=workflow-array-ephys -p no:warnings tests/ -# run one test, debug: pytest [above options] --pdb tests/tests_name.py -k function_name +''' +fresh docker: + docker run --name wf-sess -p 3306:3306 -e MYSQL_ROOT_PASSWORD=tutorial datajoint/mysql +dependencies: pip install pytest pytest-cov +run all tests: + pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings tests/ +run one test, debug: + pytest [above options] --pdb tests/tests_name.py -k function_name +''' import os import pytest @@ -10,8 +16,6 @@ import datajoint as dj import workflow_session -from workflow_session.paths import get_root_data_dir -import element_data_loader.utils # ------------------- SOME CONSTANTS ------------------- @@ -31,27 +35,31 @@ def dj_config(): dj.config['safemode'] = False dj.config['custom'] = { 'database.prefix': (os.environ.get('DATABASE_PREFIX') - or dj.config['custom']['database.prefix']), - 'root_data_dir': (os.environ.get('ROOT_DATA_DIR') - or dj.config['custom']['root_data_dir']) + or dj.config['custom']['database.prefix'])#, + # 'root_data_dir': (os.environ.get('ROOT_DATA_DIR') + # or dj.config['custom']['root_data_dir']) } return - @pytest.fixture def pipeline(): + """ Loads workflow_session.pipeline lab, session, subject""" from workflow_session import pipeline - yield {'subject': pipeline.subject, - 'lab': pipeline.lab, - 'session': pipeline.session, - 'get_root_data_dir': pipeline.get_root_data_dir} + yield {'subject': pipeline.subject, + 'session': pipeline.session, + 'lab': pipeline.lab, + #'get_root_data_dir': pipeline.get_root_data_dir + } if _tear_down: pipeline.subject.Subject.delete() + pipeline.session.Session.delete() + pipeline.lab.Lab.delete() @pytest.fixture def lab_csv(): + """ Create a 'labs.csv' file""" input_lab = pd.DataFrame(columns=['lab', 'lab_name','institution', 'address','time_zone','location', 'location_description']) @@ -65,12 +73,15 @@ def lab_csv(): 'fictional campus dedicated to imaginary experiments.'] lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') input_lab.to_csv(lab_csv_path) # write csv file - yield input_lab, lab_csv_path - lab_csv_path.unlink() # delete csv file after use + yield input_lab, lab_csv_path # return result + lab_csv_path.unlink() # delete csv file after use @pytest.fixture def lab_proj_csv(): - input_lab_proj = pd.DataFrame(columns=['project','project_description','repositoryurl','repositoryname','pharmacology','viruses','slices','stimulus','surgery','codeurl']) + """ Create a 'projects.csv' file""" + input_lab_proj = pd.DataFrame(columns=['project','project_description','repositoryurl', + 'repositoryname','pharmacology','viruses','slices', + 'stimulus','surgery','codeurl']) input_lab_proj.project = ['ProjA','ProjB'] input_lab_proj.project_description = ['Example project to populate element-lab', 'Other example project to populate element-lab'] @@ -86,41 +97,90 @@ def lab_proj_csv(): input_lab_proj.codeurl = ['https://github.com/datajoint/element-lab/tree/main/element_lab', 'https://github.com/datajoint/element-session/tree/main/element_session'] lab_proj_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') - input_lab_proj.to_csv(lab_proj_csv_path) # write csv file - yield input_lab_proj, lab_proj_csv_path - lab_proj_csv_path.unlink() + input_lab_proj.to_csv(lab_proj_csv_path) # write csv file + yield input_lab_proj, lab_proj_csv_path # return result + lab_proj_csv_path.unlink() # delete csv file after use + +@pytest.fixture +def lab_projusers_csv(): + """ Create a 'projusers.csv' file""" + input_lab_projusers = pd.DataFrame(columns=['user','project']) + input_lab_projusers.user = ['Sherlock','Sherlock','Watson','Dr. Candace Pert'] + input_lab_projusers.project = ['ProjA','ProjB','ProjB','ProjA'] + lab_projusers_csv_path = pathlib.Path('./tests/user_data/lab/projusers.csv') + input_lab_projusers.to_csv(lab_projusers_csv_path) # write csv file + yield input_lab_projusers, lab_projusers_csv_path # return result + lab_projusers_csv_path.unlink() # delete csv file after use @pytest.fixture def lab_pubs_csv(): + """ Create a 'publications.csv' file""" input_lab_pubs = pd.DataFrame(columns=['project','publication']) input_lab_pubs.project = ['ProjA','ProjA'] input_lab_pubs.publication = ['arXiv:1807.11104','arXiv:1807.11104v1'] lab_pubs_csv_path = pathlib.Path('./tests/user_data/lab/publications.csv') - input_lab_X.to_csv(lab_X_csv_path) # write csv file - yield input_lab_pubs, lab_pubs_csv_path - lab_pubs_csv_path.unlink() + input_lab_pubs.to_csv(lab_pubs_csv_path) # write csv file + yield input_lab_pubs, lab_pubs_csv_path # return result + lab_pubs_csv_path.unlink() # delete csv file after use @pytest.fixture def lab_keyw_csv(): + """ Create a 'keywords.csv' file""" input_lab_keyw = pd.DataFrame(columns=['project','keyword']) input_lab_keyw.project = ['ProjA','ProjA','ProjB'] input_lab_keyw.keyword = ['Study','Example','Alternate'] lab_keyw_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') - input_lab_keyw.to_csv(lab_keyw_csv_path) # write csv file - yield input_lab_keyw, lab_keyw_csv_path - lab_keyw_csv_path.unlink() + input_lab_keyw.to_csv(lab_keyw_csv_path) # write csv file + yield input_lab_keyw, lab_keyw_csv_path # return result + lab_keyw_csv_path.unlink() # delete csv file after use @pytest.fixture def lab_prot_csv(): + """ Create a 'protocols.csv' file""" input_lab_prot = pd.DataFrame(columns=['protocol','protocol_type','protocol_description']) input_lab_prot.protocol = ['ProtA','ProtB'] input_lab_prot.protocol_type = ['IRB expedited review','Alternative Method'] input_lab_prot.protocol_description = ['Protocol for managing data ingestion','Limited protocol for piloting only'] lab_prot_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') - input_lab_prot.to_csv(lab_prot_csv_path) # write csv file - yield input_lab_prot, lab_prot_csv_path - lab_prot_csv_path.unlink() + input_lab_prot.to_csv(lab_prot_csv_path) # write csv file + yield input_lab_prot, lab_prot_csv_path # return result + lab_prot_csv_path.unlink() # delete csv file after use + +@pytest.fixture +def lab_user_csv(): + """ Create a 'users.csv' file""" + input_lab_user = pd.DataFrame(columns=['lab','user','user_role','user_email','user_cellphone']) + input_lab_user.lab = ['LabA','LabA','LabB'] + input_lab_user.user = ['Sherlock','Watson','Dr. Candace Pert'] + input_lab_user.user_role = ['PI','Dr','PI'] + input_lab_user.user_email = ['Sherlock@BakerSt.com','DrWatson@BakerSt.com','Pert@gmail.com'] + input_lab_user.user_cellphone = ['+44 20 7946 0344','+44 73 8389 1763','+44 74 4046 5899'] + lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') + input_lab_user.to_csv(lab_user_csv_path) # write csv file + yield input_lab_user, lab_user_csv_path # return result + lab_user_csv_path.unlink() # delete csv file after use + +@pytest.fixture +def ingest_lab(pipeline, lab_csv, lab_proj_csv, lab_pubs_csv, lab_keyw_csv, + lab_prot_csv, lab_user_csv, lab_projusers_csv): + """ From workflow_session ingest.py, import ingest_lab, run """ + from workflow_session.ingest import ingest_lab + _, lab_csv_path = lab_csv + _, lab_proj_csv_path = lab_proj_csv + _, lab_pubs_csv_path = lab_pubs_csv + _, lab_keyw_csv_path = lab_keyw_csv + _, lab_prot_csv_path = lab_prot_csv + _, lab_projusers_csv_path = lab_projusers_csv + ingest_lab(lab_csv_path=lab_csv_path, + project_csv_path=lab_proj_csv_path, + pubs_csv_path=lab_pubs_csv_path, + keyw_csv_path=lab_keyw_csv_path, + protocol_csv_path=lab_prot_csv_path, + projusers_csv_path=lab_projusers_csv_path + ) + return +# Subject data and ingestion @pytest.fixture def subjects_csv(): """ Create a 'subjects.csv' file""" @@ -132,48 +192,47 @@ def subjects_csv(): input_subjects.sex = ['F', 'M'] input_subjects.subject_birth_date = ['2020-01-01 00:00:01', '2020-01-01 00:00:01'] input_subjects.subject_description = ['rich', 'manuel'] - input_sessions.death_date= ['2020-10-02 00:00:01', '2020-10-03 00:00:01'] - input_sessions.cull_method = ['natural causes', 'natural causes'] + input_subjects.death_date= ['2020-10-02 00:00:01', '2020-10-03 00:00:01'] + input_subjects.cull_method = ['natural causes', 'natural causes'] input_subjects = input_subjects.set_index('subject') - subjects_csv_path = pathlib.Path('./tests/user_data/animal/subjects.csv') - input_subjects.to_csv(subjects_csv_path) # write csv file - - yield input_subjects, subjects_csv_path - - subjects_csv_path.unlink() # delete csv file after use + input_subjects.to_csv(subjects_csv_path) # write csv file + yield input_subjects, subjects_csv_path # return result + subjects_csv_path.unlink() # delete csv file after use @pytest.fixture def ingest_subjects(pipeline, subjects_csv): + """From workflow_session ingest.py, import ingest_subjects, run""" from workflow_session.ingest import ingest_subjects _, subjects_csv_path = subjects_csv ingest_subjects(subject_csv_path=subjects_csv_path) return +# Session data and ingestion @pytest.fixture -def sessions_csv(test_data): +def sessions_csv(): """ Create a 'sessions.csv' file""" - input_sessions = pd.DataFrame(columns=['subject', 'session_dir']) - input_sessions.subject = ['subject1', 'subject2', 'subject2', - 'subject3', 'subject4', 'subject5', - 'subject6'] - input_sessions.session_dir = sessions_dirs + input_sessions = pd.DataFrame(columns=['subject', 'session_datetime', + 'session_dir', 'session_note']) + input_sessions.subject = ['subject5','subject6'] + input_sessions.session_datetime = ['2020-04-15 11:16:38','2021-06-02 14:04:22'] + input_sessions.session_dir = ['/subject5/session1','/subject6/session1'] + input_sessions.session_note = ['Successful data collection, no notes', + 'Ambient temp abnormally low'] input_sessions = input_sessions.set_index('subject') - - sessions_csv_path = pathlib.Path('./tests/user_data/sessions.csv') - input_sessions.to_csv(sessions_csv_path) # write csv file - - yield input_sessions, sessions_csv_path - - sessions_csv_path.unlink() # delete csv file after use + sessions_csv_path = pathlib.Path('./tests/user_data/session/sessions.csv') + input_sessions.to_csv(sessions_csv_path) # write csv file + yield input_sessions, sessions_csv_path # return result + sessions_csv_path.unlink() # delete csv file after use @pytest.fixture def ingest_sessions(ingest_subjects, sessions_csv): + """From workflow_session ingest.py, import ingest_sessions, run""" from workflow_session.ingest import ingest_sessions _, sessions_csv_path = sessions_csv - ingest_sessions(sessions_csv_path) + ingest_sessions(session_csv_path=sessions_csv_path) return diff --git a/tests/test_export.py b/tests/test_export.py new file mode 100644 index 0000000..942e7df --- /dev/null +++ b/tests/test_export.py @@ -0,0 +1,11 @@ +import workflow_session +from . import (dj_config, pipeline, + lab_csv, ingest_lab, + subjects_csv, ingest_subjects, + sessions_csv, ingest_sessions) + + +def test_nwb_export(): + # Ben to add integration? + # Tests with and without lab/animal elements? + pass diff --git a/tests/test_ingest.py b/tests/test_ingest.py index e333d55..731d60a 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -1,31 +1,47 @@ import sys import pathlib -from . import (dj_config, pipeline, test_data, +import workflow_session +from . import (dj_config, pipeline, + lab_csv, lab_proj_csv, lab_projusers_csv, + lab_pubs_csv, lab_keyw_csv, lab_prot_csv, + lab_user_csv, ingest_lab, subjects_csv, ingest_subjects, - sessions_csv, ingest_sessions, - testdata_paths) + sessions_csv, ingest_sessions) +def test_ingest_lab(pipeline,ingest_lab): + """Check length of various lab schema tables""" + lab = pipeline['lab'] + assert len(lab.Lab()) == 2 + assert len(lab.LabMembership()) == 3 + assert len(lab.User()) == 3 + assert len(lab.UserRole()) == 2 + assert len(lab.Location()) == 2 + assert len(lab.Project()) == 2 + assert len(lab.ProjectUser()) == 4 + assert len(lab.Protocol()) == 2 + assert len(lab.ProtocolType()) == 2 + + ## Does not have example data: + # assert len(lab.Source()) == 0 + def test_ingest_subjects(pipeline, ingest_subjects): """Check length of subject.Subject""" subject = pipeline['subject'] - assert len(subject.Subject()) == 6 + assert len(subject.Subject()) == 2 + ## Does not have example data: + # assert len(genotyping.Sequence()) == 0 def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): - ephys = pipeline['ephys'] - probe = pipeline['probe'] + """Check length/contents of Session.SessionDirectory""" session = pipeline['session'] - get_ephys_root_data_dir = pipeline['get_ephys_root_data_dir'] - assert len(session.Session()) == 7 + assert len(session.Session()) == 2 sessions, _ = sessions_csv sess = sessions.iloc[0] assert (session.SessionDirectory - & {'subject': sess.name}).fetch1('session_dir') == sess.session_dir - -def test_ingest_lab(): - pass \ No newline at end of file + & {'subject': sess.name}).fetch1('session_dir') == sess.session_dir \ No newline at end of file diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index 9837a68..a6b7288 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -1,18 +1,18 @@ +import workflow_session from . import dj_config, pipeline def test_generate_pipeline(pipeline): - subject = pipeline['subject'] - ephys = pipeline['ephys'] - probe = pipeline['probe'] session = pipeline['session'] + subject = pipeline['subject'] + lab = pipeline['lab'] - subject_tbl, *_ = session.Session.parents(as_objects=True) - - # test elements connection from lab, subject to Session - assert subject_tbl.full_table_name == subject.Subject.full_table_name + # test connection from lab to lab/subject + lab_membership, loc_tbl, subjlab_tbl = lab.Lab.children(as_objects=True) + # assert lab_tbl.full_table_name == lab.Lab.full_table_name + assert loc_tbl.full_table_name == lab.Location.full_table_name + assert subjlab_tbl.full_table_name == subject.Subject.Lab.full_table_name - # test elements connection from Session to probe, ephys - session_tbl, probe_tbl = ephys.ProbeInsertion.parents(as_objects=True) - assert session_tbl.full_table_name == session.Session.full_table_name - assert probe_tbl.full_table_name == probe.Probe.full_table_name + # test connection from lab to lab/subject + subject_tbl, *_ = session.Session.parents(as_objects=True) + assert subject_tbl.full_table_name == subject.Subject.full_table_name \ No newline at end of file diff --git a/tests/test_populate.py b/tests/test_populate.py deleted file mode 100644 index f2b9aa8..0000000 --- a/tests/test_populate.py +++ /dev/null @@ -1,11 +0,0 @@ -import numpy as np - -from . import (dj_config, pipeline, test_data, - subjects_csv, ingest_subjects, - sessions_csv, ingest_sessions) - - -def test_nwb_export(): - # test with and without subj insertion - # with and without modules active? - pass diff --git a/user_data/lab/projusers.csv b/user_data/lab/projusers.csv new file mode 100644 index 0000000..332ff90 --- /dev/null +++ b/user_data/lab/projusers.csv @@ -0,0 +1,5 @@ +user,project +Sherlock,ProjA +Sherlock,ProjB +Watson,ProjB +Dr. Candace Pert,ProjA \ No newline at end of file diff --git a/user_data/lab/users.csv b/user_data/lab/users.csv index 8092b42..29d0ed5 100644 --- a/user_data/lab/users.csv +++ b/user_data/lab/users.csv @@ -1,2 +1,4 @@ -user, user_role, user_email, user_cellphone -Sherlock, PI, Sherlock@BakerSt.com, +44 20 7946 0344 \ No newline at end of file +lab,user,user_role,user_email,user_cellphone +LabA,Sherlock,PI,Sherlock@BakerSt.com,+44 20 7946 0344 +LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763 +LabB,Dr. Candace Pert,PI,Pert@gmail.com,+44 74 4046 5899 \ No newline at end of file diff --git a/workflow_session/__init__.py b/workflow_session/__init__.py index ae798f2..aceae16 100644 --- a/workflow_session/__init__.py +++ b/workflow_session/__init__.py @@ -1,5 +1,5 @@ -__author__ = "DataJoint NEURO" -__date__ = "March 18, 2021" +__author__ = "DataJoint" +__date__ = "December, 2021" __version__ = "0.0.1" __all__ = ['__author__', '__version__', '__date__'] diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 8b0c189..751b88a 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -33,6 +33,8 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', pubs_csv_path='./user_data/lab/publications.csv', keyw_csv_path='./user_data/lab/keywords.csv', protocol_csv_path='./user_data/lab/protocols.csv', + users_csv_path='./user_data/lab/users.csv', + projusers_csv_path='./user_data/lab/projusers.csv', skip_duplicates=True): """ Inserts data from a series of csvs into their corresponding lab schema tables. @@ -42,16 +44,21 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', :param pubs_csv_path: relative path of publication csv :param keyw_csv_path: relative path of keyword csv :param protocol_csv_path: relative path of protocol csv + :param users_csv_path: relative path of users csv :param skip_duplicates=True: datajoint insert function param """ # List with repeats for when mult dj.tables fed by same CSV csvs = [lab_csv_path, lab_csv_path, project_csv_path, pubs_csv_path, keyw_csv_path, - protocol_csv_path, protocol_csv_path] + protocol_csv_path, protocol_csv_path, + users_csv_path,users_csv_path,users_csv_path, + projusers_csv_path] tables=[lab.Lab(), lab.Location(), lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), - lab.Protocol(), lab.ProtocolType()] + lab.ProtocolType(), lab.Protocol(), + lab.UserRole(), lab.User(), lab.LabMembership(), + lab.ProjectUser()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) From fb0f7b12196ef1239edb5bb475daaf6b740fa3ea Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Thu, 23 Dec 2021 16:19:18 -0600 Subject: [PATCH 11/46] remove pynwb version fix --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eeb25c6..b26b0ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,4 @@ element-lab element-animal element-session ipykernel -pynwb==1.4.0 \ No newline at end of file +pynwb From c76a6ddf4a4fd8aaeb3ecd916a7b6bd5fe88c35a Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Thu, 23 Dec 2021 16:20:01 -0600 Subject: [PATCH 12/46] Remove notes file --- temp_test.ipynb | 519 ------------------------------------------------ 1 file changed, 519 deletions(-) delete mode 100644 temp_test.ipynb diff --git a/temp_test.ipynb b/temp_test.ipynb deleted file mode 100644 index 6aff50c..0000000 --- a/temp_test.ipynb +++ /dev/null @@ -1,519 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3f1d9665-2983-425c-b4a6-8e6d45f1ad34", - "metadata": {}, - "source": [ - "## Initial Dev" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "f13c2734-23db-435f-bc9e-68064cdcc82b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting root@localhost:3306\n" - ] - } - ], - "source": [ - "import datajoint as dj\n", - "dj.conn()\n", - "import csv" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cb32b7a1-37b4-45c1-8d03-02c8bbd09b04", - "metadata": {}, - "outputs": [], - "source": [ - "from workflow_session.pipeline import lab, subject, session\n", - "from workflow_session.ingest import *" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "e94beab4-02f3-464d-989f-24651a8bef8a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "---- Insert 2 entry(s) into lab tables ----\n", - "\n", - "---- Insert 2 entry(s) into project table ----\n", - "\n", - "---- Insert entry(s) into publication/keyword tables ----\n", - "\n", - "---- Insert 2 entry(s) into protocol tables ----\n", - "\n", - "---- Insert 2 entry(s) into subject tables ----\n", - "\n", - "---- Insert 2 entry(s) into session.Session ----\n" - ] - } - ], - "source": [ - "ingest_lab(); ingest_subjects();ingest_sessions()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e306d9dc-ce9a-4485-b8da-eea7d852e5aa", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "*lab lab_name institution address time_zone \n", - "+------+ +------------+ +------------+ +------------+ +-----------+\n", - "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", - " (Total: 1)\n", - "\n", - "*project project_descri repositoryurl repositoryname pharmacology viruses slices stimulus surgery \n", - "+---------+ +------------+ +------------+ +------------+ +------------+ +---------+ +--------+ +------------+ +---------+\n", - "ProjA Example projec https://github element-lab videos generat \n", - " (Total: 1)\n", - "\n", - "*protocol protocol_type protocol_descr\n", - "+----------+ +------------+ +------------+\n", - "ProtA IRB expedited Protocol for m\n", - " (Total: 1)\n", - "\n" - ] - } - ], - "source": [ - "print(lab.Lab & 'lab=\"LabA\"')\n", - "print(lab.Project & 'project=\"ProjA\"')\n", - "print(lab.Protocol() & 'protocol=\"ProtA\"')\n", - "\n", - "lab_info = (lab.Lab & 'lab=\"LabA\"').fetch1()\n", - "proj_info = (lab.Project & 'project=\"ProjA\"').fetch1()\n", - "prot_info = (lab.Protocol() & 'protocol=\"ProtA\"').fetch1()" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "fd053496-eec1-4035-9369-fdf96e53fa95", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", - " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" - ] - }, - { - "data": { - "text/plain": [ - "root pynwb.file.NWBFile at 0x140690194467040\n", - "Fields:\n", - " file_create_date: [datetime.datetime(2021, 12, 1, 14, 22, 4, 992693, tzinfo=tzlocal())]\n", - " identifier: subject5_20200415_111638\n", - " institution: Example Uni\n", - " session_description: Successful data collection, no notes\n", - " session_start_time: 2020-04-15 11:16:38-05:00\n", - " timestamps_reference_time: 2020-04-15 11:16:38-05:00" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pynwb\n", - "from element_session.export import *\n", - "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", - "mylab_key = (lab.Lab & 'lab=\"LabA\"').fetch1('KEY')\n", - "myproj_key= (lab.Project & 'project=\"ProjA\"').fetch1('KEY')\n", - "myprot_key= (lab.Protocol() & 'protocol=\"ProtA\"').fetch1('KEY')\n", - "session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "a3e1f490-7510-4f08-af66-c106f640928b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "root pynwb.file.NWBFile at 0x140337549119696\n", - "Fields:\n", - " file_create_date: [datetime.datetime(2021, 12, 1, 14, 21, 26, 638467, tzinfo=tzlocal())]\n", - " identifier: subject5_20200415_111638\n", - " institution: Example Uni\n", - " protocol: ProtA\n", - " session_description: Successful data collection, no notes\n", - " session_start_time: 2020-04-15 11:16:38-05:00\n", - " timestamps_reference_time: 2020-04-15 11:16:38-05:00" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "mynwbfile" - ] - }, - { - "cell_type": "code", - "execution_count": 100, - "id": "d982c732-8a8f-435d-ab16-aa37a0e99904", - "metadata": {}, - "outputs": [], - "source": [ - "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", - "session_identifier = {}\n", - "for k, v in session_key.items():\n", - " session_identifier[k] = v.strftime('%Y%m%d_%H%M%S') if isinstance(v, datetime) else v\n", - "\n", - "session_info = (session.Session & session_key).join(session.SessionNote, left=True).fetch1()\n", - "\n", - "def mytuple():\n", - " identifier='_'.join(session_identifier.values()),\n", - " session_description=session_info['session_note'] if session_info['session_note'] else '',\n", - " session_start_time=session_info['session_datetime']\n", - " return identifier,session_description,session_start_time\n", - "info=dict(identifier='_'.join(session_identifier.values()),\n", - " session_description='Note',\n", - " session_start_time=session_info['session_datetime'],\n", - " institution='')\n", - "info={k: v for k, v in info.items() if v} #drop empty\n", - "asstring = ','.join('='.join((str(key),val)) for (key,val) in info.items())" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "ef8b5fb3-8b19-403c-a954-150c1ec8cf41", - "metadata": {}, - "outputs": [], - "source": [ - "a=None;b=None;c=None\n", - "if [x for x in (a, b,c) if x is not None]: print('g')" - ] - }, - { - "cell_type": "markdown", - "id": "1764858b-2470-4b81-b02a-dd0121d46210", - "metadata": {}, - "source": [ - "## Orig ingest, before generalize" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "e5e9d016-f2a7-4db5-9d8f-c4a6c06f693c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "---- Inserting 0 entry(s) into lab tables ----\n", - "\n", - "---- Inserting 0 entry(s) into project table ----\n", - "\n", - "---- Inserting 0 entry(s) into publication/keyword tables ----\n", - "\n", - "---- Inserting 0 entry(s) into protocol tables ----\n", - "\n", - "---- Inserting 0 entry(s) into subject tables ----\n", - "\n", - "---- Inserting 0 entry(s) into session.Session ----\n" - ] - } - ], - "source": [ - "def ingest_lab(lab_csv_path='./user_data/lab/labs.csv',\n", - " project_csv_path='./user_data/lab/projects.csv',\n", - " pubs_csv_path='./user_data/lab/publications.csv',\n", - " keyw_csv_path='./user_data/lab/keywords.csv',\n", - " protocol_csv_path='./user_data/lab/protocols.csv',\n", - " skip_duplicates=True, ignore_extra_fields=True):\n", - " # -------------- Insert new \"Lab\" --------------\n", - " with open(lab_csv_path, newline= '') as f:\n", - " input_labs = list(csv.DictReader(f, delimiter=','))\n", - "\n", - " prev_len = len(lab.Lab())\n", - " lab.Lab.insert(input_labs, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " lab.Location.insert(input_labs, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " insert_len = len(lab.Lab()) - prev_len\n", - " \n", - " print(f'\\n---- Inserting {insert_len} entry(s) into lab tables ----')\n", - "\n", - " # -------------- Insert new \"Project\" --------------\n", - " with open(project_csv_path, newline= '') as f:\n", - " input_projs = list(csv.DictReader(f, delimiter=','))\n", - " \n", - " prev_len = len(lab.Project())\n", - " lab.Project.insert(input_projs, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " insert_len = len(lab.Project()) - prev_len\n", - " print(f'\\n---- Inserting {insert_len} entry(s) into project table ----')\n", - "\n", - " # -------------- Insert publications + keywords --------------\n", - " with open(pubs_csv_path, newline= '') as f:\n", - " input_pubs = list(csv.DictReader(f, delimiter=','))\n", - " with open(keyw_csv_path, newline= '') as f:\n", - " input_keyw = list(csv.DictReader(f, delimiter=','))\n", - " \n", - " prev_len = len(lab.Project.Publication()) + len(lab.Project.Keywords())\n", - " lab.Project.Publication.insert(input_pubs, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " lab.Project.Keywords.insert(input_keyw, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " insert_len = len(lab.Project.Publication()) + len(lab.Project.Keywords()) - prev_len\n", - " \n", - " print(f'\\n---- Inserting {insert_len} entry(s) into publication/keyword tables ----')\n", - "\n", - " # -------------- Insert new \"Protocol\" --------------\n", - " with open(protocol_csv_path, newline= '') as f:\n", - " input_prots = list(csv.DictReader(f, delimiter=','))\n", - " prev_len = len(lab.Protocol())\n", - " lab.Protocol.insert(input_prots, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " lab.ProtocolType.insert(input_prots, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " insert_len = len(lab.Protocol()) - prev_len\n", - " print(f'\\n---- Inserting {insert_len} entry(s) into protocol tables ----')\n", - "\n", - "def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv'):\n", - " # -------------- Insert new \"Subject\" --------------\n", - " with open(subject_csv_path, newline= '') as f:\n", - " input_subjects = list(csv.DictReader(f, delimiter=','))\n", - " print(f'\\n---- Inserting {insert_len} entry(s) into subject tables ----')\n", - " subject.Subject.insert(input_subjects, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " subject.SubjectDeath.insert(input_subjects, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " subject.SubjectCullMethod.insert(input_subjects, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " ## Skipped allele info\n", - "\n", - "def ingest_sessions(session_csv_path='./user_data/session/sessions.csv'):\n", - " with open(session_csv_path, newline= '') as f:\n", - " input_sessions = list(csv.DictReader(f, delimiter=','))\n", - "\n", - " print(f'\\n---- Inserting {insert_len} entry(s) into session.Session ----')\n", - " session.Session.insert(input_sessions, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - " session.SessionDirectory.insert(input_sessions, skip_duplicates=skip_duplicates,\n", - " ignore_extra_fields=ignore_extra_fields)\n", - "\n", - " ## Type Error: 'NoneType' object is not iterable\n", - " # session.SessionNote.insert(input_sessions, skip_duplicates=skip_duplicates,\n", - " # ignore_extra_fields=ignore_extra_fields)\n", - "\n", - "\n", - "if __name__ == '__main__':\n", - " ingest_lab()\n", - " ingest_subjects()\n", - " ingest_sessions()" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "91347a4f-9024-4a33-a1b1-42d903d9b132", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/Volumes/GoogleDrive/My Drive/NWB/workflow-session'" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pwd" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "08b9898a-d07f-4baa-8bf9-6344fa0a7a82", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting root@localhost:3306\n" - ] - } - ], - "source": [ - "import datajoint as dj; dj.conn()\n", - "import pathlib\n", - "import csv\n", - "import re\n", - "from workflow_session.pipeline import lab, subject, session\n", - "from workflow_session.paths import get_root_data_dir\n", - "import element_data_loader.utils" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6bbc3fb1-01bb-42c9-ac99-83398e49a7a6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "---- Inserting 0 entry(s) into #location ----\n" - ] - }, - { - "ename": "AttributeError", - "evalue": "'dict' object has no attribute 'insert'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_7090/3634625444.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mworkflow_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mingest\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mingest_lab\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m \u001b[0mingest_subjects\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0mingest_sessions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-session/workflow_session/ingest.py\u001b[0m in \u001b[0;36mingest_lab\u001b[0;34m(lab_csv_path, project_csv_path, pubs_csv_path, keyw_csv_path, protocol_csv_path, skip_duplicates)\u001b[0m\n\u001b[1;32m 55\u001b[0m [lab.Protocol(), lab.ProtocolType()]]\n\u001b[1;32m 56\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 57\u001b[0;31m \u001b[0mingest_general\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcsvs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtables\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_duplicates\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mskip_duplicates\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 58\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 59\u001b[0m def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv',\n", - "\u001b[0;32m/Volumes/GoogleDrive/My Drive/NWB/workflow-session/workflow_session/ingest.py\u001b[0m in \u001b[0;36mingest_general\u001b[0;34m(csvs, tables, skip_duplicates)\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mtable\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# loop multiple tables\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0mprev_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# measure prev length\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m t.insert(data, skip_duplicates=skip_duplicates, # insert\n\u001b[0m\u001b[1;32m 28\u001b[0m ignore_extra_fields=True) # must be true for csvs w/mult tables\n\u001b[1;32m 29\u001b[0m \u001b[0minsert_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mprev_len\u001b[0m \u001b[0;31m# report length change\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: 'dict' object has no attribute 'insert'" - ] - } - ], - "source": [ - "from workflow_session.ingest import *\n", - "ingest_lab(); ingest_subjects();ingest_sessions()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3b2f2ff2-c6b4-489e-94be-b98dbfdfea12", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "lab : varchar(24) # Abbreviated lab name\n", - "---\n", - "lab_name : varchar(255) # full lab name\n", - "institution : varchar(255) \n", - "address : varchar(255) \n", - "time_zone : varchar(32) # UTC offset suggested e.g., UTC+1\n", - "\n" - ] - } - ], - "source": [ - "table=[lab.Lab()]\n", - "for t in table:\n", - " t.describe()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f9fb0145-30ee-46f6-9f2c-bd0945be2f02", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[*lab lab_name institution address time_zone \n", - "+------+ +------------+ +------------+ +------------+ +-----------+\n", - "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", - "LabB The Other Lab Other Uni Oxford OX1 2JD UTC+0 \n", - " (Total: 2)\n", - "]\n" - ] - } - ], - "source": [ - "if not isinstance(lab.Lab(),list): print([lab.Lab()])" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c56a3e3c-76a3-473f-970a-5507dee55d20", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "element_lab.lab.Lab" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "type(lab.Lab())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cfd86c54-b602-46e5-b2ff-b66a1f41a879", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv-nwb", - "language": "python", - "name": "venv-nwb" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 0a30e46feaedea49d07e4009b2ce042acd1b15f0 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 28 Dec 2021 12:16:01 -0600 Subject: [PATCH 13/46] reverting folder name before gitmv; for git comparisons --- {workflow_session => workflow_animal}/__init__.py | 0 {workflow_session => workflow_animal}/ingest.py | 0 {workflow_session => workflow_animal}/paths.py | 0 {workflow_session => workflow_animal}/pipeline.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {workflow_session => workflow_animal}/__init__.py (100%) rename {workflow_session => workflow_animal}/ingest.py (100%) rename {workflow_session => workflow_animal}/paths.py (100%) rename {workflow_session => workflow_animal}/pipeline.py (100%) diff --git a/workflow_session/__init__.py b/workflow_animal/__init__.py similarity index 100% rename from workflow_session/__init__.py rename to workflow_animal/__init__.py diff --git a/workflow_session/ingest.py b/workflow_animal/ingest.py similarity index 100% rename from workflow_session/ingest.py rename to workflow_animal/ingest.py diff --git a/workflow_session/paths.py b/workflow_animal/paths.py similarity index 100% rename from workflow_session/paths.py rename to workflow_animal/paths.py diff --git a/workflow_session/pipeline.py b/workflow_animal/pipeline.py similarity index 100% rename from workflow_session/pipeline.py rename to workflow_animal/pipeline.py From c67678825c729207ee637326662cc80b317aca81 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 28 Dec 2021 12:18:33 -0600 Subject: [PATCH 14/46] folder rename w/gitmv; for git comparisons --- {workflow_animal => workflow_session}/__init__.py | 0 {workflow_animal => workflow_session}/ingest.py | 0 {workflow_animal => workflow_session}/paths.py | 0 {workflow_animal => workflow_session}/pipeline.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {workflow_animal => workflow_session}/__init__.py (100%) rename {workflow_animal => workflow_session}/ingest.py (100%) rename {workflow_animal => workflow_session}/paths.py (100%) rename {workflow_animal => workflow_session}/pipeline.py (100%) diff --git a/workflow_animal/__init__.py b/workflow_session/__init__.py similarity index 100% rename from workflow_animal/__init__.py rename to workflow_session/__init__.py diff --git a/workflow_animal/ingest.py b/workflow_session/ingest.py similarity index 100% rename from workflow_animal/ingest.py rename to workflow_session/ingest.py diff --git a/workflow_animal/paths.py b/workflow_session/paths.py similarity index 100% rename from workflow_animal/paths.py rename to workflow_session/paths.py diff --git a/workflow_animal/pipeline.py b/workflow_session/pipeline.py similarity index 100% rename from workflow_animal/pipeline.py rename to workflow_session/pipeline.py From f02a8d9967d499802c384b1496862c45e90426f1 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 29 Dec 2021 18:08:56 -0600 Subject: [PATCH 15/46] rm numpy/pandas; PEP8 full eng words --- README.md | 2 +- notebooks/2_Explore_Export.ipynb | 12 +- tests/__init__.py | 279 +++++++++--------- tests/test_ingest.py | 46 ++- tests/test_pipeline_generation.py | 12 +- user_data/lab/labs.csv | 4 +- .../lab/{projusers.csv => project_users.csv} | 0 user_data/session/sessions.csv | 4 +- user_data/{animal => subject}/allele.csv | 0 user_data/{animal => subject}/line.csv | 0 user_data/{animal => subject}/source.csv | 0 user_data/{animal => subject}/strain.csv | 0 user_data/{animal => subject}/subjects.csv | 0 workflow_session/ingest.py | 30 +- 14 files changed, 214 insertions(+), 175 deletions(-) rename user_data/lab/{projusers.csv => project_users.csv} (100%) rename user_data/{animal => subject}/allele.csv (100%) rename user_data/{animal => subject}/line.csv (100%) rename user_data/{animal => subject}/source.csv (100%) rename user_data/{animal => subject}/strain.csv (100%) rename user_data/{animal => subject}/subjects.csv (100%) diff --git a/README.md b/README.md index 6654adc..6d2e9ed 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository provides demonstrations for: Setting up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) ## Workflow architecture -The lab and animal management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. +The lab and subject management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. ### element-lab diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index c1a1cf6..75abeb5 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -153,15 +153,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "Help on function elemlab_to_nwb_dict in module element_lab.export.nwb:\n", + "Help on function elementlab_nwb_dict in module element_lab.export.nwb:\n", "\n", - "elemlab_to_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", + "elementlab_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", " Generate a dictionary object containing all relevant lab information used when\n", " generating an NWB file at the session level. All parameters optional.\n", " Use: mynwbfile = NWBfile(identifier=\"your identifier\",\n", " session_description=\"your description\",\n", " session_start_time=session_datetime,\n", - " elemlab_to_nwb_dict(lab_key=key1,project_key=key2,protocol_key=key3))\n", + " elementlab_nwb_dict(lab_key=key1,project_key=key2,protocol_key=key3))\n", " Note: The lab, project and protocol keys should specify one of their respective types.\n", " \n", " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", @@ -173,8 +173,8 @@ } ], "source": [ - "from element_lab.export import elemlab_to_nwb_dict\n", - "help(elemlab_to_nwb_dict)" + "from element_lab.export import elementlab_nwb_dict\n", + "help(elementlab_nwb_dict)" ] }, { @@ -185,7 +185,7 @@ "outputs": [], "source": [ "from pynwb import NWBFile\n", - "lab_info = elemlab_to_nwb_dict(lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)\n", + "lab_info = elementlab_nwb_dict(lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)\n", "sess_info = session_to_nwb_dict(session_key)" ] }, diff --git a/tests/__init__.py b/tests/__init__.py index 0d5daae..785adce 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,9 +10,7 @@ import os import pytest -import pandas as pd import pathlib -import numpy as np import datajoint as dj import workflow_session @@ -25,6 +23,18 @@ test_user_data_dir = pathlib.Path('./tests/user_data') test_user_data_dir.mkdir(exist_ok=True) +# ------------------ GENERAL FUCNTION ------------------ + +def write_csv(content,path): + """ + General function for writing strings to lines in CSV + :param path: pathlib PosixPath + :param content: list of strings, each as row of CSV + """ + with open(path,'w') as f: + for line in content: + f.write(line+'\n') + # ------------------- FIXTURES ------------------- @pytest.fixture(autouse=True) @@ -35,10 +45,7 @@ def dj_config(): dj.config['safemode'] = False dj.config['custom'] = { 'database.prefix': (os.environ.get('DATABASE_PREFIX') - or dj.config['custom']['database.prefix'])#, - # 'root_data_dir': (os.environ.get('ROOT_DATA_DIR') - # or dj.config['custom']['root_data_dir']) - } + or dj.config['custom']['database.prefix'])} return @pytest.fixture @@ -48,9 +55,7 @@ def pipeline(): yield {'subject': pipeline.subject, 'session': pipeline.session, - 'lab': pipeline.lab, - #'get_root_data_dir': pipeline.get_root_data_dir - } + 'lab': pipeline.lab} if _tear_down: pipeline.subject.Subject.delete() @@ -60,153 +65,133 @@ def pipeline(): @pytest.fixture def lab_csv(): """ Create a 'labs.csv' file""" - input_lab = pd.DataFrame(columns=['lab', 'lab_name','institution', - 'address','time_zone','location', - 'location_description']) - input_lab.lab = ['LabA','LabB'] - input_lab.lab_name = ['The Example Lab','The Other Lab'] - input_lab.institution = ['Example Uni','Other Uni'] - input_lab.address = ['221B Baker St,London NW1 6XE,UK','Oxford OX1 2JD, United Kingdom'] - input_lab.time_zone = ['UTC+0','UTC+0'] - input_lab.location = ['Example Building','Other Building'] - input_lab.location_description = ['2nd floor lab dedicated to all fictional experiments.', - 'fictional campus dedicated to imaginary experiments.'] + lab_content = ["lab,lab_name,institution,address,time_zone,location,location_description", + "LabA,The Example Lab,Example Uni,'221B Baker St,London NW1 6XE,UK',UTC+0,Example Building,'2nd floor lab dedicated to all fictional experiments.'", + "LabB,The Other Lab,Other Uni,'Oxford OX1 2JD, United Kingdom',UTC+0,Other Building,'fictional campus dedicated to imaginary experiments.'"] lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') - input_lab.to_csv(lab_csv_path) # write csv file - yield input_lab, lab_csv_path # return result - lab_csv_path.unlink() # delete csv file after use + write_csv(lab_content,lab_csv_path) + + yield lab_content, lab_csv_path + lab_csv_path.unlink() @pytest.fixture -def lab_proj_csv(): +def lab_project_csv(): """ Create a 'projects.csv' file""" - input_lab_proj = pd.DataFrame(columns=['project','project_description','repositoryurl', - 'repositoryname','pharmacology','viruses','slices', - 'stimulus','surgery','codeurl']) - input_lab_proj.project = ['ProjA','ProjB'] - input_lab_proj.project_description = ['Example project to populate element-lab', - 'Other example project to populate element-lab'] - input_lab_proj.repositoryurl = ['https://github.com/datajoint/element-lab/', - 'https://github.com/datajoint/element-session/'] - input_lab_proj.repositoryname = ['element-lab','element-session'] - input_lab_proj.pharmacology = ['Subjects were administered 10ul sedative prior to surgery',''] - input_lab_proj.viruses = ['Exemplarvirus administered 10d before experimental session', - 'Exemplarvirus administered 8d study'] - input_lab_proj.slices = ['',''] - input_lab_proj.stimulus = ['videos generated programmatically see repository',''] - input_lab_proj.surgery = ['Craniotomy performed by session experimenter',''] - input_lab_proj.codeurl = ['https://github.com/datajoint/element-lab/tree/main/element_lab', - 'https://github.com/datajoint/element-session/tree/main/element_session'] - lab_proj_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') - input_lab_proj.to_csv(lab_proj_csv_path) # write csv file - yield input_lab_proj, lab_proj_csv_path # return result - lab_proj_csv_path.unlink() # delete csv file after use + lab_project_content = ["project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl", + "ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,Exemplarvirus administered 10d before experimental session,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab", + "ProjB,Other example project to populate element-lab,https://github.com/datajoint/element-session/,element-session,,Exemplarvirus administered 8d study,,,,https://github.com/datajoint/element-session/tree/main/element_session"] + lab_project_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') + write_csv(lab_project_content,lab_project_csv_path) + + yield lab_project_content, lab_project_csv_path + lab_project_csv_path.unlink() @pytest.fixture -def lab_projusers_csv(): - """ Create a 'projusers.csv' file""" - input_lab_projusers = pd.DataFrame(columns=['user','project']) - input_lab_projusers.user = ['Sherlock','Sherlock','Watson','Dr. Candace Pert'] - input_lab_projusers.project = ['ProjA','ProjB','ProjB','ProjA'] - lab_projusers_csv_path = pathlib.Path('./tests/user_data/lab/projusers.csv') - input_lab_projusers.to_csv(lab_projusers_csv_path) # write csv file - yield input_lab_projusers, lab_projusers_csv_path # return result - lab_projusers_csv_path.unlink() # delete csv file after use +def lab_project_users_csv(): + """ Create a 'project_users.csv' file""" + lab_project_user_content = ["user,project", + "Sherlock,ProjA", + "Sherlock,ProjB", + "Watson,ProjB", + "Dr. Candace Pert,ProjA"] + lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/project_users.csv') + write_csv(lab_project_user_content,lab_project_user_csv_path) + + yield lab_project_user_content, lab_project_user_csv_path + lab_project_user_csv_path.unlink() @pytest.fixture -def lab_pubs_csv(): +def lab_publications_csv(): """ Create a 'publications.csv' file""" - input_lab_pubs = pd.DataFrame(columns=['project','publication']) - input_lab_pubs.project = ['ProjA','ProjA'] - input_lab_pubs.publication = ['arXiv:1807.11104','arXiv:1807.11104v1'] - lab_pubs_csv_path = pathlib.Path('./tests/user_data/lab/publications.csv') - input_lab_pubs.to_csv(lab_pubs_csv_path) # write csv file - yield input_lab_pubs, lab_pubs_csv_path # return result - lab_pubs_csv_path.unlink() # delete csv file after use + lab_publication_content = ["project,publication", + "ProjA,arXiv:1807.11104", + "ProjA,arXiv:1807.11104v1"] + lab_publication_csv_path = pathlib.Path('./tests/user_data/lab/publications.csv') + write_csv(lab_publication_content, lab_publication_csv_path) + + yield lab_publication_content, lab_publication_csv_path + lab_publication_csv_path.unlink() @pytest.fixture -def lab_keyw_csv(): +def lab_keywords_csv(): """ Create a 'keywords.csv' file""" - input_lab_keyw = pd.DataFrame(columns=['project','keyword']) - input_lab_keyw.project = ['ProjA','ProjA','ProjB'] - input_lab_keyw.keyword = ['Study','Example','Alternate'] - lab_keyw_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') - input_lab_keyw.to_csv(lab_keyw_csv_path) # write csv file - yield input_lab_keyw, lab_keyw_csv_path # return result - lab_keyw_csv_path.unlink() # delete csv file after use + lab_keyword_content = ["project,keyword", + "ProjA,Study", + "ProjA,Example", + "ProjB,Alternate"] + lab_keyword_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') + write_csv(lab_keyword_content,lab_keyword_csv_path) + + yield lab_keyword_content, lab_keyword_csv_path + lab_keyword_csv_path.unlink() @pytest.fixture -def lab_prot_csv(): +def lab_protocol_csv(): """ Create a 'protocols.csv' file""" - input_lab_prot = pd.DataFrame(columns=['protocol','protocol_type','protocol_description']) - input_lab_prot.protocol = ['ProtA','ProtB'] - input_lab_prot.protocol_type = ['IRB expedited review','Alternative Method'] - input_lab_prot.protocol_description = ['Protocol for managing data ingestion','Limited protocol for piloting only'] - lab_prot_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') - input_lab_prot.to_csv(lab_prot_csv_path) # write csv file - yield input_lab_prot, lab_prot_csv_path # return result - lab_prot_csv_path.unlink() # delete csv file after use + lab_protocol_content = ["protocol,protocol_type,protocol_description", + "ProtA,IRB expedited review,Protocol for managing data ingestion", + "ProtB,Alternative Method,Limited protocol for piloting only"] + lab_protocol_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') + write_csv(lab_protocol_content,lab_protocol_csv_path) + + yield lab_protocol_content, lab_protocol_csv_path + lab_protocol_csv_path.unlink() @pytest.fixture def lab_user_csv(): """ Create a 'users.csv' file""" - input_lab_user = pd.DataFrame(columns=['lab','user','user_role','user_email','user_cellphone']) - input_lab_user.lab = ['LabA','LabA','LabB'] - input_lab_user.user = ['Sherlock','Watson','Dr. Candace Pert'] - input_lab_user.user_role = ['PI','Dr','PI'] - input_lab_user.user_email = ['Sherlock@BakerSt.com','DrWatson@BakerSt.com','Pert@gmail.com'] - input_lab_user.user_cellphone = ['+44 20 7946 0344','+44 73 8389 1763','+44 74 4046 5899'] + lab_user_content = ["lab,user,user_role,user_email,user_cellphone", + "LabA,Sherlock,PI,Sherlock@BakerSt.com,+44 20 7946 0344", + "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", + "LabB,Dr. Candace Pert,PI,Pert@gmail.com,+44 74 4046 5899"] lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') - input_lab_user.to_csv(lab_user_csv_path) # write csv file - yield input_lab_user, lab_user_csv_path # return result - lab_user_csv_path.unlink() # delete csv file after use + write_csv(lab_user_content,lab_user_csv_path) + + yield lab_user_content, lab_user_csv_path + lab_user_csv_path.unlink() @pytest.fixture -def ingest_lab(pipeline, lab_csv, lab_proj_csv, lab_pubs_csv, lab_keyw_csv, - lab_prot_csv, lab_user_csv, lab_projusers_csv): +def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, + lab_keywords_csv, lab_protocol_csv, lab_user_csv, + lab_project_users_csv): """ From workflow_session ingest.py, import ingest_lab, run """ from workflow_session.ingest import ingest_lab _, lab_csv_path = lab_csv - _, lab_proj_csv_path = lab_proj_csv - _, lab_pubs_csv_path = lab_pubs_csv - _, lab_keyw_csv_path = lab_keyw_csv - _, lab_prot_csv_path = lab_prot_csv - _, lab_projusers_csv_path = lab_projusers_csv + _, lab_project_csv_path = lab_project_csv + _, lab_publication_csv_path = lab_publications_csv + _, lab_keyword_csv_path = lab_keywords_csv + _, lab_protocol_csv_path = lab_protocol_csv + _, lab_user_csv_path = lab_user_csv + _, lab_project_user_csv_path = lab_project_users_csv ingest_lab(lab_csv_path=lab_csv_path, - project_csv_path=lab_proj_csv_path, - pubs_csv_path=lab_pubs_csv_path, - keyw_csv_path=lab_keyw_csv_path, - protocol_csv_path=lab_prot_csv_path, - projusers_csv_path=lab_projusers_csv_path - ) + project_csv_path=lab_project_csv_path, + publication_csv_path=lab_publication_csv_path, + keyword_csv_path=lab_keyword_csv_path, + protocol_csv_path=lab_protocol_csv_path, + users_csv_path=lab_user_csv_path, + project_user_csv_path=lab_project_user_csv_path) return # Subject data and ingestion @pytest.fixture def subjects_csv(): """ Create a 'subjects.csv' file""" - input_subjects = pd.DataFrame(columns=['subject', 'sex', - 'subject_birth_date', - 'subject_description', - 'death_date','cull_method']) - input_subjects.subject = ['subject5', 'subject6'] - input_subjects.sex = ['F', 'M'] - input_subjects.subject_birth_date = ['2020-01-01 00:00:01', '2020-01-01 00:00:01'] - input_subjects.subject_description = ['rich', 'manuel'] - input_subjects.death_date= ['2020-10-02 00:00:01', '2020-10-03 00:00:01'] - input_subjects.cull_method = ['natural causes', 'natural causes'] - input_subjects = input_subjects.set_index('subject') - subjects_csv_path = pathlib.Path('./tests/user_data/animal/subjects.csv') - input_subjects.to_csv(subjects_csv_path) # write csv file - yield input_subjects, subjects_csv_path # return result - subjects_csv_path.unlink() # delete csv file after use + subject_content = ["subject,sex,subject_birth_date,subject_description,death_date,cull_method", + "subject5,F,2020-01-01 00:00:01,rich,2020-10-02 00:00:01,natural causes", + "subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes"] + subject_csv_path = pathlib.Path('./tests/user_data/subject/subjects.csv') + write_csv(subject_content,subject_csv_path) + + yield subject_content, subject_csv_path + subject_csv_path.unlink() @pytest.fixture def ingest_subjects(pipeline, subjects_csv): """From workflow_session ingest.py, import ingest_subjects, run""" from workflow_session.ingest import ingest_subjects - _, subjects_csv_path = subjects_csv - ingest_subjects(subject_csv_path=subjects_csv_path) + _, subject_csv_path = subjects_csv + ingest_subjects(subject_csv_path=subject_csv_path) return @@ -214,25 +199,55 @@ def ingest_subjects(pipeline, subjects_csv): @pytest.fixture def sessions_csv(): """ Create a 'sessions.csv' file""" - input_sessions = pd.DataFrame(columns=['subject', 'session_datetime', - 'session_dir', 'session_note']) - input_sessions.subject = ['subject5','subject6'] - input_sessions.session_datetime = ['2020-04-15 11:16:38','2021-06-02 14:04:22'] - input_sessions.session_dir = ['/subject5/session1','/subject6/session1'] - input_sessions.session_note = ['Successful data collection, no notes', - 'Ambient temp abnormally low'] - input_sessions = input_sessions.set_index('subject') - sessions_csv_path = pathlib.Path('./tests/user_data/session/sessions.csv') - input_sessions.to_csv(sessions_csv_path) # write csv file - yield input_sessions, sessions_csv_path # return result - sessions_csv_path.unlink() # delete csv file after use + session_csv_path = pathlib.Path('./tests/user_data/session/sessions.csv') + session_content = ["subject,session_datetime,session_dir,session_note", + "subject5,2020-04-15 11:16:38,/subject5/session1,'Successful data collection, no notes'", + "subject6,2021-06-02 14:04:22,/subject6/session1,'Ambient temp abnormally low'"] + write_csv(session_content,session_csv_path) + + yield session_content, session_csv_path + session_csv_path.unlink() @pytest.fixture def ingest_sessions(ingest_subjects, sessions_csv): """From workflow_session ingest.py, import ingest_sessions, run""" from workflow_session.ingest import ingest_sessions - _, sessions_csv_path = sessions_csv - ingest_sessions(session_csv_path=sessions_csv_path) + _, session_csv_path = sessions_csv + ingest_sessions(session_csv_path=session_csv_path) return +""" +# FUTURE FIXTURES, pending sharable example data +lab_sources_content=["source, source_name, contact_details, source_description"] +lab_sources_csv_path = pathlib.Path('./tests/user_data/lab/sources.csv') +write_csv(lab_sources_content,lab_sources_csv_path) +yeild lab_sources_content,lab_sources_csv_path +lab_sources_csv_path.unlink() +subject_allele_content=["allele, allele_standard_name,zygosity"] +subject_allele_csv_path = pathlib.Path('./tests/user_data/subject/allele.csv') +write_csv(subject_allele_content,subject_allele_csv_path) +yeild subject_allele_content,subject_allele_csv_path +subject_allele_csv_path.unlink() +subject_genotyping_content=[] +subject_genotyping_csv_path = pathlib.Path('./tests/user_data/subject/genotyping.csv') +write_csv(subject_genotyping_content,subject_genotyping_csv_path) +yeild subject_genotyping_content,subject_genotyping_csv_path +subject_genotyping_csv_path.unlink() +subject_line_content=["line, line_description, target_phenotype, is_active"] +subject_line_csv_path = pathlib.Path('./tests/user_data/subject/line.csv') +write_csv(subject_line_content,subject_line_csv_path) +yeild subject_line_content,subject_line_csv_path +subject_line_csv_path.unlink() +subject_source_content=["allele, source_identifier, source_url, expression_data_url"] +subject_source_csv_path = pathlib.Path('./tests/user_data/subject/source.csv') +write_csv(subject_source_content,subject_source_csv_path) +yeild subject_source_content,subject_source_csv_path +subject_source_csv_path.unlink() +subject_strain_content=["strain, strain_standard_name, strain_desc"] +subject_strain_csv_path = pathlib.Path('./tests/user_data/subject/strain.csv') +write_csv(subject_strain_content,subject_strain_csv_path) +yeild subject_strain_content,subject_strain_csv_path +subject_strain_csv_path.unlink() +subject_subjects_csv_path = pathlib.Path('./tests/user_data/subject/subjects.csv') +""" \ No newline at end of file diff --git a/tests/test_ingest.py b/tests/test_ingest.py index 731d60a..30926c2 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -2,15 +2,16 @@ import pathlib import workflow_session -from . import (dj_config, pipeline, - lab_csv, lab_proj_csv, lab_projusers_csv, - lab_pubs_csv, lab_keyw_csv, lab_prot_csv, - lab_user_csv, ingest_lab, +from . import (dj_config, pipeline, lab_csv, + lab_project_csv, lab_user_csv, lab_publications_csv, + lab_keywords_csv, lab_protocol_csv, lab_user_csv, + lab_project_users_csv, ingest_lab, subjects_csv, ingest_subjects, sessions_csv, ingest_sessions) -def test_ingest_lab(pipeline,ingest_lab): +def test_ingest_lab(pipeline, ingest_lab, + lab_csv, lab_project_csv, lab_protocol_csv): """Check length of various lab schema tables""" lab = pipeline['lab'] assert len(lab.Lab()) == 2 @@ -23,25 +24,48 @@ def test_ingest_lab(pipeline,ingest_lab): assert len(lab.Protocol()) == 2 assert len(lab.ProtocolType()) == 2 + labs, _ = lab_csv + for l in labs[1:]: + l = l.split(",") + assert (lab.Lab & {'lab':l[0]}).fetch1('lab_name')==l[1] + + projects, _ = lab_project_csv + for p in projects[1:]: + p = p.split(",") + assert (lab.Project & {'project':p[0]} + ).fetch1('project_description')==p[1] + + protocols, _ = lab_protocol_csv + for p in protocols[1:]: + p = p.split(",") + assert (lab.Protocol & {'protocol':p[0]} + ).fetch1('protocol_type')==p[1] + ## Does not have example data: # assert len(lab.Source()) == 0 -def test_ingest_subjects(pipeline, ingest_subjects): +def test_ingest_subjects(pipeline, subjects_csv, ingest_subjects): """Check length of subject.Subject""" subject = pipeline['subject'] assert len(subject.Subject()) == 2 + subjects, _ = subjects_csv + for s in subjects[1:]: + s = s.split(",") + assert (subject.Subject & {'subject':s[0]} + ).fetch1('subject_description')==s[3] + ## Does not have example data: # assert len(genotyping.Sequence()) == 0 def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): """Check length/contents of Session.SessionDirectory""" session = pipeline['session'] - assert len(session.Session()) == 2 sessions, _ = sessions_csv - sess = sessions.iloc[0] - - assert (session.SessionDirectory - & {'subject': sess.name}).fetch1('session_dir') == sess.session_dir \ No newline at end of file + for sess in sessions[1:]: + sess = sess.split(",") + assert (session.SessionDirectory + & {'subject': sess[0]} + ).fetch1('session_dir') == sess[2] \ No newline at end of file diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index a6b7288..3251536 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -7,12 +7,12 @@ def test_generate_pipeline(pipeline): subject = pipeline['subject'] lab = pipeline['lab'] - # test connection from lab to lab/subject - lab_membership, loc_tbl, subjlab_tbl = lab.Lab.children(as_objects=True) - # assert lab_tbl.full_table_name == lab.Lab.full_table_name - assert loc_tbl.full_table_name == lab.Location.full_table_name - assert subjlab_tbl.full_table_name == subject.Subject.Lab.full_table_name + # test connection Lab->schema children, and Lab->Subject.Lab + lab_membership, loc_tbl, subject_lab_tbl = lab.Lab.children(as_objects=True) + assert lab_membership.full_table_name == lab.LabMembership.full_table_name + assert loc_tbl.full_table_name == lab.Location.full_table_name + assert subject_lab_tbl.full_table_name == subject.Subject.Lab.full_table_name - # test connection from lab to lab/subject + # test connection Subject->Session subject_tbl, *_ = session.Session.parents(as_objects=True) assert subject_tbl.full_table_name == subject.Subject.full_table_name \ No newline at end of file diff --git a/user_data/lab/labs.csv b/user_data/lab/labs.csv index aa1621e..33546db 100644 --- a/user_data/lab/labs.csv +++ b/user_data/lab/labs.csv @@ -1,3 +1,3 @@ lab,lab_name,institution,address,time_zone,location,location_description -LabA,The Example Lab,Example Uni,"221B Baker St,London NW1 6XE,UK",UTC+0,Example Building,"2nd floor lab dedicated to all fictional experiments." -LabB,The Other Lab,Other Uni,"Oxford OX1 2JD, United Kingdom",UTC+0,Other Building,"fictional campus dedicated to imaginary experiments." \ No newline at end of file +LabA,The Example Lab,Example Uni,'221B Baker St,London NW1 6XE,UK',UTC+0,Example Building,'2nd floor lab dedicated to all fictional experiments.' +LabB,The Other Lab,Other Uni,'Oxford OX1 2JD, United Kingdom',UTC+0,Other Building,'fictional campus dedicated to imaginary experiments.' \ No newline at end of file diff --git a/user_data/lab/projusers.csv b/user_data/lab/project_users.csv similarity index 100% rename from user_data/lab/projusers.csv rename to user_data/lab/project_users.csv diff --git a/user_data/session/sessions.csv b/user_data/session/sessions.csv index d3ad137..c316d74 100644 --- a/user_data/session/sessions.csv +++ b/user_data/session/sessions.csv @@ -1,3 +1,3 @@ subject,session_datetime,session_dir,session_note -subject5,2020-04-15 11:16:38,/subject5/session1,"Successful data collection, no notes" -subject6,2021-06-02 14:04:22,/subject6/session1,"Ambient temp abnormally low" \ No newline at end of file +subject5,2020-04-15 11:16:38,/subject5/session1,'Successful data collection, no notes' +subject6,2021-06-02 14:04:22,/subject6/session1,'Ambient temp abnormally low' \ No newline at end of file diff --git a/user_data/animal/allele.csv b/user_data/subject/allele.csv similarity index 100% rename from user_data/animal/allele.csv rename to user_data/subject/allele.csv diff --git a/user_data/animal/line.csv b/user_data/subject/line.csv similarity index 100% rename from user_data/animal/line.csv rename to user_data/subject/line.csv diff --git a/user_data/animal/source.csv b/user_data/subject/source.csv similarity index 100% rename from user_data/animal/source.csv rename to user_data/subject/source.csv diff --git a/user_data/animal/strain.csv b/user_data/subject/strain.csv similarity index 100% rename from user_data/animal/strain.csv rename to user_data/subject/strain.csv diff --git a/user_data/animal/subjects.csv b/user_data/subject/subjects.csv similarity index 100% rename from user_data/animal/subjects.csv rename to user_data/subject/subjects.csv diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 751b88a..31d1cfa 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -17,11 +17,11 @@ def ingest_general(csvs, tables, # take list of csvs/dj tables :param csvs: list of relative paths to CSV files :param tables: list of datajoint tables with () """ - for insert, table in zip(csvs, tables): # loop through lists - with open(insert, newline='') as f: # read csvs + for insert, table in zip(csvs, tables): + with open(insert, newline='') as f: data = list(csv.DictReader(f, delimiter=',')) - prev_len = len(table) # measure prev length - table.insert(data, skip_duplicates=skip_duplicates, # insert + prev_len = len(table) + table.insert(data, skip_duplicates=skip_duplicates, ignore_extra_fields=True) # must be true for csvs w/mult tables insert_len = len(table) - prev_len # report length change print(f'\n---- Inserting {insert_len} entry(s) into {table.table_name} ----') @@ -29,13 +29,13 @@ def ingest_general(csvs, tables, # take list of csvs/dj tables # TODO: permit embedded lists def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', - project_csv_path='./user_data/lab/projects.csv', - pubs_csv_path='./user_data/lab/publications.csv', - keyw_csv_path='./user_data/lab/keywords.csv', - protocol_csv_path='./user_data/lab/protocols.csv', - users_csv_path='./user_data/lab/users.csv', - projusers_csv_path='./user_data/lab/projusers.csv', - skip_duplicates=True): + project_csv_path='./user_data/lab/projects.csv', + publication_csv_path='./user_data/lab/publications.csv', + keyword_csv_path='./user_data/lab/keywords.csv', + protocol_csv_path='./user_data/lab/protocols.csv', + users_csv_path='./user_data/lab/users.csv', + project_user_csv_path='./user_data/lab/project_users.csv', + skip_duplicates=True): """ Inserts data from a series of csvs into their corresponding lab schema tables. By default, uses data from workflow_session/user_data/lab/ @@ -50,10 +50,10 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', # List with repeats for when mult dj.tables fed by same CSV csvs = [lab_csv_path, lab_csv_path, - project_csv_path, pubs_csv_path, keyw_csv_path, + project_csv_path, publication_csv_path, keyword_csv_path, protocol_csv_path, protocol_csv_path, users_csv_path,users_csv_path,users_csv_path, - projusers_csv_path] + project_user_csv_path] tables=[lab.Lab(), lab.Location(), lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), lab.ProtocolType(), lab.Protocol(), @@ -62,11 +62,11 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', ingest_general(csvs, tables, skip_duplicates=skip_duplicates) -def ingest_subjects(subject_csv_path='./user_data/animal/subjects.csv', +def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', skip_duplicates=True): """ Inserts data from a subject csv into corresponding subject schema tables - By default, uses data from workflow_session/user_data/animal/ + By default, uses data from workflow_session/user_data/subject/ :param subject_csv_path: relative path of subject csv :param skip_duplicates=True: datajoint insert function param """ From ffda57729abd739a2c6c8a6a5ec02dcd073f3856 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 30 Dec 2021 10:47:37 -0600 Subject: [PATCH 16/46] PEP8 E501,F401; Diagram aspect ration & new tables PEP8: Added IDE linter to fix E501 (line max to 79) and F401 (unused imports). Diagrams: Updated aspect ratio for legibility, preserved old See datajoint-python/issues/975 Added new element-lab tables. See element-lab/pull/11 --- images/genotyping_diagram.svg | 219 +++++++++--------- images/genotyping_diagram2.svg | 151 ------------ images/genotyping_diagram_old.svg | 138 +++++++++++ images/lab_diagram.svg | 227 ++++++++++-------- images/lab_diagram_PendingUpdate.svg | 222 ------------------ images/lab_diagram_old.svg | 130 +++++++++++ images/subject_diagram.svg | 329 +++++++++++++++------------ images/subject_diagram2.svg | 222 ------------------ images/subject_diagram_old.svg | 191 ++++++++++++++++ tests/__init__.py | 123 +++++++--- tests/test_export.py | 9 +- tests/test_ingest.py | 46 ++-- tests/test_pipeline_generation.py | 19 +- workflow_session/ingest.py | 46 ++-- workflow_session/paths.py | 8 +- workflow_session/pipeline.py | 5 +- 16 files changed, 1054 insertions(+), 1031 deletions(-) delete mode 100644 images/genotyping_diagram2.svg create mode 100644 images/genotyping_diagram_old.svg delete mode 100644 images/lab_diagram_PendingUpdate.svg create mode 100644 images/lab_diagram_old.svg delete mode 100644 images/subject_diagram2.svg create mode 100644 images/subject_diagram_old.svg diff --git a/images/genotyping_diagram.svg b/images/genotyping_diagram.svg index a6d6471..6c59d23 100644 --- a/images/genotyping_diagram.svg +++ b/images/genotyping_diagram.svg @@ -1,138 +1,151 @@ - - - - - -`genotyping`.`breeding_pair__father` - -`genotyping`.`breeding_pair__father` - - - -`genotyping`.`breeding_pair__mother` - -`genotyping`.`breeding_pair__mother` - - - -genotyping.AlleleSequence - - -genotyping.AlleleSequence + + + + + + + + + +SubjectCaging + + +SubjectCaging - - -genotyping.BreedingPair - - -genotyping.BreedingPair + + +genotyping.Weaning + + +genotyping.Weaning - - -genotyping.BreedingPair->`genotyping`.`breeding_pair__father` - + + +GenotypeTest + + +GenotypeTest + - - -genotyping.BreedingPair->`genotyping`.`breeding_pair__mother` - - - -genotyping.Litter - - -genotyping.Litter + + +Cage + + +Cage - - -genotyping.BreedingPair->genotyping.Litter - + + +Cage->SubjectCaging + + + + +BreedingPair.Mother + + +BreedingPair.Mother + + - + genotyping.SubjectLitter - - -genotyping.SubjectLitter + + +genotyping.SubjectLitter - - -genotyping.Weaning - - -genotyping.Weaning + + +BreedingPair.Father + + +BreedingPair.Father - - -genotyping.Litter->genotyping.SubjectLitter - - - - -genotyping.Litter->genotyping.Weaning - - - - -genotyping.SubjectCaging - - -genotyping.SubjectCaging + + +BreedingPair + + +BreedingPair - - -genotyping.GenotypeTest - - -genotyping.GenotypeTest - + + +BreedingPair->BreedingPair.Mother + + + +BreedingPair->BreedingPair.Father + - - -genotyping.Sequence - - -genotyping.Sequence + + +genotyping.Litter + + +genotyping.Litter - - -genotyping.Sequence->genotyping.AlleleSequence - + + +BreedingPair->genotyping.Litter + - - -genotyping.Sequence->genotyping.GenotypeTest - + + +genotyping.Litter->genotyping.Weaning + - - -genotyping.Cage - - -genotyping.Cage + + +genotyping.Litter->genotyping.SubjectLitter + + + + +Sequence + + +Sequence + + + + + +Sequence->GenotypeTest + + + + +genotyping.AlleleSequence + + +genotyping.AlleleSequence - - -genotyping.Cage->genotyping.SubjectCaging - + + +Sequence->genotyping.AlleleSequence + - \ No newline at end of file + diff --git a/images/genotyping_diagram2.svg b/images/genotyping_diagram2.svg deleted file mode 100644 index 6c59d23..0000000 --- a/images/genotyping_diagram2.svg +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - -SubjectCaging - - -SubjectCaging - - - - - -genotyping.Weaning - - -genotyping.Weaning - - - - - -GenotypeTest - - -GenotypeTest - - - - - -Cage - - -Cage - - - - - -Cage->SubjectCaging - - - - -BreedingPair.Mother - - -BreedingPair.Mother - - - - - -genotyping.SubjectLitter - - -genotyping.SubjectLitter - - - - - -BreedingPair.Father - - -BreedingPair.Father - - - - - -BreedingPair - - -BreedingPair - - - - - -BreedingPair->BreedingPair.Mother - - - - -BreedingPair->BreedingPair.Father - - - - -genotyping.Litter - - -genotyping.Litter - - - - - -BreedingPair->genotyping.Litter - - - - -genotyping.Litter->genotyping.Weaning - - - - -genotyping.Litter->genotyping.SubjectLitter - - - - -Sequence - - -Sequence - - - - - -Sequence->GenotypeTest - - - - -genotyping.AlleleSequence - - -genotyping.AlleleSequence - - - - - -Sequence->genotyping.AlleleSequence - - - - diff --git a/images/genotyping_diagram_old.svg b/images/genotyping_diagram_old.svg new file mode 100644 index 0000000..a6d6471 --- /dev/null +++ b/images/genotyping_diagram_old.svg @@ -0,0 +1,138 @@ + + + + + +`genotyping`.`breeding_pair__father` + +`genotyping`.`breeding_pair__father` + + + +`genotyping`.`breeding_pair__mother` + +`genotyping`.`breeding_pair__mother` + + + +genotyping.AlleleSequence + + +genotyping.AlleleSequence + + + + + +genotyping.BreedingPair + + +genotyping.BreedingPair + + + + + +genotyping.BreedingPair->`genotyping`.`breeding_pair__father` + + + + +genotyping.BreedingPair->`genotyping`.`breeding_pair__mother` + + + + +genotyping.Litter + + +genotyping.Litter + + + + + +genotyping.BreedingPair->genotyping.Litter + + + + +genotyping.SubjectLitter + + +genotyping.SubjectLitter + + + + + +genotyping.Weaning + + +genotyping.Weaning + + + + + +genotyping.Litter->genotyping.SubjectLitter + + + + +genotyping.Litter->genotyping.Weaning + + + + +genotyping.SubjectCaging + + +genotyping.SubjectCaging + + + + + +genotyping.GenotypeTest + + +genotyping.GenotypeTest + + + + + +genotyping.Sequence + + +genotyping.Sequence + + + + + +genotyping.Sequence->genotyping.AlleleSequence + + + + +genotyping.Sequence->genotyping.GenotypeTest + + + + +genotyping.Cage + + +genotyping.Cage + + + + + +genotyping.Cage->genotyping.SubjectCaging + + + + \ No newline at end of file diff --git a/images/lab_diagram.svg b/images/lab_diagram.svg index b954e4c..f13349d 100644 --- a/images/lab_diagram.svg +++ b/images/lab_diagram.svg @@ -1,130 +1,179 @@ - - - - - -Lab - - -Lab + + + + + + + + + +lab.Protocol + + +lab.Protocol - - -lab.LabMembership - - -lab.LabMembership + + +lab.Project + + +lab.Project - - -Lab->lab.LabMembership - + + +lab.ProjectUser + + +lab.ProjectUser + + - - -Location - - -Location + + +lab.Project->lab.ProjectUser + + + + +lab.Project.Sourcecode + + +lab.Project.Sourcecode - - -Lab->Location - + + +lab.Project->lab.Project.Sourcecode + - - -lab.Project - - -lab.Project + + +lab.Project.Keywords + + +lab.Project.Keywords - - -lab.ProjectUser - - -lab.ProjectUser + + +lab.Project->lab.Project.Keywords + + + + +lab.Project.Publication + + +lab.Project.Publication - - -lab.Project->lab.ProjectUser - + + +lab.Project->lab.Project.Publication + + + + +lab.Location + + +lab.Location + + - + lab.UserRole - - -lab.UserRole + + +lab.UserRole + + + + + +lab.LabMembership + + +lab.LabMembership - -lab.UserRole->lab.LabMembership - + +lab.UserRole->lab.LabMembership + + + + +lab.Source + + +lab.Source + + - + lab.ProtocolType - - -lab.ProtocolType + + +lab.ProtocolType - - -Protocol - - -Protocol - + + +lab.ProtocolType->lab.Protocol + + + +lab.Lab + + +lab.Lab + - - -lab.ProtocolType->Protocol - - - -Source - - -Source - + + +lab.Lab->lab.Location + + + +lab.Lab->lab.LabMembership + - - -User - - -User + + +lab.User + + +lab.User - - -User->lab.ProjectUser - + + +lab.User->lab.ProjectUser + - - -User->lab.LabMembership - + + +lab.User->lab.LabMembership + - \ No newline at end of file + diff --git a/images/lab_diagram_PendingUpdate.svg b/images/lab_diagram_PendingUpdate.svg deleted file mode 100644 index 2805db4..0000000 --- a/images/lab_diagram_PendingUpdate.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - -`neuro_lab`.`#skull_reference` - -`neuro_lab`.`#skull_reference` - - - -lab.Protocol - - -lab.Protocol - - - - - -lab.Equipment.EphysEquipment - - -lab.Equipment.EphysEquipment - - - - - -lab.ProtocolType - - -lab.ProtocolType - - - - - -lab.ProtocolType->lab.Protocol - - - - -lab.Project.Sourcecode - - -lab.Project.Sourcecode - - - - - -lab.Project - - -lab.Project - - - - - -lab.Project->lab.Project.Sourcecode - - - - -lab.ProjectUser - - -lab.ProjectUser - - - - - -lab.Project->lab.ProjectUser - - - - -lab.Project.Publication - - -lab.Project.Publication - - - - - -lab.Project->lab.Project.Publication - - - - -lab.Project.Keywords - - -lab.Project.Keywords - - - - - -lab.Project->lab.Project.Keywords - - - - -lab.User - - -lab.User - - - - - -lab.User->lab.ProjectUser - - - - -lab.LabMembership - - -lab.LabMembership - - - - - -lab.User->lab.LabMembership - - - - -lab.Lab - - -lab.Lab - - - - - -lab.Lab->lab.LabMembership - - - - -lab.Location - - -lab.Location - - - - - -lab.Lab->lab.Location - - - - -lab.Equipment - - -lab.Equipment - - - - - -lab.Equipment->lab.Equipment.EphysEquipment - - - - -lab.Equipment.CaImgEquipment - - -lab.Equipment.CaImgEquipment - - - - - -lab.Equipment->lab.Equipment.CaImgEquipment - - - - -lab.Source - - -lab.Source - - - - - -lab.UserRole - - -lab.UserRole - - - - - -lab.UserRole->lab.LabMembership - - - - diff --git a/images/lab_diagram_old.svg b/images/lab_diagram_old.svg new file mode 100644 index 0000000..b954e4c --- /dev/null +++ b/images/lab_diagram_old.svg @@ -0,0 +1,130 @@ + + + + + +Lab + + +Lab + + + + + +lab.LabMembership + + +lab.LabMembership + + + + + +Lab->lab.LabMembership + + + + +Location + + +Location + + + + + +Lab->Location + + + + +lab.Project + + +lab.Project + + + + + +lab.ProjectUser + + +lab.ProjectUser + + + + + +lab.Project->lab.ProjectUser + + + + +lab.UserRole + + +lab.UserRole + + + + + +lab.UserRole->lab.LabMembership + + + + +lab.ProtocolType + + +lab.ProtocolType + + + + + +Protocol + + +Protocol + + + + + +lab.ProtocolType->Protocol + + + + +Source + + +Source + + + + + +User + + +User + + + + + +User->lab.ProjectUser + + + + +User->lab.LabMembership + + + + \ No newline at end of file diff --git a/images/subject_diagram.svg b/images/subject_diagram.svg index ecb6c89..9864ba7 100644 --- a/images/subject_diagram.svg +++ b/images/subject_diagram.svg @@ -1,191 +1,222 @@ - - - - - -`subject`.`subject__protocol` - -`subject`.`subject__protocol` - - - -`subject`.`#allele__source` - -`subject`.`#allele__source` - - - -`subject`.`subject__source` - -`subject`.`subject__source` - - - -`subject`.`#line__allele` - -`subject`.`#line__allele` - - - -`subject`.`subject__strain` - -`subject`.`subject__strain` - - - -`subject`.`subject__line` - -`subject`.`subject__line` - - - -`subject`.`subject__lab` - -`subject`.`subject__lab` - - - -`subject`.`subject__user` - -`subject`.`subject__user` + + + + + + + + + +subject.Subject.Source + + +subject.Subject.Source + - - -subject.Subject - - -subject.Subject + + + +subject.SubjectDeath + + +subject.SubjectDeath - - -subject.Subject->`subject`.`subject__protocol` - + + +subject.Allele.Source + + +subject.Allele.Source + - - -subject.Subject->`subject`.`subject__source` - - - -subject.Subject->`subject`.`subject__strain` - + + +subject.Subject.Lab + + +subject.Subject.Lab + - - -subject.Subject->`subject`.`subject__line` - - - -subject.Subject->`subject`.`subject__lab` - + + +subject.Line.Allele + + +subject.Line.Allele + - - -subject.Subject->`subject`.`subject__user` - - - -subject.SubjectDeath - - -subject.SubjectDeath + + +subject.Subject.User + + +subject.Subject.User - - -subject.Subject->subject.SubjectDeath - + + +subject.Zygosity + + +subject.Zygosity + + + + + +subject.Subject.Strain + + +subject.Subject.Strain + + - + subject.SubjectCullMethod - - -subject.SubjectCullMethod + + +subject.SubjectCullMethod - - -subject.Subject->subject.SubjectCullMethod - - - - -subject.Zygosity - - -subject.Zygosity + + +subject.Subject.Line + + +subject.Subject.Line - - -subject.Subject->subject.Zygosity - + + +subject.Subject.Protocol + + +subject.Subject.Protocol + - - -subject.Strain - - -subject.Strain + + + +subject.Line + + +subject.Line - - -subject.Strain->`subject`.`subject__strain` - + + +subject.Line->subject.Line.Allele + + + + +subject.Line->subject.Subject.Line + - + subject.Allele - - -subject.Allele + + +subject.Allele - - -subject.Allele->`subject`.`#allele__source` - + + +subject.Allele->subject.Allele.Source + - - -subject.Allele->`subject`.`#line__allele` - + + +subject.Allele->subject.Line.Allele + - -subject.Allele->subject.Zygosity - + +subject.Allele->subject.Zygosity + - - -subject.Line - - -subject.Line + + +subject.Strain + + +subject.Strain - - -subject.Line->`subject`.`#line__allele` - + + +subject.Strain->subject.Subject.Strain + + + + +subject.Subject + + +subject.Subject + + + + + +subject.Subject->subject.Subject.Source + + + + +subject.Subject->subject.SubjectDeath + + + + +subject.Subject->subject.Subject.Lab + + + + +subject.Subject->subject.Subject.User + + + + +subject.Subject->subject.Zygosity + + + + +subject.Subject->subject.Subject.Strain + + + + +subject.Subject->subject.SubjectCullMethod + + + + +subject.Subject->subject.Subject.Line + - - -subject.Line->`subject`.`subject__line` - + + +subject.Subject->subject.Subject.Protocol + - \ No newline at end of file + diff --git a/images/subject_diagram2.svg b/images/subject_diagram2.svg deleted file mode 100644 index 9864ba7..0000000 --- a/images/subject_diagram2.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - -subject.Subject.Source - - -subject.Subject.Source - - - - - -subject.SubjectDeath - - -subject.SubjectDeath - - - - - -subject.Allele.Source - - -subject.Allele.Source - - - - - -subject.Subject.Lab - - -subject.Subject.Lab - - - - - -subject.Line.Allele - - -subject.Line.Allele - - - - - -subject.Subject.User - - -subject.Subject.User - - - - - -subject.Zygosity - - -subject.Zygosity - - - - - -subject.Subject.Strain - - -subject.Subject.Strain - - - - - -subject.SubjectCullMethod - - -subject.SubjectCullMethod - - - - - -subject.Subject.Line - - -subject.Subject.Line - - - - - -subject.Subject.Protocol - - -subject.Subject.Protocol - - - - - -subject.Line - - -subject.Line - - - - - -subject.Line->subject.Line.Allele - - - - -subject.Line->subject.Subject.Line - - - - -subject.Allele - - -subject.Allele - - - - - -subject.Allele->subject.Allele.Source - - - - -subject.Allele->subject.Line.Allele - - - - -subject.Allele->subject.Zygosity - - - - -subject.Strain - - -subject.Strain - - - - - -subject.Strain->subject.Subject.Strain - - - - -subject.Subject - - -subject.Subject - - - - - -subject.Subject->subject.Subject.Source - - - - -subject.Subject->subject.SubjectDeath - - - - -subject.Subject->subject.Subject.Lab - - - - -subject.Subject->subject.Subject.User - - - - -subject.Subject->subject.Zygosity - - - - -subject.Subject->subject.Subject.Strain - - - - -subject.Subject->subject.SubjectCullMethod - - - - -subject.Subject->subject.Subject.Line - - - - -subject.Subject->subject.Subject.Protocol - - - - diff --git a/images/subject_diagram_old.svg b/images/subject_diagram_old.svg new file mode 100644 index 0000000..ecb6c89 --- /dev/null +++ b/images/subject_diagram_old.svg @@ -0,0 +1,191 @@ + + + + + +`subject`.`subject__protocol` + +`subject`.`subject__protocol` + + + +`subject`.`#allele__source` + +`subject`.`#allele__source` + + + +`subject`.`subject__source` + +`subject`.`subject__source` + + + +`subject`.`#line__allele` + +`subject`.`#line__allele` + + + +`subject`.`subject__strain` + +`subject`.`subject__strain` + + + +`subject`.`subject__line` + +`subject`.`subject__line` + + + +`subject`.`subject__lab` + +`subject`.`subject__lab` + + + +`subject`.`subject__user` + +`subject`.`subject__user` + + + +subject.Subject + + +subject.Subject + + + + + +subject.Subject->`subject`.`subject__protocol` + + + + +subject.Subject->`subject`.`subject__source` + + + + +subject.Subject->`subject`.`subject__strain` + + + + +subject.Subject->`subject`.`subject__line` + + + + +subject.Subject->`subject`.`subject__lab` + + + + +subject.Subject->`subject`.`subject__user` + + + + +subject.SubjectDeath + + +subject.SubjectDeath + + + + + +subject.Subject->subject.SubjectDeath + + + + +subject.SubjectCullMethod + + +subject.SubjectCullMethod + + + + + +subject.Subject->subject.SubjectCullMethod + + + + +subject.Zygosity + + +subject.Zygosity + + + + + +subject.Subject->subject.Zygosity + + + + +subject.Strain + + +subject.Strain + + + + + +subject.Strain->`subject`.`subject__strain` + + + + +subject.Allele + + +subject.Allele + + + + + +subject.Allele->`subject`.`#allele__source` + + + + +subject.Allele->`subject`.`#line__allele` + + + + +subject.Allele->subject.Zygosity + + + + +subject.Line + + +subject.Line + + + + + +subject.Line->`subject`.`#line__allele` + + + + +subject.Line->`subject`.`subject__line` + + + + \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 785adce..00da41e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,9 +1,11 @@ ''' fresh docker: - docker run --name wf-sess -p 3306:3306 -e MYSQL_ROOT_PASSWORD=tutorial datajoint/mysql + docker run --name wf-sess -p 3306:3306 -e \ + MYSQL_ROOT_PASSWORD=tutorial datajoint/mysql dependencies: pip install pytest pytest-cov run all tests: - pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings tests/ + pytest -sv --cov-report term-missing --cov=workflow-session \ + -p no:warnings tests/ run one test, debug: pytest [above options] --pdb tests/tests_name.py -k function_name ''' @@ -13,9 +15,6 @@ import pathlib import datajoint as dj -import workflow_session - - # ------------------- SOME CONSTANTS ------------------- _tear_down = True @@ -25,18 +24,20 @@ # ------------------ GENERAL FUCNTION ------------------ -def write_csv(content,path): + +def write_csv(content, path): """ General function for writing strings to lines in CSV :param path: pathlib PosixPath :param content: list of strings, each as row of CSV """ - with open(path,'w') as f: + with open(path, 'w') as f: for line in content: f.write(line+'\n') # ------------------- FIXTURES ------------------- + @pytest.fixture(autouse=True) def dj_config(): """ If dj_local_config exists, load""" @@ -48,6 +49,7 @@ def dj_config(): or dj.config['custom']['database.prefix'])} return + @pytest.fixture def pipeline(): """ Loads workflow_session.pipeline lab, session, subject""" @@ -62,30 +64,55 @@ def pipeline(): pipeline.session.Session.delete() pipeline.lab.Lab.delete() + @pytest.fixture def lab_csv(): """ Create a 'labs.csv' file""" - lab_content = ["lab,lab_name,institution,address,time_zone,location,location_description", - "LabA,The Example Lab,Example Uni,'221B Baker St,London NW1 6XE,UK',UTC+0,Example Building,'2nd floor lab dedicated to all fictional experiments.'", - "LabB,The Other Lab,Other Uni,'Oxford OX1 2JD, United Kingdom',UTC+0,Other Building,'fictional campus dedicated to imaginary experiments.'"] + lab_content = ["lab,lab_name,institution,address," + + "time_zone,location,location_description", + "LabA,The Example Lab,Example Uni," + + "'221B Baker St,London NW1 6XE,UK',UTC+0," + + "Example Building,'2nd floor lab dedicated to all " + + "fictional experiments.'", + "LabB,The Other Lab,Other Uni," + + "'Oxford OX1 2JD, United Kingdom',UTC+0," + + "Other Building,'fictional campus dedicated to imaginary" + + "experiments.'"] lab_csv_path = pathlib.Path('./tests/user_data/lab/labs.csv') - write_csv(lab_content,lab_csv_path) + write_csv(lab_content, lab_csv_path) yield lab_content, lab_csv_path lab_csv_path.unlink() + @pytest.fixture def lab_project_csv(): """ Create a 'projects.csv' file""" - lab_project_content = ["project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl", - "ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,Exemplarvirus administered 10d before experimental session,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab", - "ProjB,Other example project to populate element-lab,https://github.com/datajoint/element-session/,element-session,,Exemplarvirus administered 8d study,,,,https://github.com/datajoint/element-session/tree/main/element_session"] + lab_project_content = ["project,project_description,repositoryurl," + + "repositoryname,pharmacology,viruses,slices," + + "stimulus,surgery,codeurl", + "ProjA,Example project to populate element-lab," + + "https://github.com/datajoint/element-lab/," + + "element-lab,Subjects were administered 10ul " + + "sedative prior to surgery,Exemplarvirus " + + "administered 10d before experimental session,," + + "videos generated programmatically see " + + "repository,Craniotomy performed by session " + + "experimenter,https://github.com/datajoint/" + + "element-lab/tree/main/element_lab", + "ProjB,Other example project to populate element-" + + "lab,https://github.com/datajoint/element-" + + "session/,element-session,,Exemplarvirus " + + "administered 8d study,,,,https://github.com/" + + "datajoint/element-session/tree/main/element_" + + "session"] lab_project_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') - write_csv(lab_project_content,lab_project_csv_path) + write_csv(lab_project_content, lab_project_csv_path) yield lab_project_content, lab_project_csv_path lab_project_csv_path.unlink() + @pytest.fixture def lab_project_users_csv(): """ Create a 'project_users.csv' file""" @@ -94,24 +121,28 @@ def lab_project_users_csv(): "Sherlock,ProjB", "Watson,ProjB", "Dr. Candace Pert,ProjA"] - lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/project_users.csv') - write_csv(lab_project_user_content,lab_project_user_csv_path) + lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/\ + project_users.csv') + write_csv(lab_project_user_content, lab_project_user_csv_path) yield lab_project_user_content, lab_project_user_csv_path lab_project_user_csv_path.unlink() + @pytest.fixture def lab_publications_csv(): """ Create a 'publications.csv' file""" lab_publication_content = ["project,publication", "ProjA,arXiv:1807.11104", "ProjA,arXiv:1807.11104v1"] - lab_publication_csv_path = pathlib.Path('./tests/user_data/lab/publications.csv') + lab_publication_csv_path = pathlib.Path('./tests/user_data/lab/\ + publications.csv') write_csv(lab_publication_content, lab_publication_csv_path) yield lab_publication_content, lab_publication_csv_path lab_publication_csv_path.unlink() + @pytest.fixture def lab_keywords_csv(): """ Create a 'keywords.csv' file""" @@ -120,36 +151,43 @@ def lab_keywords_csv(): "ProjA,Example", "ProjB,Alternate"] lab_keyword_csv_path = pathlib.Path('./tests/user_data/lab/keywords.csv') - write_csv(lab_keyword_content,lab_keyword_csv_path) + write_csv(lab_keyword_content, lab_keyword_csv_path) yield lab_keyword_content, lab_keyword_csv_path lab_keyword_csv_path.unlink() + @pytest.fixture def lab_protocol_csv(): """ Create a 'protocols.csv' file""" lab_protocol_content = ["protocol,protocol_type,protocol_description", - "ProtA,IRB expedited review,Protocol for managing data ingestion", - "ProtB,Alternative Method,Limited protocol for piloting only"] + "ProtA,IRB expedited review,Protocol for managing " + + "data ingestion", + "ProtB,Alternative Method,Limited protocol for " + + "piloting only"] lab_protocol_csv_path = pathlib.Path('./tests/user_data/lab/protocols.csv') - write_csv(lab_protocol_content,lab_protocol_csv_path) + write_csv(lab_protocol_content, lab_protocol_csv_path) yield lab_protocol_content, lab_protocol_csv_path lab_protocol_csv_path.unlink() + @pytest.fixture def lab_user_csv(): """ Create a 'users.csv' file""" lab_user_content = ["lab,user,user_role,user_email,user_cellphone", - "LabA,Sherlock,PI,Sherlock@BakerSt.com,+44 20 7946 0344", + "LabA,Sherlock,PI,Sherlock@BakerSt.com," + + "+44 20 7946 0344", "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", - "LabB,Dr. Candace Pert,PI,Pert@gmail.com,+44 74 4046 5899"] + "LabB,Dr. Candace Pert,PI,Pert@gmail.com," + + "+44 74 4046 5899"] lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') - write_csv(lab_user_content,lab_user_csv_path) + write_csv(lab_user_content, lab_user_csv_path) yield lab_user_content, lab_user_csv_path lab_user_csv_path.unlink() + @pytest.fixture def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, lab_user_csv, @@ -172,15 +210,19 @@ def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, project_user_csv_path=lab_project_user_csv_path) return + # Subject data and ingestion @pytest.fixture def subjects_csv(): """ Create a 'subjects.csv' file""" - subject_content = ["subject,sex,subject_birth_date,subject_description,death_date,cull_method", - "subject5,F,2020-01-01 00:00:01,rich,2020-10-02 00:00:01,natural causes", - "subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes"] + subject_content = ["subject,sex,subject_birth_date,subject_description," + + "death_date,cull_method", + "subject5,F,2020-01-01 00:00:01,rich," + + "2020-10-02 00:00:01,natural causes", + "subject6,M,2020-01-01 00:00:01,manuel," + + "2020-10-03 00:00:01,natural causes"] subject_csv_path = pathlib.Path('./tests/user_data/subject/subjects.csv') - write_csv(subject_content,subject_csv_path) + write_csv(subject_content, subject_csv_path) yield subject_content, subject_csv_path subject_csv_path.unlink() @@ -201,9 +243,11 @@ def sessions_csv(): """ Create a 'sessions.csv' file""" session_csv_path = pathlib.Path('./tests/user_data/session/sessions.csv') session_content = ["subject,session_datetime,session_dir,session_note", - "subject5,2020-04-15 11:16:38,/subject5/session1,'Successful data collection, no notes'", - "subject6,2021-06-02 14:04:22,/subject6/session1,'Ambient temp abnormally low'"] - write_csv(session_content,session_csv_path) + "subject5,2020-04-15 11:16:38,/subject5/session1," + + "'Successful data collection, no notes'", + "subject6,2021-06-02 14:04:22,/subject6/session1," + + "'Ambient temp abnormally low'"] + write_csv(session_content, session_csv_path) yield session_content, session_csv_path session_csv_path.unlink() @@ -217,9 +261,11 @@ def ingest_sessions(ingest_subjects, sessions_csv): ingest_sessions(session_csv_path=session_csv_path) return + """ # FUTURE FIXTURES, pending sharable example data -lab_sources_content=["source, source_name, contact_details, source_description"] +lab_sources_content=["source, source_name, contact_details,\ + source_description"] lab_sources_csv_path = pathlib.Path('./tests/user_data/lab/sources.csv') write_csv(lab_sources_content,lab_sources_csv_path) yeild lab_sources_content,lab_sources_csv_path @@ -230,7 +276,8 @@ def ingest_sessions(ingest_subjects, sessions_csv): yeild subject_allele_content,subject_allele_csv_path subject_allele_csv_path.unlink() subject_genotyping_content=[] -subject_genotyping_csv_path = pathlib.Path('./tests/user_data/subject/genotyping.csv') +subject_genotyping_csv_path = pathlib.Path('./tests/user_data/subject/\ + genotyping.csv') write_csv(subject_genotyping_content,subject_genotyping_csv_path) yeild subject_genotyping_content,subject_genotyping_csv_path subject_genotyping_csv_path.unlink() @@ -239,7 +286,8 @@ def ingest_sessions(ingest_subjects, sessions_csv): write_csv(subject_line_content,subject_line_csv_path) yeild subject_line_content,subject_line_csv_path subject_line_csv_path.unlink() -subject_source_content=["allele, source_identifier, source_url, expression_data_url"] +subject_source_content=["allele,source_identifier,source_url,\ + expression_data_url"] subject_source_csv_path = pathlib.Path('./tests/user_data/subject/source.csv') write_csv(subject_source_content,subject_source_csv_path) yeild subject_source_content,subject_source_csv_path @@ -249,5 +297,6 @@ def ingest_sessions(ingest_subjects, sessions_csv): write_csv(subject_strain_content,subject_strain_csv_path) yeild subject_strain_content,subject_strain_csv_path subject_strain_csv_path.unlink() -subject_subjects_csv_path = pathlib.Path('./tests/user_data/subject/subjects.csv') -""" \ No newline at end of file +subject_subjects_csv_path = pathlib.Path('./tests/user_data/subject/\ + subjects.csv') +""" diff --git a/tests/test_export.py b/tests/test_export.py index 942e7df..7f1b4eb 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -1,8 +1,7 @@ -import workflow_session -from . import (dj_config, pipeline, - lab_csv, ingest_lab, - subjects_csv, ingest_subjects, - sessions_csv, ingest_sessions) +# import workflow_session +# from . import (dj_config, pipeline, lab_csv, ingest_lab, +# subjects_csv, ingest_subjects, +# sessions_csv, ingest_sessions) def test_nwb_export(): diff --git a/tests/test_ingest.py b/tests/test_ingest.py index 30926c2..b491faa 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -1,7 +1,8 @@ -import sys -import pathlib +'''Tests ingestion into schema tables: Lab, Subject, Session + 1. Assert length of populating data from __innit__ + 2. Assert exact matches of inserted data fore key tables +''' -import workflow_session from . import (dj_config, pipeline, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, lab_user_csv, @@ -25,39 +26,42 @@ def test_ingest_lab(pipeline, ingest_lab, assert len(lab.ProtocolType()) == 2 labs, _ = lab_csv - for l in labs[1:]: - l = l.split(",") - assert (lab.Lab & {'lab':l[0]}).fetch1('lab_name')==l[1] + for this_lab in labs[1:]: + lab_values = this_lab.split(",") + assert (lab.Lab & {'lab': lab_values[0]} + ).fetch1('lab_name') == lab_values[1] projects, _ = lab_project_csv - for p in projects[1:]: - p = p.split(",") - assert (lab.Project & {'project':p[0]} - ).fetch1('project_description')==p[1] + for this_project in projects[1:]: + project_values = this_project.split(",") + assert (lab.Project & {'project': project_values[0]} + ).fetch1('project_description') == project_values[1] protocols, _ = lab_protocol_csv - for p in protocols[1:]: - p = p.split(",") - assert (lab.Protocol & {'protocol':p[0]} - ).fetch1('protocol_type')==p[1] + for this_protocol in protocols[1:]: + protocol_values = this_protocol.split(",") + assert (lab.Protocol & {'protocol': protocol_values[0]} + ).fetch1('protocol_type') == protocol_values[1] - ## Does not have example data: + # Does not have example data: # assert len(lab.Source()) == 0 + def test_ingest_subjects(pipeline, subjects_csv, ingest_subjects): """Check length of subject.Subject""" subject = pipeline['subject'] assert len(subject.Subject()) == 2 subjects, _ = subjects_csv - for s in subjects[1:]: - s = s.split(",") - assert (subject.Subject & {'subject':s[0]} - ).fetch1('subject_description')==s[3] + for this_subject in subjects[1:]: + subject_values = this_subject.split(",") + assert (subject.Subject & {'subject': subject_values[0]} + ).fetch1('subject_description') == subject_values[3] - ## Does not have example data: + # Does not have example data: # assert len(genotyping.Sequence()) == 0 + def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): """Check length/contents of Session.SessionDirectory""" session = pipeline['session'] @@ -68,4 +72,4 @@ def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): sess = sess.split(",") assert (session.SessionDirectory & {'subject': sess[0]} - ).fetch1('session_dir') == sess[2] \ No newline at end of file + ).fetch1('session_dir') == sess[2] diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index 3251536..2136fa1 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -1,18 +1,25 @@ -import workflow_session -from . import dj_config, pipeline +'''Test pipeline construction + 1. Assert lab link to within-schema children + 2. Assert lab link to subject + 3. Assert subject link to session +''' + +from . import pipeline def test_generate_pipeline(pipeline): session = pipeline['session'] subject = pipeline['subject'] - lab = pipeline['lab'] + lab = pipeline['lab'] # test connection Lab->schema children, and Lab->Subject.Lab - lab_membership, loc_tbl, subject_lab_tbl = lab.Lab.children(as_objects=True) + lab_membership, loc_tbl, subject_lab_tbl = \ + lab.Lab.children(as_objects=True) assert lab_membership.full_table_name == lab.LabMembership.full_table_name assert loc_tbl.full_table_name == lab.Location.full_table_name - assert subject_lab_tbl.full_table_name == subject.Subject.Lab.full_table_name + assert subject_lab_tbl.full_table_name == \ + subject.Subject.Lab.full_table_name # test connection Subject->Session subject_tbl, *_ = session.Session.parents(as_objects=True) - assert subject_tbl.full_table_name == subject.Subject.full_table_name \ No newline at end of file + assert subject_tbl.full_table_name == subject.Subject.full_table_name diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 31d1cfa..a28e950 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -1,19 +1,14 @@ -import pathlib import csv -import re - from workflow_session.pipeline import lab, subject, session -from workflow_session.paths import get_root_data_dir -import element_data_loader.utils -def ingest_general(csvs, tables, # take list of csvs/dj tables +def ingest_general(csvs, tables, skip_duplicates=True): """ Inserts data from a series of csvs into their corresponding table: e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], [lab.Lab(),lab.Project()] - ingest_general(csvs, tables, skip_duplicates=True, ignore_extra_fields=True) + ingest_general(csvs, tables, skip_duplicates=True) :param csvs: list of relative paths to CSV files :param tables: list of datajoint tables with () """ @@ -22,11 +17,16 @@ def ingest_general(csvs, tables, # take list of csvs/dj tables data = list(csv.DictReader(f, delimiter=',')) prev_len = len(table) table.insert(data, skip_duplicates=skip_duplicates, - ignore_extra_fields=True) # must be true for csvs w/mult tables + # Ignore Extra because some CSVs feed/mult tables + ignore_extra_fields=True) insert_len = len(table) - prev_len # report length change - print(f'\n---- Inserting {insert_len} entry(s) into {table.table_name} ----') + print(f'\n---- Inserting {insert_len} entry(s) \ + into {table.table_name} ----') + + # Future enhancement: permit embedded lists + # Currently requires a csv to be listed multiple times + # if feeding multiple tables. Could instead take lists of lists. - # TODO: permit embedded lists def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', project_csv_path='./user_data/lab/projects.csv', @@ -37,7 +37,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', project_user_csv_path='./user_data/lab/project_users.csv', skip_duplicates=True): """ - Inserts data from a series of csvs into their corresponding lab schema tables. + Inserts data from a CSVs into their corresponding lab schema tables. By default, uses data from workflow_session/user_data/lab/ :param lab_csv_path: relative path of lab csv :param project_csv_path: relative path of project csv @@ -52,16 +52,17 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', csvs = [lab_csv_path, lab_csv_path, project_csv_path, publication_csv_path, keyword_csv_path, protocol_csv_path, protocol_csv_path, - users_csv_path,users_csv_path,users_csv_path, + users_csv_path, users_csv_path, users_csv_path, project_user_csv_path] - tables=[lab.Lab(), lab.Location(), - lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), - lab.ProtocolType(), lab.Protocol(), - lab.UserRole(), lab.User(), lab.LabMembership(), - lab.ProjectUser()] + tables = [lab.Lab(), lab.Location(), + lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), + lab.ProtocolType(), lab.Protocol(), + lab.UserRole(), lab.User(), lab.LabMembership(), + lab.ProjectUser()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) + def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', skip_duplicates=True): """ @@ -71,13 +72,14 @@ def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', :param skip_duplicates=True: datajoint insert function param """ csvs = [subject_csv_path, subject_csv_path, subject_csv_path] - tables=[subject.Subject(),subject.SubjectDeath(), - subject.SubjectCullMethod()] + tables = [subject.Subject(), subject.SubjectDeath(), + subject.SubjectCullMethod()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) # TODO: add allele and genotyping data + def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', skip_duplicates=True): """ @@ -86,11 +88,13 @@ def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', :param session_csv_path: relative path of subject csv :param skip_duplicates=True: datajoint insert function param """ - csvs = [session_csv_path,session_csv_path,session_csv_path] - tables=[session.Session(),session.SessionDirectory(),session.SessionNote()] + csvs = [session_csv_path, session_csv_path, session_csv_path] + tables = [session.Session(), session.SessionDirectory(), + session.SessionNote()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) + if __name__ == '__main__': ingest_lab() ingest_subjects() diff --git a/workflow_session/paths.py b/workflow_session/paths.py index 1333a04..2e87d92 100644 --- a/workflow_session/paths.py +++ b/workflow_session/paths.py @@ -1,11 +1,13 @@ import datajoint as dj -import pathlib + def get_root_data_dir(): root_data_dirs = dj.config.get('custom', {}).get('root_data_dir', None) return root_data_dirs if root_data_dirs else None + def get_session_directory(session_key: dict) -> str: from .pipeline import session - session_dir = (session.SessionDirectory & session_key).fetch1('session_dir') - return session_dir \ No newline at end of file + session_dir = (session.SessionDirectory + & session_key).fetch1('session_dir') + return session_dir diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index 93b8aae..f07b78b 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -5,7 +5,8 @@ from element_session import session from element_animal.subject import Subject -from element_animal.genotyping import Sequence, BreedingPair, Cage, SubjectCaging, GenotypeTest +from element_animal.genotyping import Sequence, BreedingPair, Cage,\ + SubjectCaging, GenotypeTest from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session import Session @@ -24,4 +25,4 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) -genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) \ No newline at end of file +genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) From 13e73fe8578d3aecd2bf21128756acd635e5c1df Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 30 Dec 2021 10:56:20 -0600 Subject: [PATCH 17/46] Update README subject->experiment subject --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d2e9ed..c067c1a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository provides demonstrations for: Setting up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) ## Workflow architecture -The lab and subject management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. +The lab and experiment subject management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. ### element-lab From 3cea936b7a63d26a1e9fed4eaa1ca9744fdb46a4 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 30 Dec 2021 11:01:48 -0600 Subject: [PATCH 18/46] minor: remove redundant import --- tests/test_ingest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ingest.py b/tests/test_ingest.py index b491faa..4a49ad8 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -5,7 +5,7 @@ from . import (dj_config, pipeline, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, - lab_keywords_csv, lab_protocol_csv, lab_user_csv, + lab_keywords_csv, lab_protocol_csv, lab_project_users_csv, ingest_lab, subjects_csv, ingest_subjects, sessions_csv, ingest_sessions) From e55af5bec16ef54a0f0256c6cec07be9edb9327b Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Wed, 27 Oct 2021 12:01:29 -0500 Subject: [PATCH 19/46] Fixed link formatting, adding parens [title]link.com -> [title](link.com) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72dcb12..5d4824e 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ element-animal contains two modules, `subject` and `genotyping`. This workflow serves as an example of the upstream part of a typical data workflow, for examples using these two elements more intact workflows, refer to: -+ [workflow-array-ephys]https://github.com/datajoint/workflow-array-ephys -+ [workflow-calcium-imaging]https://github.com/datajoint/workflow-calcium-imaging ++ [workflow-array-ephys](https://github.com/datajoint/workflow-array-ephys) ++ [workflow-calcium-imaging](https://github.com/datajoint/workflow-calcium-imaging) ## Installation instructions From beaa5911da4ad771643afa62f3baa6c5ac4a9a6a Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Wed, 1 Dec 2021 15:19:56 -0600 Subject: [PATCH 20/46] Adding NB for export example --- LICENSE | 2 +- ...orkflow.ipynb => 1_Explore_Workflow.ipynb} | 0 notebooks/2_Explore_Export.ipynb | 152 ++++++++++++++++++ setup.py | 2 +- 4 files changed, 154 insertions(+), 2 deletions(-) rename notebooks/{explore_workflow.ipynb => 1_Explore_Workflow.ipynb} (100%) create mode 100644 notebooks/2_Explore_Export.ipynb diff --git a/LICENSE b/LICENSE index a9f8903..6bf141b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 DataJoint NEURO +Copyright (c) 2021 DataJoint Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/notebooks/explore_workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb similarity index 100% rename from notebooks/explore_workflow.ipynb rename to notebooks/1_Explore_Workflow.ipynb diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb new file mode 100644 index 0000000..c114077 --- /dev/null +++ b/notebooks/2_Explore_Export.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3054518f-87bc-42ff-a3e7-84bf3d2a37f6", + "metadata": {}, + "source": [ + "# DataJoint U24 - Export Session" + ] + }, + { + "cell_type": "markdown", + "id": "79c15f36-039d-4304-96be-f56ba0d6b10a", + "metadata": {}, + "source": [ + "Same as before, import data." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "39dda42b-a81b-4f04-8ec4-eef559168379", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n", + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "import os\n", + "os.chdir('..')\n", + "import datajoint as dj\n", + "dj.conn()\n", + "from workflow_session.pipeline import * \n", + "from workflow_session.ingest import *\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "markdown", + "id": "ab2a3f10-b96e-4f0d-9e54-0015bb9b8622", + "metadata": {}, + "source": [ + "Identify items for export with keys." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "76c040c8-15cc-4d61-ae5d-bc7646a6a0be", + "metadata": {}, + "outputs": [], + "source": [ + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')\n", + "mylab_key = (lab.Lab & 'lab=\"LabA\"').fetch1('KEY')\n", + "myproj_key= (lab.Project & 'project=\"ProjA\"').fetch1('KEY')\n", + "myprot_key= (lab.Protocol() & 'protocol=\"ProtA\"').fetch1('KEY')" + ] + }, + { + "cell_type": "markdown", + "id": "902e050c-3133-4fb7-850d-f0b954e1b634", + "metadata": {}, + "source": [ + "Get export function and related pynwb dependency, then export with keys from prev step." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e76fa25d-700b-4f9a-9bb4-62d2217288b6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + }, + { + "data": { + "text/plain": [ + "root pynwb.file.NWBFile at 0x140227470306368\n", + "Fields:\n", + " file_create_date: [datetime.datetime(2021, 12, 1, 15, 14, 12, 86034, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pynwb\n", + "from element_session.export import *\n", + "session_to_nwb(session_key,lab_key=mylab_key,project_key=myproj_key)" + ] + }, + { + "cell_type": "markdown", + "id": "1bccad09-d5e4-4200-bb73-08ddf98cfb80", + "metadata": {}, + "source": [ + "Learn more about using NWB formats [here](https://www.nwb.org/how-to-use/)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/setup.py b/setup.py index 1c10e13..5552179 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ requirements = f.read().splitlines() setup( - name='workflow-animal', + name='workflow-session', version='0.0.1', description="DataJoint Elements for Animal Management", long_description=long_description, From 7fffc41ad90ac0cf709f2b4017671039d2d1cd59 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 3 Jan 2022 15:04:12 -0600 Subject: [PATCH 21/46] Minor; resolve conflicts, same notebooks --- notebooks/1_Explore_Workflow.ipynb | 1617 ++++++++++++++++++++++++++++ notebooks/2_Explore_Export.ipynb | 304 ++++++ 2 files changed, 1921 insertions(+) create mode 100644 notebooks/1_Explore_Workflow.ipynb create mode 100644 notebooks/2_Explore_Export.ipynb diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb new file mode 100644 index 0000000..0a26d52 --- /dev/null +++ b/notebooks/1_Explore_Workflow.ipynb @@ -0,0 +1,1617 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d26010d6-acbc-4c90-8b62-a2448c50452d", + "metadata": {}, + "source": [ + "# DataJoint U24 - Workflow Session" + ] + }, + { + "cell_type": "markdown", + "id": "c5ffe5d2-5b2a-45c3-8d8f-8c20efa8c5eb", + "metadata": {}, + "source": [ + "This notebook will describe the steps to explore the lab and animal management tables created by the elements.\n", + "Prior to using this notebook, please refer to the README for the installation instructions." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4351c4bb-9763-4d4d-8558-37662adc930e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n" + ] + }, + { + "data": { + "text/plain": [ + "DataJoint connection (connected) root@localhost:3306" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# change to the upper level folder to detect dj_local_conf.json\n", + "import os\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')\n", + "import datajoint as dj\n", + "dj.conn()" + ] + }, + { + "cell_type": "markdown", + "id": "ee820754-bceb-476a-acf9-238fa8b201d9", + "metadata": {}, + "source": [ + "Importing the module `workflow_session.pipeline` is sufficient to create tables inside the elements. This workflow comes prepackaged with example data and ingestion functions to populate lab, subject, and session tables." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "868b79bc-f754-4d51-a327-94a209cde374", + "metadata": {}, + "outputs": [], + "source": [ + "from element_lab import lab\n", + "from element_animal import subject\n", + "from element_session import session\n", + "from workflow_session.ingest import ingest_lab, ingest_subjects, ingest_sessions" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9c211f0c-16fd-4d51-abf3-d67bbe271c26", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "markdown", + "id": "2e19116d-bc32-4cea-9caf-f3e8eaa9b181", + "metadata": {}, + "source": [ + "## Workflow architecture" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1e7a0a8b-eaf1-41a1-bf08-1aff2f2812be", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
    \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
    \n", + "

    lab

    \n", + " Abbreviated lab name\n", + "
    \n", + "

    lab_name

    \n", + " full lab name\n", + "
    \n", + "

    institution

    \n", + " \n", + "
    \n", + "

    address

    \n", + " \n", + "
    \n", + "

    time_zone

    \n", + " UTC offset suggested e.g., UTC+1\n", + "
    LabAThe Example LabExample Uni221B Baker St,London NW1 6XE,UKUTC+0
    LabBThe Other LabOther UniOxford OX1 2JD, United KingdomUTC+0
    \n", + " \n", + "

    Total: 2

    \n", + " " + ], + "text/plain": [ + "*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +-----------+\n", + "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", + "LabB The Other Lab Other Uni Oxford OX1 2JD UTC+0 \n", + " (Total: 2)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lab.Lab()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "63679df4-3064-402b-99ce-2f553dff877b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "`neuro_lab`.`#skull_reference`\n", + "\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "lab.Project\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Publication\n", + "\n", + "\n", + "lab.Project.Publication\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.Project.Publication\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Keywords\n", + "\n", + "\n", + "lab.Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.Project.Keywords\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project.Sourcecode\n", + "\n", + "\n", + "lab.Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Project->lab.Project.Sourcecode\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment.EphysEquipment\n", + "\n", + "\n", + "lab.Equipment.EphysEquipment\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Protocol\n", + "\n", + "\n", + "lab.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType->lab.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment\n", + "\n", + "\n", + "lab.Equipment\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment->lab.Equipment.EphysEquipment\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment.CaImgEquipment\n", + "\n", + "\n", + "lab.Equipment.CaImgEquipment\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Equipment->lab.Equipment.CaImgEquipment\n", + "\n", + "\n", + "\n", + "\n", + "lab.Lab\n", + "\n", + "\n", + "lab.Lab\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Lab->lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.Lab->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "lab.User\n", + "\n", + "\n", + "lab.User\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.User->lab.ProjectUser\n", + "\n", + "\n", + "\n", + "\n", + "lab.User->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "lab.UserRole\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", + "\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.Diagram(lab)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8cf0f64b-e523-4a94-9a43-fca4ed793f82", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Animal Subject\n", + "
    \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
    \n", + "

    subject

    \n", + " \n", + "
    \n", + "

    sex

    \n", + " \n", + "
    \n", + "

    subject_birth_date

    \n", + " \n", + "
    \n", + "

    subject_description

    \n", + " \n", + "
    subject1M2020-12-30test animal
    subject2F2020-11-30test animal
    subject3F2020-12-30test animal
    subject4M2021-02-12test animal
    subject5F2020-01-03lmash_E105
    subject6M2020-01-03hneih_E105
    subject7U2020-08-30test animal
    subject8F2020-09-30test animal
    \n", + " \n", + "

    Total: 8

    \n", + " " + ], + "text/plain": [ + "*subject sex subject_birth_ subject_descri\n", + "+----------+ +-----+ +------------+ +------------+\n", + "subject1 M 2020-12-30 test animal \n", + "subject2 F 2020-11-30 test animal \n", + "subject3 F 2020-12-30 test animal \n", + "subject4 M 2021-02-12 test animal \n", + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", + "subject7 U 2020-08-30 test animal \n", + "subject8 F 2020-09-30 test animal \n", + " (Total: 8)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject.Subject()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "75576be2-2984-451f-a86b-f05f9ddec6b7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "subject.Allele\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Strain->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "subject.Subject\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Zygosity\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "subject.Subject.Source\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Source\n", + "\n", + "\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Subject.Line\n", + "\n", + "\n", + "\n", + "\n", + "subject.Line->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.Diagram(subject)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5243a782-93da-40fa-b243-03ddcb230c1d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
    \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "
    \n", + "

    subject

    \n", + " \n", + "
    \n", + "

    session_datetime

    \n", + " \n", + "
    subject32021-04-30 12:22:15.032000
    subject52020-04-15 11:16:38
    subject62021-01-15 11:16:38
    subject62021-06-02 14:04:22
    \n", + " \n", + "

    Total: 4

    \n", + " " + ], + "text/plain": [ + "*subject *session_datet\n", + "+----------+ +------------+\n", + "subject3 2021-04-30 12:\n", + "subject5 2020-04-15 11:\n", + "subject6 2021-01-15 11:\n", + "subject6 2021-06-02 14:\n", + " (Total: 4)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "session.Session()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7e48d7c0-b7bd-4f0b-abcb-1aedc69d5310", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "session.Session\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", + "\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "session.SessionDirectory\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionDirectory\n", + "\n", + "\n", + "\n", + "\n", + "session.SessionExperimenter\n", + "\n", + "\n", + "session.SessionExperimenter\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionExperimenter\n", + "\n", + "\n", + "\n", + "\n", + "session.SessionNote\n", + "\n", + "\n", + "session.SessionNote\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "session.Session->session.SessionNote\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dj.Diagram(session)" + ] + }, + { + "cell_type": "markdown", + "id": "c510fe4d-09ed-472f-830f-4401bd6830d0", + "metadata": {}, + "source": [ + "(Workflow needs continued development to import geotyping tables)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1984077d-a9f2-4b17-8034-ee2af8f105f4", + "metadata": {}, + "outputs": [], + "source": [ + "# dj.Diagram(genotyping) + dj.Diagram(subject.Subject) + dj.Diagram(subject.Allele)" + ] + }, + { + "cell_type": "markdown", + "id": "b60f5f4c-d366-4034-a40d-2d2095cb2a14", + "metadata": {}, + "source": [ + "## Explore each table" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9c0821e1-9125-4c41-bc9c-567f53d0a5e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Animal Subject\n", + "subject : varchar(32) \n", + "---\n", + "sex : enum('M','F','U') \n", + "subject_birth_date : date \n", + "subject_description=\"\" : varchar(1024) \n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "'# Animal Subject\\nsubject : varchar(32) \\n---\\nsex : enum(\\'M\\',\\'F\\',\\'U\\') \\nsubject_birth_date : date \\nsubject_description=\"\" : varchar(1024) \\n'" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# check table definition with describe()\n", + "subject.Subject.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "504d55ed-25bd-4bb1-bce9-c516bd3595df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-> subject.Subject\n", + "-> subject.Allele\n", + "---\n", + "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "\"-> subject.Subject\\n-> subject.Allele\\n---\\nzygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\\n\"" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# check table definition with dependencies with describe()\n", + "subject.Zygosity.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "1edded31-bdef-42fb-8f58-b42b9b186cf1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "# \n", + "subject : varchar(32) # \n", + "allele : varchar(32) # abbreviated allele name\n", + "---\n", + "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# check the name of every attribute with heading, \n", + "# which will spell out the foreign key definition inherited from another table\n", + "subject.Zygosity.heading" + ] + }, + { + "cell_type": "markdown", + "id": "f6c110c0-0966-4283-a0ba-a7de2ce69e25", + "metadata": {}, + "source": [ + "## Insert data into Manual and Lookup tables" + ] + }, + { + "cell_type": "markdown", + "id": "54cf050e-882e-4672-be31-1ca3df52fa58", + "metadata": {}, + "source": [ + "Tables in this workflow are either manual tables or lookup tables. To insert into these tables, DataJoint provide method `.insert1()` and `insert()`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d5b43904-9711-4bce-8ae5-d0d797118dec", + "metadata": {}, + "outputs": [], + "source": [ + "subject.Subject.insert1(\n", + " dict(subject='subject1', sex='M', subject_birth_date='2020-12-30', \n", + " subject_description='test animal'), skip_duplicates=True)\n", + "subject.Subject.insert1(\n", + " ('subject2', 'F', '2020-11-30', 'test animal'), skip_duplicates=True)" + ] + }, + { + "cell_type": "markdown", + "id": "49d43ca2-2cd3-4659-849f-5bcc09c1367e", + "metadata": {}, + "source": [ + "`skip_duplicates=True` will prevent an error if you already have data for the primary keys in a given entry." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "9bf2c953-7b4c-4a70-99fd-124a4d28171b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Animal Subject\n", + "
    \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
    \n", + "

    subject

    \n", + " \n", + "
    \n", + "

    sex

    \n", + " \n", + "
    \n", + "

    subject_birth_date

    \n", + " \n", + "
    \n", + "

    subject_description

    \n", + " \n", + "
    subject1M2020-12-30test animal
    subject2F2020-11-30test animal
    subject3F2020-12-30test animal
    subject4M2021-02-12test animal
    subject5F2020-01-03lmash_E105
    subject6M2020-01-03hneih_E105
    subject7U2020-08-30test animal
    subject8F2020-09-30test animal
    \n", + " \n", + "

    Total: 8

    \n", + " " + ], + "text/plain": [ + "*subject sex subject_birth_ subject_descri\n", + "+----------+ +-----+ +------------+ +------------+\n", + "subject1 M 2020-12-30 test animal \n", + "subject2 F 2020-11-30 test animal \n", + "subject3 F 2020-12-30 test animal \n", + "subject4 M 2021-02-12 test animal \n", + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", + "subject7 U 2020-08-30 test animal \n", + "subject8 F 2020-09-30 test animal \n", + " (Total: 8)" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject.Subject()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "7a10ddab-d0fd-45a0-8183-09c1b1933e0a", + "metadata": {}, + "outputs": [], + "source": [ + "# `insert()` takes a list of dicts or tuples\n", + "subject.Subject.insert(\n", + " [dict(subject='subject3', sex='F', subject_birth_date='2020-12-30', \n", + " subject_description='test animal'),\n", + " dict(subject='subject4', sex='M', subject_birth_date='2021-02-12', \n", + " subject_description='test animal')\n", + " ],\n", + " skip_duplicates=True)\n", + "subject.Subject.insert(\n", + " [\n", + " ('subject7', 'U', '2020-08-30', 'test animal'),\n", + " ('subject8', 'F', '2020-09-30', 'test animal')\n", + " ],\n", + " skip_duplicates=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "064ddaae-3410-47fc-be22-671d2afe7fb6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " Animal Subject\n", + "
    \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
    \n", + "

    subject

    \n", + " \n", + "
    \n", + "

    sex

    \n", + " \n", + "
    \n", + "

    subject_birth_date

    \n", + " \n", + "
    \n", + "

    subject_description

    \n", + " \n", + "
    subject1M2020-12-30test animal
    subject2F2020-11-30test animal
    subject3F2020-12-30test animal
    subject4M2021-02-12test animal
    subject5F2020-01-03lmash_E105
    subject6M2020-01-03hneih_E105
    subject7U2020-08-30test animal
    subject8F2020-09-30test animal
    \n", + " \n", + "

    Total: 8

    \n", + " " + ], + "text/plain": [ + "*subject sex subject_birth_ subject_descri\n", + "+----------+ +-----+ +------------+ +------------+\n", + "subject1 M 2020-12-30 test animal \n", + "subject2 F 2020-11-30 test animal \n", + "subject3 F 2020-12-30 test animal \n", + "subject4 M 2021-02-12 test animal \n", + "subject5 F 2020-01-03 lmash_E105 \n", + "subject6 M 2020-01-03 hneih_E105 \n", + "subject7 U 2020-08-30 test animal \n", + "subject8 F 2020-09-30 test animal \n", + " (Total: 8)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subject.Subject()" + ] + }, + { + "cell_type": "markdown", + "id": "c47691a0-b016-4092-a5ad-fefff93c54dd", + "metadata": {}, + "source": [ + "For more documentation of insert, please refer to [DataJoint Docs](https://docs.datajoint.io/python/manipulation/1-Insert.html) and [DataJoint playground](https://playground.datajoint.io/)" + ] + }, + { + "cell_type": "markdown", + "id": "13f8a8ed-2656-46d8-82ba-cdf130c4873e", + "metadata": {}, + "source": [ + "## Insert into Manual and Lookup tables with Graphical User Interface" + ] + }, + { + "cell_type": "markdown", + "id": "4775dd80-8a54-47b7-a9ba-99995db9ff1a", + "metadata": {}, + "source": [ + "DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](../images/DataJoint_Labbook.png)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb new file mode 100644 index 0000000..bc54ae6 --- /dev/null +++ b/notebooks/2_Explore_Export.ipynb @@ -0,0 +1,304 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3054518f-87bc-42ff-a3e7-84bf3d2a37f6", + "metadata": {}, + "source": [ + "# DataJoint U24 - Export Session" + ] + }, + { + "cell_type": "markdown", + "id": "79c15f36-039d-4304-96be-f56ba0d6b10a", + "metadata": {}, + "source": [ + "Same as before, import data." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0e7fa407-d67b-403e-975c-bd0bd499d88c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6f11ca71-5e4f-460c-ad94-2037ef0f6448", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting root@localhost:3306\n", + "\n", + "---- Insert 2 entry(s) into lab tables ----\n", + "\n", + "---- Insert 2 entry(s) into project table ----\n", + "\n", + "---- Insert entry(s) into publication/keyword tables ----\n", + "\n", + "---- Insert 2 entry(s) into protocol tables ----\n", + "\n", + "---- Insert 2 entry(s) into subject tables ----\n", + "\n", + "---- Insert 2 entry(s) into session.Session ----\n" + ] + } + ], + "source": [ + "import datajoint as dj\n", + "dj.conn()\n", + "from element_lab import lab\n", + "from element_animal import subject\n", + "from element_session import session\n", + "from workflow_session.ingest import ingest_lab, ingest_subjects, ingest_sessions\n", + "## delete if needed, to update/repopulate\n", + "# lab.Lab.delete();lab.Project.delete();lab.Protocol.delete()\n", + "ingest_lab(); ingest_subjects();ingest_sessions()" + ] + }, + { + "cell_type": "markdown", + "id": "ab2a3f10-b96e-4f0d-9e54-0015bb9b8622", + "metadata": {}, + "source": [ + "Identify items for export with keys." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "76c040c8-15cc-4d61-ae5d-bc7646a6a0be", + "metadata": {}, + "outputs": [], + "source": [ + "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')" + ] + }, + { + "cell_type": "markdown", + "id": "902e050c-3133-4fb7-850d-f0b954e1b634", + "metadata": {}, + "source": [ + "Get export from element-session, then export with keys from prev step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", + "metadata": {}, + "outputs": [], + "source": [ + "from element_session.export import session_to_nwb" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "fdc6d3c7-8f4d-41c8-af6f-5c63ab68e316", + "metadata": {}, + "outputs": [ + { + "ename": "DataJointError", + "evalue": "fetch1 requires exactly one tuple in the input set.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_1373/2627209259.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubject\u001b[0m\u001b[0;34m&\u001b[0m\u001b[0;34m\"subject!='subject5'\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/fetch.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, squeeze, download_path, *attrs)\u001b[0m\n\u001b[1;32m 246\u001b[0m \u001b[0mret\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 247\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mret\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 248\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDataJointError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'fetch1 requires exactly one tuple in the input set.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 249\u001b[0m ret = dict((name, _get(self._expression.connection, heading[name], ret[name],\n\u001b[1;32m 250\u001b[0m squeeze=squeeze, download_path=download_path))\n", + "\u001b[0;31mDataJointError\u001b[0m: fetch1 requires exactly one tuple in the input set." + ] + } + ], + "source": [ + "(subject.Subject&\"subject!='subject5'\").fetch1()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e76fa25d-700b-4f9a-9bb4-62d2217288b6", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + } + ], + "source": [ + "mynwbfile=session_to_nwb(session_key)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "56ebdd79-f0dc-4925-aeb1-109887df361d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on function elementlab_nwb_dict in module element_lab.export.nwb:\n", + "\n", + "elementlab_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", + " Generate a dictionary object containing all relevant lab information used when\n", + " generating an NWB file at the session level. All parameters optional.\n", + " Use: mynwbfile = NWBfile(identifier=\"your identifier\",\n", + " session_description=\"your description\",\n", + " session_start_time=session_datetime,\n", + " elementlab_nwb_dict(lab_key=key1,project_key=key2,protocol_key=key3))\n", + " Note: The lab, project and protocol keys should specify one of their respective types.\n", + " \n", + " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", + " :param project_key: Key specifying one entry in element_lab.lab.Project\n", + " :param protocol_key: Key specifying one entry in element_lab.lab.PRotocol\n", + " :return: dictionary with NWB parameters\n", + "\n" + ] + } + ], + "source": [ + "from element_lab.export import elementlab_nwb_dict\n", + "help(elementlab_nwb_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "afc6555d-cd25-4d55-93e2-51c72ddba9ae", + "metadata": {}, + "outputs": [], + "source": [ + "from pynwb import NWBFile\n", + "lab_info = elementlab_nwb_dict(lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)\n", + "sess_info = session_to_nwb_dict(session_key)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "97e01cbf-b225-45d3-a030-0f7400e3526b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" + ] + } + ], + "source": [ + "mynwbfile = NWBFile(**sess_info,**lab_info)" + ] + }, + { + "cell_type": "markdown", + "id": "1bccad09-d5e4-4200-bb73-08ddf98cfb80", + "metadata": {}, + "source": [ + "Learn more about using NWB formats [here](https://www.nwb.org/how-to-use/)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b914d8db-2584-403c-9f24-12a64103f1cb", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/build/objectmapper.py:653: MissingRequiredBuildWarning: NWBFile 'root' is missing required value for attribute 'source_script_file_name'.\n", + " warnings.warn(msg, MissingRequiredBuildWarning)\n" + ] + } + ], + "source": [ + "from pynwb import NWBHDF5IO\n", + "with NWBHDF5IO('session_metadata.nwb', mode='w') as io:\n", + " io.write(mynwbfile)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "c81b1c85-9623-4cc1-9438-79b0c5287756", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root pynwb.file.NWBFile at 0x140190306285216\n", + "Fields:\n", + " experiment_description: Example project to populate element-lab\n", + " file_create_date: [datetime.datetime(2021, 12, 6, 17, 1, 11, 974467, tzinfo=tzlocal())]\n", + " identifier: subject5_20200415_111638\n", + " institution: Example Uni\n", + " keywords: ['Example' 'Study']\n", + " lab: The Example Lab\n", + " pharmacology: Subjects were administered 10ul sedative prior to surgery\n", + " protocol: ProtA\n", + " related_publications: ['arXiv:1807.11104' 'arXiv:1807.11104v1']\n", + " session_description: Successful data collection, no notes\n", + " session_start_time: 2020-04-15 11:16:38-05:00\n", + " source_script: https://github.com/datajoint/element-lab/\n", + " surgery: Craniotomy performed by session experimenter\n", + " timestamps_reference_time: 2020-04-15 11:16:38-05:00\n", + "\n" + ] + } + ], + "source": [ + "print(mynwbfile)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e75c3795-96d6-4ed1-889e-3da58d9e8533", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv-nwb", + "language": "python", + "name": "venv-nwb" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From 292a692a1b47811dd8f2612ea9f2c580021f1dd5 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 7 Jan 2022 18:17:41 -0600 Subject: [PATCH 22/46] remove old diagrams, update README links --- README.md | 6 +- images/genotyping_diagram_old.svg | 138 --------------------- images/lab_diagram.svg | 179 ---------------------------- images/lab_diagram_old.svg | 130 -------------------- images/subject_diagram_old.svg | 191 ------------------------------ workflow_session/ingest.py | 4 +- 6 files changed, 5 insertions(+), 643 deletions(-) delete mode 100644 images/genotyping_diagram_old.svg delete mode 100644 images/lab_diagram.svg delete mode 100644 images/lab_diagram_old.svg delete mode 100644 images/subject_diagram_old.svg diff --git a/README.md b/README.md index c067c1a..987f531 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,14 @@ element-lab is used for all lab-general metadata, including personnel and projec element-animal contains two modules, `subject` and `genotyping`. `subject` contains basic information of subjects. -![subject](images/subject_diagram2.svg) +![subject](images/subject_diagram.svg) `genotyping` is designed for labs that handle animal care and genetic information themselves, which is optional. -![genotyping](images/genotyping_diagram2.svg) +![genotyping](images/genotyping_diagram.svg) ### element-session `session` is designed to handle metadata related to data collection, including collection date-time, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. -![session](images/session_diagram2.png) +![session](images/session_diagram.png) ### This workflow This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements in tandem with other data collection modalities, refer to: diff --git a/images/genotyping_diagram_old.svg b/images/genotyping_diagram_old.svg deleted file mode 100644 index a6d6471..0000000 --- a/images/genotyping_diagram_old.svg +++ /dev/null @@ -1,138 +0,0 @@ - - - - - -`genotyping`.`breeding_pair__father` - -`genotyping`.`breeding_pair__father` - - - -`genotyping`.`breeding_pair__mother` - -`genotyping`.`breeding_pair__mother` - - - -genotyping.AlleleSequence - - -genotyping.AlleleSequence - - - - - -genotyping.BreedingPair - - -genotyping.BreedingPair - - - - - -genotyping.BreedingPair->`genotyping`.`breeding_pair__father` - - - - -genotyping.BreedingPair->`genotyping`.`breeding_pair__mother` - - - - -genotyping.Litter - - -genotyping.Litter - - - - - -genotyping.BreedingPair->genotyping.Litter - - - - -genotyping.SubjectLitter - - -genotyping.SubjectLitter - - - - - -genotyping.Weaning - - -genotyping.Weaning - - - - - -genotyping.Litter->genotyping.SubjectLitter - - - - -genotyping.Litter->genotyping.Weaning - - - - -genotyping.SubjectCaging - - -genotyping.SubjectCaging - - - - - -genotyping.GenotypeTest - - -genotyping.GenotypeTest - - - - - -genotyping.Sequence - - -genotyping.Sequence - - - - - -genotyping.Sequence->genotyping.AlleleSequence - - - - -genotyping.Sequence->genotyping.GenotypeTest - - - - -genotyping.Cage - - -genotyping.Cage - - - - - -genotyping.Cage->genotyping.SubjectCaging - - - - \ No newline at end of file diff --git a/images/lab_diagram.svg b/images/lab_diagram.svg deleted file mode 100644 index f13349d..0000000 --- a/images/lab_diagram.svg +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - -lab.Protocol - - -lab.Protocol - - - - - -lab.Project - - -lab.Project - - - - - -lab.ProjectUser - - -lab.ProjectUser - - - - - -lab.Project->lab.ProjectUser - - - - -lab.Project.Sourcecode - - -lab.Project.Sourcecode - - - - - -lab.Project->lab.Project.Sourcecode - - - - -lab.Project.Keywords - - -lab.Project.Keywords - - - - - -lab.Project->lab.Project.Keywords - - - - -lab.Project.Publication - - -lab.Project.Publication - - - - - -lab.Project->lab.Project.Publication - - - - -lab.Location - - -lab.Location - - - - - -lab.UserRole - - -lab.UserRole - - - - - -lab.LabMembership - - -lab.LabMembership - - - - - -lab.UserRole->lab.LabMembership - - - - -lab.Source - - -lab.Source - - - - - -lab.ProtocolType - - -lab.ProtocolType - - - - - -lab.ProtocolType->lab.Protocol - - - - -lab.Lab - - -lab.Lab - - - - - -lab.Lab->lab.Location - - - - -lab.Lab->lab.LabMembership - - - - -lab.User - - -lab.User - - - - - -lab.User->lab.ProjectUser - - - - -lab.User->lab.LabMembership - - - - diff --git a/images/lab_diagram_old.svg b/images/lab_diagram_old.svg deleted file mode 100644 index b954e4c..0000000 --- a/images/lab_diagram_old.svg +++ /dev/null @@ -1,130 +0,0 @@ - - - - - -Lab - - -Lab - - - - - -lab.LabMembership - - -lab.LabMembership - - - - - -Lab->lab.LabMembership - - - - -Location - - -Location - - - - - -Lab->Location - - - - -lab.Project - - -lab.Project - - - - - -lab.ProjectUser - - -lab.ProjectUser - - - - - -lab.Project->lab.ProjectUser - - - - -lab.UserRole - - -lab.UserRole - - - - - -lab.UserRole->lab.LabMembership - - - - -lab.ProtocolType - - -lab.ProtocolType - - - - - -Protocol - - -Protocol - - - - - -lab.ProtocolType->Protocol - - - - -Source - - -Source - - - - - -User - - -User - - - - - -User->lab.ProjectUser - - - - -User->lab.LabMembership - - - - \ No newline at end of file diff --git a/images/subject_diagram_old.svg b/images/subject_diagram_old.svg deleted file mode 100644 index ecb6c89..0000000 --- a/images/subject_diagram_old.svg +++ /dev/null @@ -1,191 +0,0 @@ - - - - - -`subject`.`subject__protocol` - -`subject`.`subject__protocol` - - - -`subject`.`#allele__source` - -`subject`.`#allele__source` - - - -`subject`.`subject__source` - -`subject`.`subject__source` - - - -`subject`.`#line__allele` - -`subject`.`#line__allele` - - - -`subject`.`subject__strain` - -`subject`.`subject__strain` - - - -`subject`.`subject__line` - -`subject`.`subject__line` - - - -`subject`.`subject__lab` - -`subject`.`subject__lab` - - - -`subject`.`subject__user` - -`subject`.`subject__user` - - - -subject.Subject - - -subject.Subject - - - - - -subject.Subject->`subject`.`subject__protocol` - - - - -subject.Subject->`subject`.`subject__source` - - - - -subject.Subject->`subject`.`subject__strain` - - - - -subject.Subject->`subject`.`subject__line` - - - - -subject.Subject->`subject`.`subject__lab` - - - - -subject.Subject->`subject`.`subject__user` - - - - -subject.SubjectDeath - - -subject.SubjectDeath - - - - - -subject.Subject->subject.SubjectDeath - - - - -subject.SubjectCullMethod - - -subject.SubjectCullMethod - - - - - -subject.Subject->subject.SubjectCullMethod - - - - -subject.Zygosity - - -subject.Zygosity - - - - - -subject.Subject->subject.Zygosity - - - - -subject.Strain - - -subject.Strain - - - - - -subject.Strain->`subject`.`subject__strain` - - - - -subject.Allele - - -subject.Allele - - - - - -subject.Allele->`subject`.`#allele__source` - - - - -subject.Allele->`subject`.`#line__allele` - - - - -subject.Allele->subject.Zygosity - - - - -subject.Line - - -subject.Line - - - - - -subject.Line->`subject`.`#line__allele` - - - - -subject.Line->`subject`.`subject__line` - - - - \ No newline at end of file diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index a28e950..09b9682 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -20,8 +20,8 @@ def ingest_general(csvs, tables, # Ignore Extra because some CSVs feed/mult tables ignore_extra_fields=True) insert_len = len(table) - prev_len # report length change - print(f'\n---- Inserting {insert_len} entry(s) \ - into {table.table_name} ----') + print(f'\n---- Inserting {insert_len} entry(s) ' + + f'into {table.table_name} ----') # Future enhancement: permit embedded lists # Currently requires a csv to be listed multiple times From 20052431aa253eceef3b05c04027c2d0949121d2 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 11 Jan 2022 12:00:06 -0600 Subject: [PATCH 23/46] Removed lab.Project refs to slices, viruses, etc. - see details As discussed in [element-session issue #9](https://github.com/datajoint/element-session/issues/9), moving pharmacology, slices, surgery, and virus to session level. Here, removed from - projects.csv - ingest.py - notebooks 2_Explore_Export.ipynb will need more changes after [element-session PR #7](https://github.com/datajoint/element-session/pull/7) is finalized --- notebooks/1_Explore_Workflow.ipynb | 939 +++++++++++++---------------- notebooks/2_Explore_Export.ipynb | 80 +-- user_data/lab/projects.csv | 6 +- workflow_session/ingest.py | 6 +- 4 files changed, 482 insertions(+), 549 deletions(-) diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index 0a26d52..57153f2 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "4351c4bb-9763-4d4d-8558-37662adc930e", "metadata": {}, "outputs": [ @@ -36,7 +36,7 @@ "DataJoint connection (connected) root@localhost:3306" ] }, - "execution_count": 2, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -59,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "868b79bc-f754-4d51-a327-94a209cde374", "metadata": {}, "outputs": [], @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "9c211f0c-16fd-4d51-abf3-d67bbe271c26", "metadata": {}, "outputs": [ @@ -81,17 +81,41 @@ "output_type": "stream", "text": [ "\n", - "---- Insert 2 entry(s) into lab tables ----\n", + "---- Inserting 0 entry(s) into #lab ----\n", + "\n", + "---- Inserting 0 entry(s) into #location ----\n", + "\n", + "---- Inserting 0 entry(s) into #project ----\n", + "\n", + "---- Inserting 2 entry(s) into #project__source_code ----\n", + "\n", + "---- Inserting 2 entry(s) into #project__publication ----\n", + "\n", + "---- Inserting 3 entry(s) into #project__keywords ----\n", + "\n", + "---- Inserting 2 entry(s) into #protocol_type ----\n", "\n", - "---- Insert 2 entry(s) into project table ----\n", + "---- Inserting 2 entry(s) into #protocol ----\n", "\n", - "---- Insert entry(s) into publication/keyword tables ----\n", + "---- Inserting 2 entry(s) into #user_role ----\n", "\n", - "---- Insert 2 entry(s) into protocol tables ----\n", + "---- Inserting 3 entry(s) into #user ----\n", "\n", - "---- Insert 2 entry(s) into subject tables ----\n", + "---- Inserting 3 entry(s) into #lab_membership ----\n", "\n", - "---- Insert 2 entry(s) into session.Session ----\n" + "---- Inserting 4 entry(s) into project_user ----\n", + "\n", + "---- Inserting 2 entry(s) into subject ----\n", + "\n", + "---- Inserting 2 entry(s) into subject_death ----\n", + "\n", + "---- Inserting 2 entry(s) into subject_cull_method ----\n", + "\n", + "---- Inserting 2 entry(s) into session ----\n", + "\n", + "---- Inserting 2 entry(s) into session_directory ----\n", + "\n", + "---- Inserting 2 entry(s) into session_note ----\n" ] } ], @@ -109,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "1e7a0a8b-eaf1-41a1-bf08-1aff2f2812be", "metadata": {}, "outputs": [ @@ -183,31 +207,31 @@ " \n", "
    \n", "

    time_zone

    \n", - " UTC offset suggested e.g., UTC+1\n", + " \n", "
    \n", " LabA\n", "The Example Lab\n", "Example Uni\n", - "221B Baker St,London NW1 6XE,UK\n", - "UTC+0LabB\n", + "'221B Baker St\n", + "London NW1 6XELabB\n", "The Other Lab\n", "Other Uni\n", - "Oxford OX1 2JD, United Kingdom\n", - "UTC+0 \n", + "'Oxford OX1 2JD\n", + " United Kingdom' \n", " \n", " \n", "

    Total: 2

    \n", " " ], "text/plain": [ - "*lab lab_name institution address time_zone \n", - "+------+ +------------+ +------------+ +------------+ +-----------+\n", - "LabA The Example La Example Uni 221B Baker St, UTC+0 \n", - "LabB The Other Lab Other Uni Oxford OX1 2JD UTC+0 \n", + "*lab lab_name institution address time_zone \n", + "+------+ +------------+ +------------+ +------------+ +------------+\n", + "LabA The Example La Example Uni '221B Baker St London NW1 6XE\n", + "LabB The Other Lab Other Uni 'Oxford OX1 2J United Kingdo\n", " (Total: 2)" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -218,289 +242,233 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "63679df4-3064-402b-99ce-2f553dff877b", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "`neuro_lab`.`#skull_reference`\n", - "\n", - "`neuro_lab`.`#skull_reference`\n", - "\n", - "\n", - "\n", - "lab.Project\n", - "lab.UserRole\n", + "\n", - "\n", - "lab.Project\n", + "\n", + "lab.UserRole\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.ProjectUser\n", - "\n", + "\n", + "lab.LabMembership\n", + "\n", - "\n", - "lab.ProjectUser\n", + "\n", + "lab.LabMembership\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "lab.Project->lab.ProjectUser\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project.Publication\n", - "\n", - "\n", - "lab.Project.Publication\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.Project.Publication\n", - "\n", + "lab.UserRole->lab.LabMembership\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.Project.Keywords\n", - "\n", - "\n", - "lab.Project.Keywords\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project->lab.Project.Keywords\n", - "\n", - "\n", - "\n", - "\n", - "lab.Project.Sourcecode\n", - "\n", - "\n", - "lab.Project.Sourcecode\n", + "\n", + "lab.Project.Keywords\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Project->lab.Project.Sourcecode\n", - "\n", - "\n", - "\n", + "\n", "\n", - "lab.Equipment.EphysEquipment\n", - "lab.Project\n", + "\n", - "\n", - "lab.Equipment.EphysEquipment\n", + "\n", + "lab.Project\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.ProtocolType\n", - "\n", - "\n", - "lab.ProtocolType\n", - "\n", - "\n", + "\n", + "\n", + "lab.Project->lab.Project.Keywords\n", + "\n", "\n", - "\n", - "\n", - "lab.Protocol\n", - "\n", + "\n", + "lab.ProjectUser\n", + "\n", - "\n", - "lab.Protocol\n", + "\n", + "lab.ProjectUser\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.ProtocolType->lab.Protocol\n", - "\n", + "\n", + "\n", + "lab.Project->lab.ProjectUser\n", + "\n", "\n", - "\n", - "\n", - "lab.Equipment\n", - "\n", + "\n", + "lab.Project.Publication\n", + "\n", - "\n", - "lab.Equipment\n", + "\n", + "lab.Project.Publication\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Equipment->lab.Equipment.EphysEquipment\n", - "\n", + "\n", + "\n", + "lab.Project->lab.Project.Publication\n", + "\n", "\n", - "\n", + "\n", "\n", - "lab.Equipment.CaImgEquipment\n", - "lab.Project.SourceCode\n", + "\n", - "\n", - "lab.Equipment.CaImgEquipment\n", + "\n", + "lab.Project.SourceCode\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Equipment->lab.Equipment.CaImgEquipment\n", - "\n", - "\n", - "\n", - "\n", - "lab.Lab\n", - "\n", - "\n", - "lab.Lab\n", - "\n", - "\n", + "\n", + "\n", + "lab.Project->lab.Project.SourceCode\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.Location\n", - "\n", - "\n", - "lab.Location\n", + "\n", + "lab.Location\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Lab->lab.Location\n", - "\n", - "\n", - "\n", - "\n", - "lab.LabMembership\n", - "\n", + "\n", + "lab.Protocol\n", + "\n", - "\n", - "lab.LabMembership\n", + "\n", + "lab.Protocol\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.Lab->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", + "lab.Source\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.User\n", - "\n", - "\n", - "lab.User\n", + "\n", + "lab.User\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "lab.User->lab.ProjectUser\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "lab.User->lab.LabMembership\n", - "\n", + "\n", "\n", - "\n", - "\n", - "lab.UserRole\n", - "\n", + "\n", + "lab.Lab\n", + "\n", - "\n", - "lab.UserRole\n", + "\n", + "lab.Lab\n", "\n", "\n", "\n", - "\n", - "\n", - "lab.UserRole->lab.LabMembership\n", - "\n", + "\n", + "\n", + "lab.Lab->lab.LabMembership\n", + "\n", "\n", - "\n", - "\n", - "lab.Source\n", - "\n", + "\n", + "lab.Lab->lab.Location\n", + "\n", + "\n", + "\n", + "\n", + "lab.ProtocolType\n", + "\n", - "\n", - "lab.Source\n", + "\n", + "lab.ProtocolType\n", "\n", "\n", "\n", + "\n", + "\n", + "lab.ProtocolType->lab.Protocol\n", + "\n", + "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -511,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "8cf0f64b-e523-4a94-9a43-fca4ed793f82", "metadata": {}, "outputs": [ @@ -584,51 +552,27 @@ "

    subject_description

    \n", " \n", " \n", - " subject1\n", - "M\n", - "2020-12-30\n", - "test animalsubject2\n", + " subject5\n", "F\n", - "2020-11-30\n", - "test animalsubject3\n", - "F\n", - "2020-12-30\n", - "test animalsubject4\n", - "M\n", - "2021-02-12\n", - "test animalsubject5\n", - "F\n", - "2020-01-03\n", - "lmash_E105subject6\n", + "2020-01-01\n", + "richsubject6\n", "M\n", - "2020-01-03\n", - "hneih_E105subject7\n", - "U\n", - "2020-08-30\n", - "test animalsubject8\n", - "F\n", - "2020-09-30\n", - "test animal \n", + "2020-01-01\n", + "manuel \n", " \n", " \n", - "

    Total: 8

    \n", + "

    Total: 2

    \n", " " ], "text/plain": [ "*subject sex subject_birth_ subject_descri\n", "+----------+ +-----+ +------------+ +------------+\n", - "subject1 M 2020-12-30 test animal \n", - "subject2 F 2020-11-30 test animal \n", - "subject3 F 2020-12-30 test animal \n", - "subject4 M 2021-02-12 test animal \n", - "subject5 F 2020-01-03 lmash_E105 \n", - "subject6 M 2020-01-03 hneih_E105 \n", - "subject7 U 2020-08-30 test animal \n", - "subject8 F 2020-09-30 test animal \n", - " (Total: 8)" + "subject5 F 2020-01-01 rich \n", + "subject6 M 2020-01-01 manuel \n", + " (Total: 2)" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -639,196 +583,181 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "75576be2-2984-451f-a86b-f05f9ddec6b7", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "subject.Allele\n", - "subject.Subject.Strain\n", + "\n", - "\n", - "subject.Allele\n", + "\n", + "subject.Subject.Strain\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Zygosity\n", - "\n", + "\n", + "subject.Strain\n", + "\n", - "\n", - "subject.Zygosity\n", + "\n", + "subject.Strain\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Allele->subject.Zygosity\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele.Source\n", - "\n", - "\n", - "subject.Allele.Source\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "subject.Allele->subject.Allele.Source\n", - "\n", + "subject.Strain->subject.Subject.Strain\n", + "\n", "\n", - "\n", - "\n", - "subject.Line.Allele\n", - "\n", + "\n", + "subject.Subject.Protocol\n", + "\n", - "\n", - "subject.Line.Allele\n", + "\n", + "subject.Subject.Protocol\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Allele->subject.Line.Allele\n", - "\n", - "\n", "\n", - "\n", + "\n", "subject.Subject.Line\n", - "\n", - "\n", - "subject.Subject.Line\n", + "\n", + "subject.Subject.Line\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject.User\n", - "\n", + "\n", + "subject.Line.Allele\n", + "\n", - "\n", - "subject.Subject.User\n", + "\n", + "subject.Line.Allele\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Strain\n", - "\n", + "\n", + "subject.Allele\n", + "\n", - "\n", - "subject.Strain\n", + "\n", + "subject.Allele\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject.Strain\n", - "\n", + "\n", + "subject.Allele->subject.Line.Allele\n", + "\n", + "\n", + "\n", + "\n", + "subject.Allele.Source\n", + "\n", - "\n", - "subject.Subject.Strain\n", + "\n", + "subject.Allele.Source\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Strain->subject.Subject.Strain\n", - "\n", + "\n", + "\n", + "subject.Allele->subject.Allele.Source\n", + "\n", "\n", - "\n", - "\n", - "subject.SubjectDeath\n", - "\n", + "\n", + "subject.Zygosity\n", + "\n", - "\n", - "subject.SubjectDeath\n", + "\n", + "subject.Zygosity\n", "\n", "\n", "\n", - "\n", - "\n", - "subject.Subject.Protocol\n", - "\n", - "\n", - "subject.Subject.Protocol\n", - "\n", - "\n", + "\n", + "\n", + "subject.Allele->subject.Zygosity\n", + "\n", "\n", "\n", - "\n", + "\n", "subject.Subject\n", - "\n", - "\n", - "subject.Subject\n", + "\n", + "subject.Subject\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Line\n", - "\n", + "subject.Subject->subject.Subject.Strain\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Zygosity\n", - "\n", + "subject.Subject->subject.Subject.Protocol\n", + "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.Subject.User\n", - "\n", + "subject.Subject->subject.Subject.Line\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject->subject.SubjectDeath\n", - "\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", + "\n", + "subject.Subject.Lab\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject->subject.Subject.Protocol\n", - "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.Lab\n", + "\n", "\n", "\n", "\n", @@ -837,88 +766,104 @@ "------------------------------\r", "→ lab.Source\r", "\">\n", - "\n", - "subject.Subject.Source\n", + "\n", + "subject.Subject.Source\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "subject.Subject->subject.Subject.Source\n", - "\n", + "\n", "\n", - "\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "subject.Subject.User\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "subject.Subject->subject.Subject.User\n", + "\n", + "\n", + "\n", "\n", - "subject.SubjectCullMethod\n", + "subject.SubjectDeath\n", "\n", - "\n", - "subject.SubjectCullMethod\n", + "\n", + "subject.SubjectDeath\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "subject.Subject->subject.SubjectCullMethod\n", - "\n", - "\n", - "\n", - "\n", - "subject.Subject->subject.Subject.Strain\n", - "\n", + "subject.Subject->subject.SubjectDeath\n", + "\n", "\n", - "\n", - "\n", - "subject.Subject.Lab\n", - "\n", + "\n", + "subject.SubjectCullMethod\n", + "\n", - "\n", - "subject.Subject.Lab\n", + "\n", + "subject.SubjectCullMethod\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "subject.Subject->subject.SubjectCullMethod\n", + "\n", + "\n", + "\n", "\n", - "subject.Subject->subject.Subject.Lab\n", - "\n", + "subject.Subject->subject.Zygosity\n", + "\n", "\n", "\n", - "\n", + "\n", "subject.Line\n", - "\n", - "\n", - "subject.Line\n", + "\n", + "subject.Line\n", "\n", "\n", "\n", "\n", "\n", "subject.Line->subject.Subject.Line\n", - "\n", + "\n", "\n", "\n", "\n", "subject.Line->subject.Line.Allele\n", - "\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -929,7 +874,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "5243a782-93da-40fa-b243-03ddcb230c1d", "metadata": {}, "outputs": [ @@ -996,27 +941,23 @@ "

    session_datetime

    \n", " \n", " \n", - " subject3\n", - "2021-04-30 12:22:15.032000subject5\n", + " subject5\n", "2020-04-15 11:16:38subject6\n", - "2021-01-15 11:16:38subject6\n", "2021-06-02 14:04:22 \n", " \n", " \n", - "

    Total: 4

    \n", + "

    Total: 2

    \n", " " ], "text/plain": [ "*subject *session_datet\n", "+----------+ +------------+\n", - "subject3 2021-04-30 12:\n", "subject5 2020-04-15 11:\n", - "subject6 2021-01-15 11:\n", "subject6 2021-06-02 14:\n", - " (Total: 4)" + " (Total: 2)" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -1027,7 +968,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "7e48d7c0-b7bd-4f0b-abcb-1aedc69d5310", "metadata": {}, "outputs": [ @@ -1037,21 +978,10 @@ "\n", "\n", "\n", - "\n", - "\n", - "session.Session\n", - "\n", - "\n", - "session.Session\n", - "\n", - "\n", - "\n", "\n", - "\n", + "\n", "session.ProjectSession\n", - "\n", "\n", @@ -1059,69 +989,80 @@ "\n", "\n", "\n", - "\n", - "\n", - "session.Session->session.ProjectSession\n", - "\n", + "\n", + "\n", + "session.SessionExperimenter\n", + "\n", + "\n", + "session.SessionExperimenter\n", + "\n", "\n", - "\n", + "\n", + "\n", "\n", - "session.SessionDirectory\n", - "session.Session\n", + "\n", - "\n", - "session.SessionDirectory\n", + "\n", + "session.Session\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "session.Session->session.ProjectSession\n", + "\n", + "\n", + "\n", "\n", - "session.Session->session.SessionDirectory\n", - "\n", + "session.Session->session.SessionExperimenter\n", + "\n", "\n", - "\n", + "\n", "\n", - "session.SessionExperimenter\n", + "session.SessionNote\n", "\n", - "\n", - "session.SessionExperimenter\n", + "\n", + "session.SessionNote\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session->session.SessionExperimenter\n", - "\n", + "session.Session->session.SessionNote\n", + "\n", "\n", - "\n", + "\n", "\n", - "session.SessionNote\n", + "session.SessionDirectory\n", "\n", - "\n", - "session.SessionNote\n", + "\n", + "session.SessionDirectory\n", "\n", "\n", "\n", - "\n", + "\n", "\n", - "session.Session->session.SessionNote\n", - "\n", + "session.Session->session.SessionDirectory\n", + "\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -1140,7 +1081,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "1984077d-a9f2-4b17-8034-ee2af8f105f4", "metadata": {}, "outputs": [], @@ -1158,7 +1099,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "9c0821e1-9125-4c41-bc9c-567f53d0a5e5", "metadata": {}, "outputs": [ @@ -1167,7 +1108,7 @@ "output_type": "stream", "text": [ "# Animal Subject\n", - "subject : varchar(32) \n", + "subject : varchar(8) \n", "---\n", "sex : enum('M','F','U') \n", "subject_birth_date : date \n", @@ -1178,10 +1119,10 @@ { "data": { "text/plain": [ - "'# Animal Subject\\nsubject : varchar(32) \\n---\\nsex : enum(\\'M\\',\\'F\\',\\'U\\') \\nsubject_birth_date : date \\nsubject_description=\"\" : varchar(1024) \\n'" + "'# Animal Subject\\nsubject : varchar(8) \\n---\\nsex : enum(\\'M\\',\\'F\\',\\'U\\') \\nsubject_birth_date : date \\nsubject_description=\"\" : varchar(1024) \\n'" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1193,7 +1134,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "504d55ed-25bd-4bb1-bce9-c516bd3595df", "metadata": {}, "outputs": [ @@ -1204,17 +1145,17 @@ "-> subject.Subject\n", "-> subject.Allele\n", "---\n", - "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\n", + "zygosity : enum('Present','Absent','Homozygous','Heterozygous') \n", "\n" ] }, { "data": { "text/plain": [ - "\"-> subject.Subject\\n-> subject.Allele\\n---\\nzygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity\\n\"" + "\"-> subject.Subject\\n-> subject.Allele\\n---\\nzygosity : enum('Present','Absent','Homozygous','Heterozygous') \\n\"" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -1226,7 +1167,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "1edded31-bdef-42fb-8f58-b42b9b186cf1", "metadata": {}, "outputs": [ @@ -1234,13 +1175,13 @@ "data": { "text/plain": [ "# \n", - "subject : varchar(32) # \n", + "subject : varchar(8) # \n", "allele : varchar(32) # abbreviated allele name\n", "---\n", - "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # zygosity" + "zygosity : enum('Present','Absent','Homozygous','Heterozygous') # " ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -1269,7 +1210,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "d5b43904-9711-4bce-8ae5-d0d797118dec", "metadata": {}, "outputs": [], @@ -1291,7 +1232,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "9bf2c953-7b4c-4a70-99fd-124a4d28171b", "metadata": {}, "outputs": [ @@ -1370,28 +1311,16 @@ "test animalsubject2\n", "F\n", "2020-11-30\n", - "test animalsubject3\n", - "F\n", - "2020-12-30\n", - "test animalsubject4\n", - "M\n", - "2021-02-12\n", "test animalsubject5\n", "F\n", - "2020-01-03\n", - "lmash_E105subject6\n", + "2020-01-01\n", + "richsubject6\n", "M\n", - "2020-01-03\n", - "hneih_E105subject7\n", - "U\n", - "2020-08-30\n", - "test animalsubject8\n", - "F\n", - "2020-09-30\n", - "test animal \n", + "2020-01-01\n", + "manuel \n", " \n", " \n", - "

    Total: 8

    \n", + "

    Total: 4

    \n", " " ], "text/plain": [ @@ -1399,16 +1328,12 @@ "+----------+ +-----+ +------------+ +------------+\n", "subject1 M 2020-12-30 test animal \n", "subject2 F 2020-11-30 test animal \n", - "subject3 F 2020-12-30 test animal \n", - "subject4 M 2021-02-12 test animal \n", - "subject5 F 2020-01-03 lmash_E105 \n", - "subject6 M 2020-01-03 hneih_E105 \n", - "subject7 U 2020-08-30 test animal \n", - "subject8 F 2020-09-30 test animal \n", - " (Total: 8)" + "subject5 F 2020-01-01 rich \n", + "subject6 M 2020-01-01 manuel \n", + " (Total: 4)" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1419,7 +1344,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "7a10ddab-d0fd-45a0-8183-09c1b1933e0a", "metadata": {}, "outputs": [], @@ -1442,7 +1367,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "064ddaae-3410-47fc-be22-671d2afe7fb6", "metadata": {}, "outputs": [ @@ -1529,11 +1454,11 @@ "2021-02-12\n", "test animalsubject5\n", "F\n", - "2020-01-03\n", - "lmash_E105subject6\n", + "2020-01-01\n", + "richsubject6\n", "M\n", - "2020-01-03\n", - "hneih_E105subject7\n", + "2020-01-01\n", + "manuelsubject7\n", "U\n", "2020-08-30\n", "test animalsubject8\n", @@ -1552,14 +1477,14 @@ "subject2 F 2020-11-30 test animal \n", "subject3 F 2020-12-30 test animal \n", "subject4 M 2021-02-12 test animal \n", - "subject5 F 2020-01-03 lmash_E105 \n", - "subject6 M 2020-01-03 hneih_E105 \n", + "subject5 F 2020-01-01 rich \n", + "subject6 M 2020-01-01 manuel \n", "subject7 U 2020-08-30 test animal \n", "subject8 F 2020-09-30 test animal \n", " (Total: 8)" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1614,4 +1539,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index bc54ae6..6c2b5e3 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -39,17 +39,41 @@ "text": [ "Connecting root@localhost:3306\n", "\n", - "---- Insert 2 entry(s) into lab tables ----\n", + "---- Inserting 0 entry(s) into #lab ----\n", "\n", - "---- Insert 2 entry(s) into project table ----\n", + "---- Inserting 0 entry(s) into #location ----\n", "\n", - "---- Insert entry(s) into publication/keyword tables ----\n", + "---- Inserting 0 entry(s) into #project ----\n", "\n", - "---- Insert 2 entry(s) into protocol tables ----\n", + "---- Inserting 0 entry(s) into #project__source_code ----\n", "\n", - "---- Insert 2 entry(s) into subject tables ----\n", + "---- Inserting 0 entry(s) into #project__publication ----\n", "\n", - "---- Insert 2 entry(s) into session.Session ----\n" + "---- Inserting 0 entry(s) into #project__keywords ----\n", + "\n", + "---- Inserting 0 entry(s) into #protocol_type ----\n", + "\n", + "---- Inserting 0 entry(s) into #protocol ----\n", + "\n", + "---- Inserting 0 entry(s) into #user_role ----\n", + "\n", + "---- Inserting 0 entry(s) into #user ----\n", + "\n", + "---- Inserting 0 entry(s) into #lab_membership ----\n", + "\n", + "---- Inserting 0 entry(s) into project_user ----\n", + "\n", + "---- Inserting 0 entry(s) into subject ----\n", + "\n", + "---- Inserting 0 entry(s) into subject_death ----\n", + "\n", + "---- Inserting 0 entry(s) into subject_cull_method ----\n", + "\n", + "---- Inserting 0 entry(s) into session ----\n", + "\n", + "---- Inserting 0 entry(s) into session_directory ----\n", + "\n", + "---- Inserting 0 entry(s) into session_note ----\n" ] } ], @@ -91,55 +115,37 @@ "Get export from element-session, then export with keys from prev step." ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", - "metadata": {}, - "outputs": [], - "source": [ - "from element_session.export import session_to_nwb" - ] - }, { "cell_type": "code", "execution_count": 13, - "id": "fdc6d3c7-8f4d-41c8-af6f-5c63ab68e316", + "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", "metadata": {}, "outputs": [ { - "ename": "DataJointError", - "evalue": "fetch1 requires exactly one tuple in the input set.", + "ename": "AttributeError", + "evalue": "module 'element_session.export.nwb' has no attribute 'activate'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mDataJointError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_1373/2627209259.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m(\u001b[0m\u001b[0msubject\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSubject\u001b[0m\u001b[0;34m&\u001b[0m\u001b[0;34m\"subject!='subject5'\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetch1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/datajoint/fetch.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, squeeze, download_path, *attrs)\u001b[0m\n\u001b[1;32m 246\u001b[0m \u001b[0mret\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 247\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mret\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mcur\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfetchone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 248\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mDataJointError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'fetch1 requires exactly one tuple in the input set.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 249\u001b[0m ret = dict((name, _get(self._expression.connection, heading[name], ret[name],\n\u001b[1;32m 250\u001b[0m squeeze=squeeze, download_path=download_path))\n", - "\u001b[0;31mDataJointError\u001b[0m: fetch1 requires exactly one tuple in the input set." + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_13855/3549832774.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexport\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnwb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mnwb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mactivate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'workflow_session.pipeline'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mhelp\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexport\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msession_to_nwb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'element_session.export.nwb' has no attribute 'activate'" ] } ], "source": [ - "(subject.Subject&\"subject!='subject5'\").fetch1()" + "# pending further revisions\n", + "from element_session.export.nwb session_to_nwb" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "e76fa25d-700b-4f9a-9bb4-62d2217288b6", + "execution_count": 7, + "id": "fdc6d3c7-8f4d-41c8-af6f-5c63ab68e316", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", - " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" - ] - } - ], + "outputs": [], "source": [ + "session_key=(session.Session&\"subject='subject5'\").fetch1()\n", "mynwbfile=session_to_nwb(session_key)" ] }, @@ -301,4 +307,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/user_data/lab/projects.csv b/user_data/lab/projects.csv index 27a01c6..1879c1c 100644 --- a/user_data/lab/projects.csv +++ b/user_data/lab/projects.csv @@ -1,3 +1,3 @@ -project,project_description,repositoryurl,repositoryname,pharmacology,viruses,slices,stimulus,surgery,codeurl -ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,Subjects were administered 10ul sedative prior to surgery,Exemplarvirus administered 10d before experimental session,,videos generated programmatically see repository,Craniotomy performed by session experimenter,https://github.com/datajoint/element-lab/tree/main/element_lab -ProjB,Other example project to populate element-lab,https://github.com/datajoint/element-session/,element-session,,Exemplarvirus administered 8d study,,,,https://github.com/datajoint/element-session/tree/main/element_session +project,project_description,repository_url,repository_name,codeurl +ProjA,Example project to populate element-lab,https://github.com/datajoint/element-lab/,element-lab,https://github.com/datajoint/element-lab/tree/main/element_lab +ProjB,Other example project to populate element-lab,https://github.com/datajoint/element-session/,element-session,https://github.com/datajoint/element-session/tree/main/element_session diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 09b9682..48c61ee 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -50,12 +50,14 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', # List with repeats for when mult dj.tables fed by same CSV csvs = [lab_csv_path, lab_csv_path, - project_csv_path, publication_csv_path, keyword_csv_path, + project_csv_path, project_csv_path, + publication_csv_path, keyword_csv_path, protocol_csv_path, protocol_csv_path, users_csv_path, users_csv_path, users_csv_path, project_user_csv_path] tables = [lab.Lab(), lab.Location(), - lab.Project(), lab.Project.Publication(), lab.Project.Keywords(), + lab.Project(), lab.Project.SourceCode(), + lab.Project.Publication(), lab.Project.Keywords(), lab.ProtocolType(), lab.Protocol(), lab.UserRole(), lab.User(), lab.LabMembership(), lab.ProjectUser()] From 2ac483aa554c86a0892ef4001efbfcbf88e205c6 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 11 Jan 2022 12:21:33 -0600 Subject: [PATCH 24/46] Removed lab.Project refs to slices, etc. from integration test --- tests/__init__.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 00da41e..0332dfc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -88,24 +88,16 @@ def lab_csv(): @pytest.fixture def lab_project_csv(): """ Create a 'projects.csv' file""" - lab_project_content = ["project,project_description,repositoryurl," - + "repositoryname,pharmacology,viruses,slices," - + "stimulus,surgery,codeurl", + lab_project_content = ["project,project_description,repository_url," + + "repository_name,codeurl", "ProjA,Example project to populate element-lab," + "https://github.com/datajoint/element-lab/," - + "element-lab,Subjects were administered 10ul " - + "sedative prior to surgery,Exemplarvirus " - + "administered 10d before experimental session,," - + "videos generated programmatically see " - + "repository,Craniotomy performed by session " - + "experimenter,https://github.com/datajoint/" - + "element-lab/tree/main/element_lab", + + "element-lab,https://github.com/datajoint/element" + + "-lab/tree/main/element_lab", "ProjB,Other example project to populate element-" - + "lab,https://github.com/datajoint/element-" - + "session/,element-session,,Exemplarvirus " - + "administered 8d study,,,,https://github.com/" - + "datajoint/element-session/tree/main/element_" - + "session"] + + "lab,https://github.com/datajoint/element-session" + + "/,element-session,https://github.com/datajoint/" + + "element-session/tree/main/element_session"] lab_project_csv_path = pathlib.Path('./tests/user_data/lab/projects.csv') write_csv(lab_project_content, lab_project_csv_path) From 836390ed922aa9f89fad684865512daef24c71df Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 13 Jan 2022 12:37:06 -0600 Subject: [PATCH 25/46] Version. Update CHANGELOG. notebook: remove specifics - Version: move from __init__.py to version.py per updated convention - Version: bump - CHANGELOG: update with rename, integration tests - Notebook: remove my username from output. Needs future updates after [element-session PR](https://github.com/datajoint/element-session/pull/7) --- CHANGELOG.md | 9 ++++++--- notebooks/2_Explore_Export.ipynb | 4 ++-- workflow_session/__init__.py | 5 ----- workflow_session/version.py | 2 ++ 4 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 workflow_session/version.py diff --git a/CHANGELOG.md b/CHANGELOG.md index abad20e..2ed23c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,13 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention. -## [unreleased: 0.0.1] +## [0.0.0b2] ### Added -+ Change to include `element-session` -+ Add export notebook ++ Export notebook ++ Integration tests + +### Changed ++ Name `element-animal` -> `element-session` ## [0.0.0b1] - 2021-03-24 diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb index 6c2b5e3..5694d69 100644 --- a/notebooks/2_Explore_Export.ipynb +++ b/notebooks/2_Explore_Export.ipynb @@ -205,7 +205,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", + "~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" ] } @@ -232,7 +232,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/cb/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/build/objectmapper.py:653: MissingRequiredBuildWarning: NWBFile 'root' is missing required value for attribute 'source_script_file_name'.\n", + "~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/build/objectmapper.py:653: MissingRequiredBuildWarning: NWBFile 'root' is missing required value for attribute 'source_script_file_name'.\n", " warnings.warn(msg, MissingRequiredBuildWarning)\n" ] } diff --git a/workflow_session/__init__.py b/workflow_session/__init__.py index aceae16..e69de29 100644 --- a/workflow_session/__init__.py +++ b/workflow_session/__init__.py @@ -1,5 +0,0 @@ -__author__ = "DataJoint" -__date__ = "December, 2021" -__version__ = "0.0.1" - -__all__ = ['__author__', '__version__', '__date__'] diff --git a/workflow_session/version.py b/workflow_session/version.py new file mode 100644 index 0000000..77ac34d --- /dev/null +++ b/workflow_session/version.py @@ -0,0 +1,2 @@ +"""Package metadata.""" +__version__ = '0.0.0b2' From 61477952fcd87646575654de9a49eb2da8a3700a Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 21 Jan 2022 19:03:38 -0600 Subject: [PATCH 26/46] Docker integration test successful. See details. Changes: README: Refactor to point to `datajoint-elements` for installation Docker-compose*: follow new `workflow-array-ephys` model notebook/2_Explore_Export: remove pending separate nwb PR tests/__init__.py: add mkdir for csv subdirectories tests/test_export.py: remove pending nwb PR workflow_session/pipeline.py: comment out genotyping schema info --- .gitignore | 4 +- Dockerfile.dev | 29 +++ Dockerfile.test | 35 ++++ README.md | 169 +---------------- docker-compose-dev.yaml | 31 ++++ docker-compose-test.yaml | 19 +- notebooks/2_Explore_Export.ipynb | 310 ------------------------------- tests/__init__.py | 13 +- tests/test_export.py | 10 - workflow_session/pipeline.py | 6 +- 10 files changed, 124 insertions(+), 502 deletions(-) create mode 100644 Dockerfile.dev create mode 100644 Dockerfile.test create mode 100644 docker-compose-dev.yaml delete mode 100644 notebooks/2_Explore_Export.ipynb delete mode 100644 tests/test_export.py diff --git a/.gitignore b/.gitignore index 5bb6f36..7c86f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,7 @@ celerybeat-schedule *.sage.py # dotenv, virtualenv, pyenv, mypy -./.env +.env .venv venv/ ENV/ @@ -82,4 +82,4 @@ ENV/ # datajoint, notes, nwb export dj_local_c*.json temp* -*nwb \ No newline at end of file +*nwb diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..a259816 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,29 @@ +FROM datajoint/djlab:py3.8-debian + +USER root +RUN apt-get update -y +RUN apt-get install git -y + +USER anaconda + +RUN mkdir /main/element-lab \ + /main/element-animal \ + /main/element-session \ + /main/workflow-session + +# Copy user's local fork of elements and workflow +COPY --chown=anaconda:anaconda ./element-lab /main/element-lab +COPY --chown=anaconda:anaconda ./element-animal /main/element-animal +COPY --chown=anaconda:anaconda ./element-session /main/element-session +COPY --chown=anaconda:anaconda ./workflow-session /main/workflow-session + +# Install packages +RUN pip install -e /main/element-lab +RUN pip install -e /main/element-animal +RUN pip install -e /main/element-session +RUN pip install -e /main/workflow-session +RUN pip install -r /main/workflow-session/requirements_test.txt + +WORKDIR /main/workflow-session + +ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..fe80883 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,35 @@ +FROM datajoint/djlab:py3.8-debian + +USER root +RUN apt-get update -y +RUN apt-get install git -y + +USER anaconda +WORKDIR /main/workflow-session + +# Option 1 - Install DataJoint's remote fork of the workflow and elements +# RUN git clone https://github.com/datajoint/workflow-session.git /main/workflow-session + +# Option 2 - Install user's remote fork of element and workflow +# or an unreleased version of the element +# RUN pip install git+https://github.com//element-lab.git +# RUN pip install git+https://github.com//element-animal.git +# RUN pip install git+https://github.com//element-session.git +# RUN git clone https://github.com//workflow-session.git /main/workflow-session + +# Option 3 - Install user's local fork of element and workflow +RUN mkdir /main/element-lab +COPY --chown=anaconda:anaconda ./element-lab /main/element-lab +RUN pip install -e /main/element-lab +RUN mkdir /main/element-animal +COPY --chown=anaconda:anaconda ./element-animal /main/element-animal +RUN pip install -e /main/element-animal +RUN mkdir /main/element-session +COPY --chown=anaconda:anaconda ./element-session /main/element-session +RUN pip install -e /main/element-session +COPY --chown=anaconda:anaconda ./workflow-session /main/workflow-session +# RUN rm -f /main/workflow-session/dj_local_conf.json + +# Install the workflow +RUN pip install /main/workflow-session +RUN pip install -r /main/workflow-session/requirements_test.txt diff --git a/README.md b/README.md index 987f531..dda3c9f 100644 --- a/README.md +++ b/README.md @@ -40,168 +40,11 @@ This workflow serves as an example of the upstream part of a typical data workfl ## Installation instructions -### Step 1 - Clone this repository - -+ Launch a new terminal and change directory to where you want to clone the repository - ``` - cd C:/Projects - ``` -+ Clone the repository - ``` - git clone https://github.com/datajoint/workflow-session - ``` -+ Change directory to `workflow-session` - ``` - cd workflow-session - ``` ++ The installation instructions can be found at [datajoint-elements/install.md]( + https://github.com/datajoint/datajoint-elements/blob/main/install.md). -### Step 2 - Setup a virtual environment -It is highly recommended (though not strictly required) to create a virtual environment to run the pipeline. This can be done with either `virtualenv` or `conda` - -+ For `virtualenv`: - - + If not yet installed, run `pip install --user virtualenv` - - + To create a new virtual environment named `venv`: - ``` - virtualenv venv - ``` - - + To activated the virtual environment: - + On Windows: - ``` - .\venv\Scripts\activate - ``` - - + On Linux/macOS: - ``` - source venv/bin/activate - ``` -+ For `conda`: - + If not yet installed, run `pip install --user conda` +## Interacting with the DataJoint workflow - + To create a new virtual environment named `venv`: - ``` - conda create --name venv python=3.8 - ``` - - + To activated the virtual environment: - + On Windows: - ``` - activate venv - ``` - - + On Linux/macOS: - ``` - source activate venv - ``` - -### Step 3 - Install this repository - -From the root of the cloned repository directory: - ``` - pip install -e . - ``` - -Note: the `-e` flag will install this repository in editable mode, -in case you'd like to to modify the code (e.g. the `pipeline.py` or `paths.py` scripts). -If no such modification required, using `pip install .` is sufficient. - - -### Step 4 - Jupyter Notebook -+ Register an IPython kernel with Jupyter - ``` - ipython kernel install --name=workflow-session - ``` - -### Step 5 - Configure the `dj_local_conf.json` - -At the root of the repository folder, -create a new file `dj_local_conf.json` with the following template: - -```json -{ - "database.host": "", - "database.user": "", - "database.password": "", - "loglevel": "INFO", - "safemode": true, - "display.limit": 7, - "display.width": 14, - "display.show_tuple_count": true, - "custom": { - "database.prefix": "", -} -``` - -+ Specify database's `hostname`, `username`, and `password` according to the database you plan to use (see [set-up instructions here](https://tutorials.datajoint.io/setting-up/get-database.html)). - -+ Specify a `database.prefix` to create the schemas. - - -### Installation complete - -+ At this point the setup of this workflow is complete. - - -## Interacting with the DataJoint pipeline and exploring data - -+ [Connect to database](https://tutorials.datajoint.io/setting-up/get-database.html) - -+ Import tables - ``` - from workflow_session.pipeline import * - ``` - This will create all tables defined in the elements in the database server. - -+ Preview the tables created by calling the classes, for example: - ``` - lab.Lab() - subject.Subject() - session.Session() - genotyping.GenotypingTest() - ``` - -+ If required to drop all schemas, the following is the dependency order. - ``` - from workflow_session.pipeline import * - - genotyping.schema.drop() - session.schema.drop() - subject.schema.drop() - lab.schema.drop() - ``` - -+ For a more in-depth exploration of the tables created, please refer to the example [notebooks](notebooks/1_Explore_Workflow.ipynb). - - -## Insert into Manual and Lookup tables with Graphical User Interface DataJoint Labbook - -DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. - -![DataJoint Labbook preview](images/DataJoint_Labbook.png) - -Please refer to the [DataJoint Labbook page](https://github.com/datajoint/datajoint-labbook) for instructions to set it up. - -## Development mode installation - -This method allows you to modify the source code for `workflow-calcium-imaging`, `element-calcium-imaging`, `element-session`, `element-animal`, and `element-lab`. - -+ Launch a new terminal and change directory to where you want to clone the repositories - ``` - cd C:/Projects - ``` -+ Clone the repositories - ``` - git clone https://github.com/datajoint/element-lab - git clone https://github.com/datajoint/element-animal - git clone https://github.com/datajoint/element-session - git clone https://github.com/datajoint/workflow-session - ``` -+ Install each package with the `-e` option - ``` - pip install -e ./workflow-session - pip install -e ./element-session - pip install -e ./element-lab - pip install -e ./element-animal - ``` ++ Please refer to the following workflow-specific +[Jupyter notebooks](/notebooks) for an in-depth explanation of how to run the +workflow ([1-Explore_Workflow.ipynb](notebooks/1_Explore_Workflow.ipynb). diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml new file mode 100644 index 0000000..3bcb2eb --- /dev/null +++ b/docker-compose-dev.yaml @@ -0,0 +1,31 @@ +# docker-compose -f docker-compose-dev.yaml up -d --build +# docker-compose -f docker-compose-dev.yaml down + +version: "2.4" +x-net: &net + networks: + - main +services: + db: + <<: *net + image: datajoint/mysql:5.7 + environment: + - MYSQL_ROOT_PASSWORD=simple + workflow: + <<: *net + build: + context: ../ + dockerfile: ./workflow-session/Dockerfile.dev + env_file: .env + image: workflow_session_dev:0.0.0b2 + volumes: + - ./apt_requirements.txt:/tmp/apt_requirements.txt + - ../element-lab:/main/element-lab + - ../element-animal:/main/element-animal + - ../element-session:/main/element-session + - .:/main/workflow-session + depends_on: + db: + condition: service_healthy +networks: + main: diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 56407b2..509386e 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -1,3 +1,6 @@ +# docker-compose -f docker-compose-test.yaml up --build +# docker-compose -f docker-compose-test.yaml down + version: "2.4" x-net: &net networks: @@ -8,16 +11,17 @@ services: image: datajoint/mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=simple - workflow_array_ephys: + workflow: <<: *net build: - context: . - image: workflow_session:v0.0.0 + context: ../ + dockerfile: ./workflow-session/Dockerfile.test + env_file: .env + image: workflow_session:0.0.0b2 environment: - DJ_HOST=db - DJ_USER=root - DJ_PASS=simple - - EPHYS_ROOT_DATA_DIR=/main/test_data - DATABASE_PREFIX=test_ command: - bash @@ -27,10 +31,13 @@ services: pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings tail -f /dev/null volumes: - - ${TEST_DATA_DIR}:/main/test_data - ./apt_requirements.txt:/tmp/apt_requirements.txt + - ../element-lab:/main/element-lab + - ../element-animal:/main/element-animal + - ../element-session:/main/element-session + - .:/main/workflow-session depends_on: db: condition: service_healthy networks: - main: \ No newline at end of file + main: diff --git a/notebooks/2_Explore_Export.ipynb b/notebooks/2_Explore_Export.ipynb deleted file mode 100644 index 5694d69..0000000 --- a/notebooks/2_Explore_Export.ipynb +++ /dev/null @@ -1,310 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3054518f-87bc-42ff-a3e7-84bf3d2a37f6", - "metadata": {}, - "source": [ - "# DataJoint U24 - Export Session" - ] - }, - { - "cell_type": "markdown", - "id": "79c15f36-039d-4304-96be-f56ba0d6b10a", - "metadata": {}, - "source": [ - "Same as before, import data." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "0e7fa407-d67b-403e-975c-bd0bd499d88c", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "if os.path.basename(os.getcwd())=='notebooks': os.chdir('..')" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6f11ca71-5e4f-460c-ad94-2037ef0f6448", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connecting root@localhost:3306\n", - "\n", - "---- Inserting 0 entry(s) into #lab ----\n", - "\n", - "---- Inserting 0 entry(s) into #location ----\n", - "\n", - "---- Inserting 0 entry(s) into #project ----\n", - "\n", - "---- Inserting 0 entry(s) into #project__source_code ----\n", - "\n", - "---- Inserting 0 entry(s) into #project__publication ----\n", - "\n", - "---- Inserting 0 entry(s) into #project__keywords ----\n", - "\n", - "---- Inserting 0 entry(s) into #protocol_type ----\n", - "\n", - "---- Inserting 0 entry(s) into #protocol ----\n", - "\n", - "---- Inserting 0 entry(s) into #user_role ----\n", - "\n", - "---- Inserting 0 entry(s) into #user ----\n", - "\n", - "---- Inserting 0 entry(s) into #lab_membership ----\n", - "\n", - "---- Inserting 0 entry(s) into project_user ----\n", - "\n", - "---- Inserting 0 entry(s) into subject ----\n", - "\n", - "---- Inserting 0 entry(s) into subject_death ----\n", - "\n", - "---- Inserting 0 entry(s) into subject_cull_method ----\n", - "\n", - "---- Inserting 0 entry(s) into session ----\n", - "\n", - "---- Inserting 0 entry(s) into session_directory ----\n", - "\n", - "---- Inserting 0 entry(s) into session_note ----\n" - ] - } - ], - "source": [ - "import datajoint as dj\n", - "dj.conn()\n", - "from element_lab import lab\n", - "from element_animal import subject\n", - "from element_session import session\n", - "from workflow_session.ingest import ingest_lab, ingest_subjects, ingest_sessions\n", - "## delete if needed, to update/repopulate\n", - "# lab.Lab.delete();lab.Project.delete();lab.Protocol.delete()\n", - "ingest_lab(); ingest_subjects();ingest_sessions()" - ] - }, - { - "cell_type": "markdown", - "id": "ab2a3f10-b96e-4f0d-9e54-0015bb9b8622", - "metadata": {}, - "source": [ - "Identify items for export with keys." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "76c040c8-15cc-4d61-ae5d-bc7646a6a0be", - "metadata": {}, - "outputs": [], - "source": [ - "session_key=(session.Session&'subject=\"subject5\"').fetch1('KEY')" - ] - }, - { - "cell_type": "markdown", - "id": "902e050c-3133-4fb7-850d-f0b954e1b634", - "metadata": {}, - "source": [ - "Get export from element-session, then export with keys from prev step." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "61fdbdce-a808-49ad-bb5d-f9b9894446fa", - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "module 'element_session.export.nwb' has no attribute 'activate'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/_9/tzvq__ws5z9gv5s7jvkj570r0000gn/T/ipykernel_13855/3549832774.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0melement_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexport\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnwb\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mnwb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mactivate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'workflow_session.pipeline'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mhelp\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexport\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msession_to_nwb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mAttributeError\u001b[0m: module 'element_session.export.nwb' has no attribute 'activate'" - ] - } - ], - "source": [ - "# pending further revisions\n", - "from element_session.export.nwb session_to_nwb" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "fdc6d3c7-8f4d-41c8-af6f-5c63ab68e316", - "metadata": {}, - "outputs": [], - "source": [ - "session_key=(session.Session&\"subject='subject5'\").fetch1()\n", - "mynwbfile=session_to_nwb(session_key)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "56ebdd79-f0dc-4925-aeb1-109887df361d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Help on function elementlab_nwb_dict in module element_lab.export.nwb:\n", - "\n", - "elementlab_nwb_dict(lab_key=None, project_key=None, protocol_key=None)\n", - " Generate a dictionary object containing all relevant lab information used when\n", - " generating an NWB file at the session level. All parameters optional.\n", - " Use: mynwbfile = NWBfile(identifier=\"your identifier\",\n", - " session_description=\"your description\",\n", - " session_start_time=session_datetime,\n", - " elementlab_nwb_dict(lab_key=key1,project_key=key2,protocol_key=key3))\n", - " Note: The lab, project and protocol keys should specify one of their respective types.\n", - " \n", - " :param lab_key: Key specifying one entry in element_lab.lab.Lab\n", - " :param project_key: Key specifying one entry in element_lab.lab.Project\n", - " :param protocol_key: Key specifying one entry in element_lab.lab.PRotocol\n", - " :return: dictionary with NWB parameters\n", - "\n" - ] - } - ], - "source": [ - "from element_lab.export import elementlab_nwb_dict\n", - "help(elementlab_nwb_dict)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "afc6555d-cd25-4d55-93e2-51c72ddba9ae", - "metadata": {}, - "outputs": [], - "source": [ - "from pynwb import NWBFile\n", - "lab_info = elementlab_nwb_dict(lab_key=mylab_key,project_key=myproj_key,protocol_key=myprot_key)\n", - "sess_info = session_to_nwb_dict(session_key)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "97e01cbf-b225-45d3-a030-0f7400e3526b", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/pynwb/file.py:753: UserWarning: Date is missing timezone information. Updating to local timezone.\n", - " warn(\"Date is missing timezone information. Updating to local timezone.\")\n" - ] - } - ], - "source": [ - "mynwbfile = NWBFile(**sess_info,**lab_info)" - ] - }, - { - "cell_type": "markdown", - "id": "1bccad09-d5e4-4200-bb73-08ddf98cfb80", - "metadata": {}, - "source": [ - "Learn more about using NWB formats [here](https://www.nwb.org/how-to-use/)." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "b914d8db-2584-403c-9f24-12a64103f1cb", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "~/miniconda3/envs/venv-nwb/lib/python3.8/site-packages/hdmf/build/objectmapper.py:653: MissingRequiredBuildWarning: NWBFile 'root' is missing required value for attribute 'source_script_file_name'.\n", - " warnings.warn(msg, MissingRequiredBuildWarning)\n" - ] - } - ], - "source": [ - "from pynwb import NWBHDF5IO\n", - "with NWBHDF5IO('session_metadata.nwb', mode='w') as io:\n", - " io.write(mynwbfile)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "c81b1c85-9623-4cc1-9438-79b0c5287756", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "root pynwb.file.NWBFile at 0x140190306285216\n", - "Fields:\n", - " experiment_description: Example project to populate element-lab\n", - " file_create_date: [datetime.datetime(2021, 12, 6, 17, 1, 11, 974467, tzinfo=tzlocal())]\n", - " identifier: subject5_20200415_111638\n", - " institution: Example Uni\n", - " keywords: ['Example' 'Study']\n", - " lab: The Example Lab\n", - " pharmacology: Subjects were administered 10ul sedative prior to surgery\n", - " protocol: ProtA\n", - " related_publications: ['arXiv:1807.11104' 'arXiv:1807.11104v1']\n", - " session_description: Successful data collection, no notes\n", - " session_start_time: 2020-04-15 11:16:38-05:00\n", - " source_script: https://github.com/datajoint/element-lab/\n", - " surgery: Craniotomy performed by session experimenter\n", - " timestamps_reference_time: 2020-04-15 11:16:38-05:00\n", - "\n" - ] - } - ], - "source": [ - "print(mynwbfile)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e75c3795-96d6-4ed1-889e-3da58d9e8533", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv-nwb", - "language": "python", - "name": "venv-nwb" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/tests/__init__.py b/tests/__init__.py index 0332dfc..40bfa89 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,11 +1,6 @@ ''' -fresh docker: - docker run --name wf-sess -p 3306:3306 -e \ - MYSQL_ROOT_PASSWORD=tutorial datajoint/mysql -dependencies: pip install pytest pytest-cov run all tests: - pytest -sv --cov-report term-missing --cov=workflow-session \ - -p no:warnings tests/ + pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings tests/ run one test, debug: pytest [above options] --pdb tests/tests_name.py -k function_name ''' @@ -19,8 +14,10 @@ _tear_down = True -test_user_data_dir = pathlib.Path('./tests/user_data') -test_user_data_dir.mkdir(exist_ok=True) +pathlib.Path('./tests/user_data').mkdir(exist_ok=True) +pathlib.Path('./tests/user_data/lab').mkdir(exist_ok=True) +pathlib.Path('./tests/user_data/session').mkdir(exist_ok=True) +pathlib.Path('./tests/user_data/subject').mkdir(exist_ok=True) # ------------------ GENERAL FUCNTION ------------------ diff --git a/tests/test_export.py b/tests/test_export.py deleted file mode 100644 index 7f1b4eb..0000000 --- a/tests/test_export.py +++ /dev/null @@ -1,10 +0,0 @@ -# import workflow_session -# from . import (dj_config, pipeline, lab_csv, ingest_lab, -# subjects_csv, ingest_subjects, -# sessions_csv, ingest_sessions) - - -def test_nwb_export(): - # Ben to add integration? - # Tests with and without lab/animal elements? - pass diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index f07b78b..4f142da 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -5,8 +5,8 @@ from element_session import session from element_animal.subject import Subject -from element_animal.genotyping import Sequence, BreedingPair, Cage,\ - SubjectCaging, GenotypeTest +# from element_animal.genotyping import Sequence, BreedingPair, Cage,\ +# SubjectCaging, GenotypeTest from element_lab.lab import Source, Lab, Protocol, User, Project from element_session.session import Session @@ -25,4 +25,4 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) -genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) +# genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) From 2966000e82b9d37d317468fec256a590f35e4ed4 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Tue, 25 Jan 2022 10:00:06 -0600 Subject: [PATCH 27/46] Remove diagrams, link to elements in readme --- README.md | 13 +- images/DataJoint_Labbook.png | Bin 84080 -> 0 bytes images/genotyping_diagram.svg | 151 -------------------- images/subject_diagram.svg | 222 ----------------------------- notebooks/1_Explore_Workflow.ipynb | 2 +- session_diagram.png | Bin 0 -> 11060 bytes tests/test_ingest.py | 5 + 7 files changed, 11 insertions(+), 382 deletions(-) delete mode 100644 images/DataJoint_Labbook.png delete mode 100644 images/genotyping_diagram.svg delete mode 100644 images/subject_diagram.svg create mode 100644 session_diagram.png diff --git a/README.md b/README.md index dda3c9f..1c29fdc 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,16 @@ The lab and experiment subject management workflow presented here uses component ### element-lab -element-lab is used for all lab-general metadata, including personnel and projects. Many fields are optional, but may help for keeping track of lab members for multi-site projects or exporting data for publication. - -![lab](images/lab_diagram.svg) +![element-lab]( +https://github.com/datajoint/element-lab/raw/main/images/element_lab_diagram.svg) ### element-animal -element-animal contains two modules, `subject` and `genotyping`. - -`subject` contains basic information of subjects. -![subject](images/subject_diagram.svg) +![element-animal]( +https://github.com/datajoint/element-animal/blob/main/images/subject_diagram.svg) `genotyping` is designed for labs that handle animal care and genetic information themselves, which is optional. -![genotyping](images/genotyping_diagram.svg) +![genotyping](https://github.com/datajoint/element-animal/blob/nwb/images/genotyping_diagram.svg) ### element-session `session` is designed to handle metadata related to data collection, including collection date-time, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. diff --git a/images/DataJoint_Labbook.png b/images/DataJoint_Labbook.png deleted file mode 100644 index 2d8b83a1667c6ef112a13c8d070770053baea11d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84080 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 -fft_qjC983obxcG!cl_e=v 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 diff --git a/images/genotyping_diagram.svg b/images/genotyping_diagram.svg deleted file mode 100644 index 6c59d23..0000000 --- a/images/genotyping_diagram.svg +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - -SubjectCaging - - -SubjectCaging - - - - - -genotyping.Weaning - - -genotyping.Weaning - - - - - -GenotypeTest - - -GenotypeTest - - - - - -Cage - - -Cage - - - - - -Cage->SubjectCaging - - - - -BreedingPair.Mother - - -BreedingPair.Mother - - - - - -genotyping.SubjectLitter - - -genotyping.SubjectLitter - - - - - -BreedingPair.Father - - -BreedingPair.Father - - - - - -BreedingPair - - -BreedingPair - - - - - -BreedingPair->BreedingPair.Mother - - - - -BreedingPair->BreedingPair.Father - - - - -genotyping.Litter - - -genotyping.Litter - - - - - -BreedingPair->genotyping.Litter - - - - -genotyping.Litter->genotyping.Weaning - - - - -genotyping.Litter->genotyping.SubjectLitter - - - - -Sequence - - -Sequence - - - - - -Sequence->GenotypeTest - - - - -genotyping.AlleleSequence - - -genotyping.AlleleSequence - - - - - -Sequence->genotyping.AlleleSequence - - - - diff --git a/images/subject_diagram.svg b/images/subject_diagram.svg deleted file mode 100644 index 9864ba7..0000000 --- a/images/subject_diagram.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - -subject.Subject.Source - - -subject.Subject.Source - - - - - -subject.SubjectDeath - - -subject.SubjectDeath - - - - - -subject.Allele.Source - - -subject.Allele.Source - - - - - -subject.Subject.Lab - - -subject.Subject.Lab - - - - - -subject.Line.Allele - - -subject.Line.Allele - - - - - -subject.Subject.User - - -subject.Subject.User - - - - - -subject.Zygosity - - -subject.Zygosity - - - - - -subject.Subject.Strain - - -subject.Subject.Strain - - - - - -subject.SubjectCullMethod - - -subject.SubjectCullMethod - - - - - -subject.Subject.Line - - -subject.Subject.Line - - - - - -subject.Subject.Protocol - - -subject.Subject.Protocol - - - - - -subject.Line - - -subject.Line - - - - - -subject.Line->subject.Line.Allele - - - - -subject.Line->subject.Subject.Line - - - - -subject.Allele - - -subject.Allele - - - - - -subject.Allele->subject.Allele.Source - - - - -subject.Allele->subject.Line.Allele - - - - -subject.Allele->subject.Zygosity - - - - -subject.Strain - - -subject.Strain - - - - - -subject.Strain->subject.Subject.Strain - - - - -subject.Subject - - -subject.Subject - - - - - -subject.Subject->subject.Subject.Source - - - - -subject.Subject->subject.SubjectDeath - - - - -subject.Subject->subject.Subject.Lab - - - - -subject.Subject->subject.Subject.User - - - - -subject.Subject->subject.Zygosity - - - - -subject.Subject->subject.Subject.Strain - - - - -subject.Subject->subject.SubjectCullMethod - - - - -subject.Subject->subject.Subject.Line - - - - -subject.Subject->subject.Subject.Protocol - - - - diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index 57153f2..f33fbe4 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -1514,7 +1514,7 @@ "id": "4775dd80-8a54-47b7-a9ba-99995db9ff1a", "metadata": {}, "source": [ - "DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](../images/DataJoint_Labbook.png)" + "DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](https://github.com/datajoint/datajoint-labbook/blob/master/docs/sphinx/_static/images/walkthroughDemoOptimized.gif)" ] } ], diff --git a/session_diagram.png b/session_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b93d6e9499a4a6e940c4c55075cc95b0dad53284 GIT binary patch literal 11060 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 Pun>7^WoW6SQQ&_A7XvMY literal 0 HcmV?d00001 diff --git a/tests/test_ingest.py b/tests/test_ingest.py index 4a49ad8..18b486d 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -3,6 +3,11 @@ 2. Assert exact matches of inserted data fore key tables ''' +__all__ = ['dj_config', 'pipeline', 'lab_csv', 'lab_project_csv', 'lab_user_csv', + 'lab_publications_csv', 'lab_keywords_csv', 'lab_protocol_csv', + 'lab_project_users_csv', 'ingest_lab', 'subjects_csv', 'ingest_subjects', + 'sessions_csv', 'ingest_sessions'] + from . import (dj_config, pipeline, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, From ab50c0c1fc10966810cc1341022d78f7706ba55e Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 28 Jan 2022 09:31:52 -0600 Subject: [PATCH 28/46] Apply suggestions from @kabilar's code review Co-authored-by: Kabilar Gunalan --- CHANGELOG.md | 6 +++--- CONTRIBUTING.md | 2 +- setup.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed23c5..a5193a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and + Integration tests ### Changed -+ Name `element-animal` -> `element-session` ++ Name `workflow-animal` -> `workflow-session` ## [0.0.0b1] - 2021-03-24 @@ -21,5 +21,5 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and ### Added + Added notebooks -[0.0.0b1]: https://github.com/datajoint/workflow-animal/tree/f6d6a3353aae83ca13ff9fcc536017eb34c18f90 -[0.0.0a1]: https://github.com/datajoint/workflow-animal/tree/0d3d3c970056ff4c243d17cf4f738f48268d80ad +[0.0.0b1]: https://github.com/datajoint/workflow-session/tree/f6d6a3353aae83ca13ff9fcc536017eb34c18f90 +[0.0.0a1]: https://github.com/datajoint/workflow-session/tree/0d3d3c970056ff4c243d17cf4f738f48268d80ad diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f07cecb..350d3e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,3 @@ # Contribution Guidelines -This project follows the [DataJoint Contribution Guidelines](https://docs.datajoint.io/python/community/02-Contribute.html). Please reference the link for more full details. \ No newline at end of file +This project follows the [DataJoint Contribution Guidelines](https://docs.datajoint.org/python/community/02-Contribute.html). Please reference the link for more full details. \ No newline at end of file diff --git a/setup.py b/setup.py index 924014b..25e6a3d 100644 --- a/setup.py +++ b/setup.py @@ -9,9 +9,9 @@ # Workflow for lab, animal, and session management Build a workflow for lab management and animal metadata using DataJoint Elements -+ [elements-lab](https://github.com/datajoint/element-lab) -+ [elements-animal](https://github.com/datajoint/element-animal) -+ [elements-session](https://github.com/datajoint/element-session) ++ [element-lab](https://github.com/datajoint/element-lab) ++ [element-animal](https://github.com/datajoint/element-animal) ++ [element-session](https://github.com/datajoint/element-session) """ with open(path.join(here, 'requirements.txt')) as f: @@ -23,7 +23,7 @@ description="DataJoint Elements for Lab, Animal and Session Management", long_description=long_description, author='DataJoint', - author_email='info@vathes.com', + author_email='info@datajoint.com', license='MIT', url='https://github.com/datajoint/workflow-session', keywords='neuroscience lab-management animal-management session-management datajoint', From ac52192ff053e98859839b857d8b56e86524d68b Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 28 Jan 2022 09:33:09 -0600 Subject: [PATCH 29/46] pin requirements --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index b26b0ed..3cf9e25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ datajoint>=0.13.0 -element-lab -element-animal -element-session +element-lab==0.1.0b0 +element-animal==0.1.0b0 +element-session==0.1.0b0 ipykernel -pynwb +pynwb==1.4.0 From d8516490c75d5e84f5674dcf1f2d2af982b4da57 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Fri, 28 Jan 2022 16:05:12 -0600 Subject: [PATCH 30/46] Move diagrams to element. Revise table names per elem-lab change --- .gitignore | 31 +++++--------- README.md | 4 +- images/session_diagram.svg | 77 ----------------------------------- session_diagram.png | Bin 11060 -> 0 bytes workflow_session/ingest.py | 4 +- workflow_session/pipeline.py | 6 ++- 6 files changed, 19 insertions(+), 103 deletions(-) delete mode 100644 images/session_diagram.svg delete mode 100644 session_diagram.png diff --git a/.gitignore b/.gitignore index 7c86f5c..130f191 100644 --- a/.gitignore +++ b/.gitignore @@ -7,51 +7,42 @@ __pycache__/ .Python env/ build/ -develop-eggs/ -dist/ +*egg*/ +*dist/ downloads/ -eggs/ -.eggs/ -lib/ -lib64/ +lib*/ parts/ -sdist/ var/ wheels/ -*.egg-info/ .installed.cfg *.egg *.manifest *.spec -pip-log.txt -pip-delete*.txt +pip-*.txt # Unit test / coverage reports htmlcov/ .tox/ -.coverage -.coverage.* +*.cov* .cache nosetests.xml coverage.xml -*.cover .hypothesis/ .pytest_cache/ docker-compose.y*ml # C extension, Translations +# editors: vscode, emacs, Mac *.so *.mo *.pot - -# editors: vscode, emacs, Mac .vscode **/*~ **/#*# **/.#* .DS_Store -# Django, Flask, Scrapy, Sphinx, mkdocs: +# Django, Flask, Scrapy, Sphinx, mkdocs # PyBuilder, Jupyter, SageMath, celery beat *.log local_settings.py @@ -62,21 +53,19 @@ scratchpaper.* docs/_build/ /site target/ -.ipynb_checkpoints +.*checkpoints celerybeat-schedule *.sage.py # dotenv, virtualenv, pyenv, mypy -.env -.venv +.*env venv/ ENV/ .python-version .mypy_cache/ # Spyder/Rope project settings -.spyderproject -.spyproject +.spy*project .ropeproject # datajoint, notes, nwb export diff --git a/README.md b/README.md index 1c29fdc..2e9d1e5 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ https://github.com/datajoint/element-lab/raw/main/images/element_lab_diagram.svg https://github.com/datajoint/element-animal/blob/main/images/subject_diagram.svg) `genotyping` is designed for labs that handle animal care and genetic information themselves, which is optional. -![genotyping](https://github.com/datajoint/element-animal/blob/nwb/images/genotyping_diagram.svg) +![genotyping](https://github.com/datajoint/element-animal/blob/main/images/genotyping_diagram.svg) ### element-session `session` is designed to handle metadata related to data collection, including collection date-time, file paths, and notes. Most workflows will include element-session as a starting point for further data entry. -![session](images/session_diagram.png) +![session](https://github.com/datajoint/element-session/blob/main/images/session_diagram.svg) ### This workflow This workflow serves as an example of the upstream part of a typical data workflow, for examples using these elements in tandem with other data collection modalities, refer to: diff --git a/images/session_diagram.svg b/images/session_diagram.svg deleted file mode 100644 index a11a66e..0000000 --- a/images/session_diagram.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - -session.SessionNote - - -session.SessionNote - - - - - -session.SessionDirectory - - -session.SessionDirectory - - - - - -session.Session - - -session.Session - - - - - -session.Session->session.SessionNote - - - - -session.Session->session.SessionDirectory - - - - -session.ProjectSession - - -session.ProjectSession - - - - - -session.Session->session.ProjectSession - - - - -session.SessionExperimenter - - -session.SessionExperimenter - - - - - -session.Session->session.SessionExperimenter - - - - diff --git a/session_diagram.png b/session_diagram.png deleted file mode 100644 index b93d6e9499a4a6e940c4c55075cc95b0dad53284..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11060 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 Pun>7^WoW6SQQ&_A7XvMY diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 48c61ee..6d877ec 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -56,8 +56,8 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', users_csv_path, users_csv_path, users_csv_path, project_user_csv_path] tables = [lab.Lab(), lab.Location(), - lab.Project(), lab.Project.SourceCode(), - lab.Project.Publication(), lab.Project.Keywords(), + lab.Project(), lab.ProjectSourceCode(), + lab.ProjectPublication(), lab.ProjectKeywords(), lab.ProtocolType(), lab.Protocol(), lab.UserRole(), lab.User(), lab.LabMembership(), lab.ProjectUser()] diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index 4f142da..e3fb0b6 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -7,7 +7,8 @@ from element_animal.subject import Subject # from element_animal.genotyping import Sequence, BreedingPair, Cage,\ # SubjectCaging, GenotypeTest -from element_lab.lab import Source, Lab, Protocol, User, Project +from element_lab.lab import Source, Lab, Protocol, User, Project, ProjectKeywords,\ + ProjectPublication, ProjectSourceCode, ProjectUser from element_session.session import Session if 'custom' not in dj.config: @@ -15,6 +16,9 @@ db_prefix = dj.config['custom'].get('database.prefix', '') +__all__ = ['genotyping', 'session', 'Subject', 'Source', 'Lab', 'Protocol', 'User', + 'Project', 'ProjectKeywords', 'ProjectPublication', 'ProjectSourceCode', + 'ProjectUser', 'Session'] # Activate "lab", "subject", "session" schema ------------- From c7ae13710740cd68c76419cbbd82a3eb4d443264 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 3 Feb 2022 09:44:07 -0600 Subject: [PATCH 31/46] add integration tests for Subject part tables --- CHANGELOG.md | 7 ++-- Dockerfile.test | 2 +- docker-compose-test.yaml | 3 +- setup.py | 2 +- tests/__init__.py | 39 +++++++++++++++++---- tests/test_ingest.py | 53 ++++++++++++++++++----------- tests/test_pipeline_generation.py | 18 ++++++++++ user_data/lab/project_users.csv | 3 +- user_data/lab/users.csv | 3 +- user_data/session/sessions.csv | 7 ++-- user_data/subject/subjects.csv | 2 +- user_data/subject/subjects_part.csv | 3 ++ workflow_session/ingest.py | 12 ++++--- workflow_session/pipeline.py | 10 +++--- 14 files changed, 116 insertions(+), 48 deletions(-) create mode 100644 user_data/subject/subjects_part.csv diff --git a/CHANGELOG.md b/CHANGELOG.md index a5193a2..42a43c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,21 +3,20 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention. -## [0.0.0b2] +## 0.0.0b2 ### Added -+ Export notebook + Integration tests ### Changed + Name `workflow-animal` -> `workflow-session` -## [0.0.0b1] - 2021-03-24 +## 0.0.0b1 - 2021-03-24 ### Added + First beta release -## [0.0.0a1] - 2021-03-18 +## 0.0.0a1 - 2021-03-18 ### Added + Added notebooks diff --git a/Dockerfile.test b/Dockerfile.test index fe80883..5351e15 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -12,7 +12,7 @@ WORKDIR /main/workflow-session # Option 2 - Install user's remote fork of element and workflow # or an unreleased version of the element -# RUN pip install git+https://github.com//element-lab.git +# RUN pip install git+https://github.com/cbroz1/element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git # RUN git clone https://github.com//workflow-session.git /main/workflow-session diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 509386e..298661c 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -1,4 +1,5 @@ # docker-compose -f docker-compose-test.yaml up --build +# docker exec -it workflow-session_workflow_1 /bin/bash # docker-compose -f docker-compose-test.yaml down version: "2.4" @@ -19,7 +20,7 @@ services: env_file: .env image: workflow_session:0.0.0b2 environment: - - DJ_HOST=db + - DJ_HOST=workflow-session_db_1 - DJ_USER=root - DJ_PASS=simple - DATABASE_PREFIX=test_ diff --git a/setup.py b/setup.py index 25e6a3d..cc6bd5d 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( name='workflow-session', - version='0.0.1', + version=__version__, description="DataJoint Elements for Lab, Animal and Session Management", long_description=long_description, author='DataJoint', diff --git a/tests/__init__.py b/tests/__init__.py index 40bfa89..380f343 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -41,6 +41,12 @@ def dj_config(): if pathlib.Path('./dj_local_conf.json').exists(): dj.config.load('./dj_local_conf.json') dj.config['safemode'] = False + dj.config['database.host'] = (os.environ.get('DJ_HOST') + or dj.config['database.host']) + dj.config['database.password'] = (os.environ.get('DJ_PASS') + or dj.config['database.password']) + dj.config['database.user'] = (os.environ.get('DJ_USER') + or dj.config['database.user']) dj.config['custom'] = { 'database.prefix': (os.environ.get('DATABASE_PREFIX') or dj.config['custom']['database.prefix'])} @@ -109,7 +115,8 @@ def lab_project_users_csv(): "Sherlock,ProjA", "Sherlock,ProjB", "Watson,ProjB", - "Dr. Candace Pert,ProjA"] + "Dr. Candace Pert,ProjA", + "User1,ProjA"] lab_project_user_csv_path = pathlib.Path('./tests/user_data/lab/\ project_users.csv') write_csv(lab_project_user_content, lab_project_user_csv_path) @@ -169,7 +176,8 @@ def lab_user_csv(): + "+44 20 7946 0344", "LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763", "LabB,Dr. Candace Pert,PI,Pert@gmail.com," - + "+44 74 4046 5899"] + + "+44 74 4046 5899", + "LabA,User1,Lab Tech,fake@email.com,+44 1632 960103"] lab_user_csv_path = pathlib.Path('./tests/user_data/lab/users.csv') write_csv(lab_user_content, lab_user_csv_path) @@ -218,11 +226,26 @@ def subjects_csv(): @pytest.fixture -def ingest_subjects(pipeline, subjects_csv): +def subjects_part_csv(): + """Create a 'subjects_part.csv for Subject part tables""" + subject_part_content = ["subject,protocol,user,line,strain,source,lab", + "subject6,ProtA,User1,line,strain,source,LabA", + "subject5,ProtA,User1,line,strain,source,LabA"] + subject_part_csv_path = pathlib.Path('./tests/user_data/subject/subjects_part.csv') + write_csv(subject_part_content, subject_part_csv_path) + + yield subject_part_content, subject_part_csv_path + subject_part_csv_path.unlink() + + +@pytest.fixture +def ingest_subjects(pipeline, subjects_csv, subjects_part_csv): """From workflow_session ingest.py, import ingest_subjects, run""" from workflow_session.ingest import ingest_subjects _, subject_csv_path = subjects_csv - ingest_subjects(subject_csv_path=subject_csv_path) + _, subject_part_csv_path = subjects_part_csv + ingest_subjects(subject_csv_path=subject_csv_path, + subject_part_csv_path=subject_part_csv_path) return @@ -231,10 +254,12 @@ def ingest_subjects(pipeline, subjects_csv): def sessions_csv(): """ Create a 'sessions.csv' file""" session_csv_path = pathlib.Path('./tests/user_data/session/sessions.csv') - session_content = ["subject,session_datetime,session_dir,session_note", - "subject5,2020-04-15 11:16:38,/subject5/session1," + session_content = ["subject,project,session_datetime,session_dir,session_note", + "subject5,ProjA,2020-04-15 11:16:38,/subject5/session1," + "'Successful data collection, no notes'", - "subject6,2021-06-02 14:04:22,/subject6/session1," + "subject5,ProjA,2020-05-12 04:13:07,subject5\\session1," + + "'Data collection notes'", + "subject6,ProjA,2021-06-02 14:04:22,/subject6/session1," + "'Ambient temp abnormally low'"] write_csv(session_content, session_csv_path) diff --git a/tests/test_ingest.py b/tests/test_ingest.py index 18b486d..b532263 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -1,18 +1,18 @@ '''Tests ingestion into schema tables: Lab, Subject, Session - 1. Assert length of populating data from __innit__ + 1. Assert length of populating data from __init__ 2. Assert exact matches of inserted data fore key tables ''' __all__ = ['dj_config', 'pipeline', 'lab_csv', 'lab_project_csv', 'lab_user_csv', 'lab_publications_csv', 'lab_keywords_csv', 'lab_protocol_csv', - 'lab_project_users_csv', 'ingest_lab', 'subjects_csv', 'ingest_subjects', - 'sessions_csv', 'ingest_sessions'] + 'lab_project_users_csv', 'ingest_lab', 'subjects_csv', 'subjects_part_csv', + 'ingest_subjects', 'sessions_csv', 'ingest_sessions'] from . import (dj_config, pipeline, lab_csv, lab_project_csv, lab_user_csv, lab_publications_csv, lab_keywords_csv, lab_protocol_csv, lab_project_users_csv, ingest_lab, - subjects_csv, ingest_subjects, + subjects_csv, subjects_part_csv, ingest_subjects, sessions_csv, ingest_sessions) @@ -20,15 +20,18 @@ def test_ingest_lab(pipeline, ingest_lab, lab_csv, lab_project_csv, lab_protocol_csv): """Check length of various lab schema tables""" lab = pipeline['lab'] - assert len(lab.Lab()) == 2 - assert len(lab.LabMembership()) == 3 - assert len(lab.User()) == 3 - assert len(lab.UserRole()) == 2 - assert len(lab.Location()) == 2 - assert len(lab.Project()) == 2 - assert len(lab.ProjectUser()) == 4 - assert len(lab.Protocol()) == 2 - assert len(lab.ProtocolType()) == 2 + assert len(lab.Lab()) == 2, f'Check Lab: len={len(lab.Lab())}' + assert len(lab.LabMembership()) == 4, \ + f'Check LabMembership: len={len(lab.LabMembership())}' + assert len(lab.User()) == 4, f'Check User: len={len(lab.User())}' + assert len(lab.UserRole()) == 3, f'Check UserRole: len={len(lab.UserRole())}' + assert len(lab.Location()) == 2, f'Check Location: len={len(lab.Location())}' + assert len(lab.Project()) == 2, f'Check Project: len={len(lab.Project())}' + assert len(lab.ProjectUser()) == 5, \ + f'Check ProjectUser: len={len(lab.ProjectUser())}' + assert len(lab.Protocol()) == 2, f'Check Protocol: len={len(lab.Protocol())}' + assert len(lab.ProtocolType()) == 2, \ + f'Check ProtocolType: len={len(lab.ProtocolType())}' labs, _ = lab_csv for this_lab in labs[1:]: @@ -52,16 +55,27 @@ def test_ingest_lab(pipeline, ingest_lab, # assert len(lab.Source()) == 0 -def test_ingest_subjects(pipeline, subjects_csv, ingest_subjects): +def test_ingest_subjects(pipeline, subjects_csv, subjects_part_csv, ingest_subjects): """Check length of subject.Subject""" subject = pipeline['subject'] - assert len(subject.Subject()) == 2 + assert len(subject.Subject()) == 2, f'Check Subject: len={len(subject.Subject())}' + assert len(subject.Subject.Protocol()) == 2, \ + f'Check Subject.Protocol: len={len(subject.Subject.Protocol())}' + assert len(subject.Subject.User()) == 2, \ + f'Check Subject.User: len={len(subject.Subject.User())}' subjects, _ = subjects_csv + subjects_parts, _ = subjects_part_csv for this_subject in subjects[1:]: subject_values = this_subject.split(",") assert (subject.Subject & {'subject': subject_values[0]} ).fetch1('subject_description') == subject_values[3] + for this_subject in subjects_parts[1:]: + subject_values = this_subject.split(",") + assert (subject.Subject.Protocol & {'subject': subject_values[0]} + ).fetch1('protocol') == subject_values[1] + assert (subject.Subject.User & {'subject': subject_values[0]} + ).fetch1('user') == subject_values[2] # Does not have example data: # assert len(genotyping.Sequence()) == 0 @@ -70,11 +84,12 @@ def test_ingest_subjects(pipeline, subjects_csv, ingest_subjects): def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): """Check length/contents of Session.SessionDirectory""" session = pipeline['session'] - assert len(session.Session()) == 2 + assert len(session.Session()) == 3, f'Check Session: len={len(session.Session())}' + assert len(session.ProjectSession()) == 3, \ + f'Check ProjectSession: len={len(session.ProjectSession())}' sessions, _ = sessions_csv for sess in sessions[1:]: sess = sess.split(",") - assert (session.SessionDirectory - & {'subject': sess[0]} - ).fetch1('session_dir') == sess[2] + assert (session.SessionDirectory & {'subject': sess[0]} + & {'session_datetime': sess[2]}).fetch1('session_dir') == sess[3] diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index 2136fa1..79c10b5 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -4,6 +4,8 @@ 3. Assert subject link to session ''' +__all__ = ['pipeline'] + from . import pipeline @@ -20,6 +22,22 @@ def test_generate_pipeline(pipeline): assert subject_lab_tbl.full_table_name == \ subject.Subject.Lab.full_table_name + # test connection Subject -> schema children + session_tbl, _, subject_line_tbl, subject_protocol_tbl, subject_source_tbl, \ + subject_strain_tbl, subject_user_tbl, subject_cull_table, subject_death_tbl,\ + subject_zygotsity_tbl = subject.Subject.children(as_objects=True) + assert session_tbl.full_table_name == session.Session.full_table_name + assert subject_line_tbl.full_table_name == subject.Subject.Line.full_table_name + assert subject_protocol_tbl.full_table_name == \ + subject.Subject.Protocol.full_table_name + assert subject_source_tbl.full_table_name == subject.Subject.Source.full_table_name + assert subject_strain_tbl.full_table_name == subject.Subject.Strain.full_table_name + assert subject_user_tbl.full_table_name == subject.Subject.User.full_table_name + assert subject_cull_table.full_table_name == \ + subject.SubjectCullMethod.full_table_name + assert subject_death_tbl.full_table_name == subject.SubjectDeath.full_table_name + assert subject_zygotsity_tbl.full_table_name == subject.Zygosity.full_table_name + # test connection Subject->Session subject_tbl, *_ = session.Session.parents(as_objects=True) assert subject_tbl.full_table_name == subject.Subject.full_table_name diff --git a/user_data/lab/project_users.csv b/user_data/lab/project_users.csv index 332ff90..fe21d31 100644 --- a/user_data/lab/project_users.csv +++ b/user_data/lab/project_users.csv @@ -2,4 +2,5 @@ user,project Sherlock,ProjA Sherlock,ProjB Watson,ProjB -Dr. Candace Pert,ProjA \ No newline at end of file +Dr. Candace Pert,ProjA +User1,ProjA diff --git a/user_data/lab/users.csv b/user_data/lab/users.csv index 29d0ed5..6d6a8bd 100644 --- a/user_data/lab/users.csv +++ b/user_data/lab/users.csv @@ -1,4 +1,5 @@ lab,user,user_role,user_email,user_cellphone LabA,Sherlock,PI,Sherlock@BakerSt.com,+44 20 7946 0344 LabA,Watson,Dr,DrWatson@BakerSt.com,+44 73 8389 1763 -LabB,Dr. Candace Pert,PI,Pert@gmail.com,+44 74 4046 5899 \ No newline at end of file +LabB,Dr. Candace Pert,PI,Pert@gmail.com,+44 74 4046 5899 +LabA,User1,Lab Tech,fake@email.com,+44 1632 960103 diff --git a/user_data/session/sessions.csv b/user_data/session/sessions.csv index c316d74..1d83729 100644 --- a/user_data/session/sessions.csv +++ b/user_data/session/sessions.csv @@ -1,3 +1,4 @@ -subject,session_datetime,session_dir,session_note -subject5,2020-04-15 11:16:38,/subject5/session1,'Successful data collection, no notes' -subject6,2021-06-02 14:04:22,/subject6/session1,'Ambient temp abnormally low' \ No newline at end of file +subject,project,session_datetime,session_dir,session_note +subject5,ProjA,2020-04-15 11:16:38,/subject5/session1,'Successful data collection, no notes' +subject5,ProjA,2020-05-12 04:13:07,subject5\session1,'Data collection notes' +subject6,ProjA,2021-06-02 14:04:22,/subject6/session1,'Ambient temp abnormally low' diff --git a/user_data/subject/subjects.csv b/user_data/subject/subjects.csv index 862269b..bf63d78 100644 --- a/user_data/subject/subjects.csv +++ b/user_data/subject/subjects.csv @@ -1,3 +1,3 @@ subject,sex,subject_birth_date,subject_description,death_date,cull_method subject5,F,2020-01-01 00:00:01,rich,2020-10-02 00:00:01,natural causes -subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes \ No newline at end of file +subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes diff --git a/user_data/subject/subjects_part.csv b/user_data/subject/subjects_part.csv new file mode 100644 index 0000000..ef99174 --- /dev/null +++ b/user_data/subject/subjects_part.csv @@ -0,0 +1,3 @@ +subject,protocol,user,line,strain,source,lab +subject6,ProtA,User1,line,strain,source,LabA +subject5,ProtA,User1,line,strain,source,LabA diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 6d877ec..f1fd7a4 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -66,6 +66,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', + subject_part_csv_path='./user_data/subject/subjects_part.csv', skip_duplicates=True): """ Inserts data from a subject csv into corresponding subject schema tables @@ -73,9 +74,10 @@ def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', :param subject_csv_path: relative path of subject csv :param skip_duplicates=True: datajoint insert function param """ - csvs = [subject_csv_path, subject_csv_path, subject_csv_path] - tables = [subject.Subject(), subject.SubjectDeath(), - subject.SubjectCullMethod()] + csvs = [subject_csv_path, subject_csv_path, subject_csv_path, + subject_part_csv_path, subject_part_csv_path] + tables = [subject.Subject(), subject.SubjectDeath(), subject.SubjectCullMethod(), + subject.Subject.Protocol(), subject.Subject.User()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) @@ -90,9 +92,9 @@ def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', :param session_csv_path: relative path of subject csv :param skip_duplicates=True: datajoint insert function param """ - csvs = [session_csv_path, session_csv_path, session_csv_path] + csvs = [session_csv_path, session_csv_path, session_csv_path, session_csv_path] tables = [session.Session(), session.SessionDirectory(), - session.SessionNote()] + session.SessionNote(), session.ProjectSession()] ingest_general(csvs, tables, skip_duplicates=skip_duplicates) diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index e3fb0b6..dc9d301 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -7,9 +7,10 @@ from element_animal.subject import Subject # from element_animal.genotyping import Sequence, BreedingPair, Cage,\ # SubjectCaging, GenotypeTest -from element_lab.lab import Source, Lab, Protocol, User, Project, ProjectKeywords,\ - ProjectPublication, ProjectSourceCode, ProjectUser -from element_session.session import Session +from element_lab.lab import Source, Lab, Protocol, User, Project, ProjectUser +from element_lab.lab import ProjectKeywords, ProjectPublication, ProjectSourceCode +from element_session.session import Session, SessionDirectory, SessionExperimenter +from element_session.session import SessionNote, ProjectSession if 'custom' not in dj.config: dj.config['custom'] = {} @@ -18,7 +19,8 @@ __all__ = ['genotyping', 'session', 'Subject', 'Source', 'Lab', 'Protocol', 'User', 'Project', 'ProjectKeywords', 'ProjectPublication', 'ProjectSourceCode', - 'ProjectUser', 'Session'] + 'ProjectUser', 'Session', 'SessionDirectory', 'SessionExperimenter', + 'SessionNote', 'ProjectSession'] # Activate "lab", "subject", "session" schema ------------- From 32df6bce3713367d148902429e6ccb95c3e352da Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Thu, 3 Feb 2022 12:49:30 -0600 Subject: [PATCH 32/46] Fix coverage output, Add verbosity option. See details docker-compose-test.yaml: fix -cov hyphen->underscore requirements.txt: version bump element-lab to new version with ProjectKeywords table setup.py: restructure following wf-ephys format tests/__init__.py: add verbosity boolean to clean integration test output workflow_session/ingest.py: add optional verbosity function arguments workflow_session/paths.py: remove unused root_dir function regarding verbosity: I found removing print statements made it easier to see which tests passed --- docker-compose-test.yaml | 3 ++- requirements.txt | 2 +- setup.py | 8 +++++--- tests/__init__.py | 39 ++++++++++++++++++++++++++++---------- workflow_session/ingest.py | 35 ++++++++++++++++++++-------------- workflow_session/paths.py | 6 ------ 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 298661c..28cbe7d 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -1,3 +1,4 @@ +# export COMPOSE_DOCKER_CLI_BUILD=0 # some machines need for smooth --build # docker-compose -f docker-compose-test.yaml up --build # docker exec -it workflow-session_workflow_1 /bin/bash # docker-compose -f docker-compose-test.yaml down @@ -29,7 +30,7 @@ services: - -c - | echo "------ INTEGRATION TESTS ------" - pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings + pytest -sv --cov-report term-missing --cov=workflow_session -p no:warnings tail -f /dev/null volumes: - ./apt_requirements.txt:/tmp/apt_requirements.txt diff --git a/requirements.txt b/requirements.txt index 3cf9e25..8cf9a5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ datajoint>=0.13.0 -element-lab==0.1.0b0 +element-lab==0.1.0b1 element-animal==0.1.0b0 element-session==0.1.0b0 ipykernel diff --git a/setup.py b/setup.py index cc6bd5d..bbdb2fa 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python from setuptools import setup, find_packages from os import path -import sys +pkg_name = 'workflow_session' here = path.abspath(path.dirname(__file__)) long_description = """" @@ -17,6 +16,9 @@ with open(path.join(here, 'requirements.txt')) as f: requirements = f.read().splitlines() +with open(path.join(here, pkg_name, 'version.py')) as f: + exec(f.read()) + setup( name='workflow-session', version=__version__, @@ -26,7 +28,7 @@ author_email='info@datajoint.com', license='MIT', url='https://github.com/datajoint/workflow-session', - keywords='neuroscience lab-management animal-management session-management datajoint', + keywords='neuroscience lab-management animal-management session datajoint', packages=find_packages(exclude=['contrib', 'docs', 'tests*']), install_requires=requirements, ) diff --git a/tests/__init__.py b/tests/__init__.py index 380f343..3abb04f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,11 +1,12 @@ ''' run all tests: - pytest -sv --cov-report term-missing --cov=workflow-session -p no:warnings tests/ + pytest -sv --cov-report term-missing --cov=workflow_session -p no:warnings tests/ run one test, debug: pytest [above options] --pdb tests/tests_name.py -k function_name ''' import os +import sys import pytest import pathlib import datajoint as dj @@ -13,6 +14,7 @@ # ------------------- SOME CONSTANTS ------------------- _tear_down = True +verbose = False pathlib.Path('./tests/user_data').mkdir(exist_ok=True) pathlib.Path('./tests/user_data/lab').mkdir(exist_ok=True) @@ -32,7 +34,18 @@ def write_csv(content, path): for line in content: f.write(line+'\n') -# ------------------- FIXTURES ------------------- + +class QuietStdOut: + """If verbose set to false, used to quiet tear_down table.delete prints""" + def __enter__(self): + self._original_stdout = sys.stdout + sys.stdout = open(os.devnull, 'w') + + def __exit__(self, exc_type, exc_val, exc_tb): + sys.stdout.close() + sys.stdout = self._original_stdout + +# ---------------------- FIXTURES ---------------------- @pytest.fixture(autouse=True) @@ -63,9 +76,15 @@ def pipeline(): 'lab': pipeline.lab} if _tear_down: - pipeline.subject.Subject.delete() - pipeline.session.Session.delete() - pipeline.lab.Lab.delete() + if verbose: + pipeline.subject.Subject.delete() + pipeline.session.Session.delete() + pipeline.lab.Lab.delete() + else: + with QuietStdOut(): + pipeline.subject.Subject.delete() + pipeline.session.Session.delete() + pipeline.lab.Lab.delete() @pytest.fixture @@ -204,7 +223,7 @@ def ingest_lab(pipeline, lab_csv, lab_project_csv, lab_publications_csv, keyword_csv_path=lab_keyword_csv_path, protocol_csv_path=lab_protocol_csv_path, users_csv_path=lab_user_csv_path, - project_user_csv_path=lab_project_user_csv_path) + project_user_csv_path=lab_project_user_csv_path, verbose=verbose) return @@ -239,13 +258,13 @@ def subjects_part_csv(): @pytest.fixture -def ingest_subjects(pipeline, subjects_csv, subjects_part_csv): +def ingest_subjects(pipeline, ingest_lab, subjects_csv, subjects_part_csv): """From workflow_session ingest.py, import ingest_subjects, run""" from workflow_session.ingest import ingest_subjects _, subject_csv_path = subjects_csv _, subject_part_csv_path = subjects_part_csv ingest_subjects(subject_csv_path=subject_csv_path, - subject_part_csv_path=subject_part_csv_path) + subject_part_csv_path=subject_part_csv_path, verbose=verbose) return @@ -268,11 +287,11 @@ def sessions_csv(): @pytest.fixture -def ingest_sessions(ingest_subjects, sessions_csv): +def ingest_sessions(ingest_lab, ingest_subjects, sessions_csv): """From workflow_session ingest.py, import ingest_sessions, run""" from workflow_session.ingest import ingest_sessions _, session_csv_path = sessions_csv - ingest_sessions(session_csv_path=session_csv_path) + ingest_sessions(session_csv_path=session_csv_path, verbose=verbose) return diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index f1fd7a4..59daaae 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -3,7 +3,7 @@ def ingest_general(csvs, tables, - skip_duplicates=True): + skip_duplicates=True, verbose=True): """ Inserts data from a series of csvs into their corresponding table: e.g., ingest_general(['./lab_data.csv', './proj_data.csv'], @@ -11,17 +11,20 @@ def ingest_general(csvs, tables, ingest_general(csvs, tables, skip_duplicates=True) :param csvs: list of relative paths to CSV files :param tables: list of datajoint tables with () + :param verbose: print number inserted (i.e., table length change) """ for insert, table in zip(csvs, tables): with open(insert, newline='') as f: data = list(csv.DictReader(f, delimiter=',')) - prev_len = len(table) + if verbose: + prev_len = len(table) table.insert(data, skip_duplicates=skip_duplicates, # Ignore Extra because some CSVs feed/mult tables ignore_extra_fields=True) - insert_len = len(table) - prev_len # report length change - print(f'\n---- Inserting {insert_len} entry(s) ' - + f'into {table.table_name} ----') + if verbose: + insert_len = len(table) - prev_len # report length change + print(f'\n---- Inserting {insert_len} entry(s) ' + + f'into {table.table_name} ----') # Future enhancement: permit embedded lists # Currently requires a csv to be listed multiple times @@ -35,7 +38,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', protocol_csv_path='./user_data/lab/protocols.csv', users_csv_path='./user_data/lab/users.csv', project_user_csv_path='./user_data/lab/project_users.csv', - skip_duplicates=True): + skip_duplicates=True, verbose=True): """ Inserts data from a CSVs into their corresponding lab schema tables. By default, uses data from workflow_session/user_data/lab/ @@ -46,6 +49,7 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', :param protocol_csv_path: relative path of protocol csv :param users_csv_path: relative path of users csv :param skip_duplicates=True: datajoint insert function param + :param verbose: print number inserted (i.e., table length change) """ # List with repeats for when mult dj.tables fed by same CSV @@ -62,41 +66,44 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', lab.UserRole(), lab.User(), lab.LabMembership(), lab.ProjectUser()] - ingest_general(csvs, tables, skip_duplicates=skip_duplicates) + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', subject_part_csv_path='./user_data/subject/subjects_part.csv', - skip_duplicates=True): + skip_duplicates=True, verbose=True): """ Inserts data from a subject csv into corresponding subject schema tables By default, uses data from workflow_session/user_data/subject/ - :param subject_csv_path: relative path of subject csv + :param subject_csv_path: relative path of csv for subject data + :param subject_part_csv_path: relative path of csv for subject part tables :param skip_duplicates=True: datajoint insert function param + :param verbose: print number inserted (i.e., table length change) """ csvs = [subject_csv_path, subject_csv_path, subject_csv_path, - subject_part_csv_path, subject_part_csv_path] + subject_part_csv_path, subject_part_csv_path, subject_part_csv_path] tables = [subject.Subject(), subject.SubjectDeath(), subject.SubjectCullMethod(), - subject.Subject.Protocol(), subject.Subject.User()] + subject.Subject.Protocol(), subject.Subject.User(), subject.Subject.Lab()] - ingest_general(csvs, tables, skip_duplicates=skip_duplicates) + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) # TODO: add allele and genotyping data def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', - skip_duplicates=True): + skip_duplicates=True, verbose=True): """ Inserts data from a sessions csv into corresponding session schema tables By default, uses data from workflow_session/user_data/session/ :param session_csv_path: relative path of subject csv :param skip_duplicates=True: datajoint insert function param + :param verbose: print number inserted (i.e., table length change) """ csvs = [session_csv_path, session_csv_path, session_csv_path, session_csv_path] tables = [session.Session(), session.SessionDirectory(), session.SessionNote(), session.ProjectSession()] - ingest_general(csvs, tables, skip_duplicates=skip_duplicates) + ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) if __name__ == '__main__': diff --git a/workflow_session/paths.py b/workflow_session/paths.py index 2e87d92..53ea76b 100644 --- a/workflow_session/paths.py +++ b/workflow_session/paths.py @@ -1,9 +1,3 @@ -import datajoint as dj - - -def get_root_data_dir(): - root_data_dirs = dj.config.get('custom', {}).get('root_data_dir', None) - return root_data_dirs if root_data_dirs else None def get_session_directory(session_key: dict) -> str: From 3409ded4ba3ac121cac4e87e307547c72b0f4a39 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 28 Feb 2022 09:24:27 -0600 Subject: [PATCH 33/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- CHANGELOG.md | 4 ++-- LICENSE | 2 +- apt-requirements.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a43c5..cdf4eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,12 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and + Name `workflow-animal` -> `workflow-session` -## 0.0.0b1 - 2021-03-24 +## [0.0.0b1] - 2021-03-24 ### Added + First beta release -## 0.0.0a1 - 2021-03-18 +## [0.0.0a1] - 2021-03-18 ### Added + Added notebooks diff --git a/LICENSE b/LICENSE index 6bf141b..2f92789 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 DataJoint +Copyright (c) 2022 DataJoint Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/apt-requirements.txt b/apt-requirements.txt index 3daada4..2907e3f 100644 --- a/apt-requirements.txt +++ b/apt-requirements.txt @@ -1 +1,2 @@ +git locales-all \ No newline at end of file From 9b613b0610807c067bdd98744561bcc2f58f22ec Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 28 Feb 2022 09:25:09 -0600 Subject: [PATCH 34/46] rename apt_requirements.txt --- .../apt-requirements.txt | 0 user_data/subject/subjects.csv | 3 +++ workflow_session/export.py | 3 +++ 3 files changed, 6 insertions(+) rename apt-requirements.txt => apt_requirements.txt/apt-requirements.txt (100%) create mode 100644 workflow_session/export.py diff --git a/apt-requirements.txt b/apt_requirements.txt/apt-requirements.txt similarity index 100% rename from apt-requirements.txt rename to apt_requirements.txt/apt-requirements.txt diff --git a/user_data/subject/subjects.csv b/user_data/subject/subjects.csv index bf63d78..c11552f 100644 --- a/user_data/subject/subjects.csv +++ b/user_data/subject/subjects.csv @@ -1,3 +1,6 @@ subject,sex,subject_birth_date,subject_description,death_date,cull_method subject5,F,2020-01-01 00:00:01,rich,2020-10-02 00:00:01,natural causes subject6,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes +subjectX,F,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes +subjectY,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes +subjectZ,M,2020-01-01 00:00:01,manuel,2020-10-03 00:00:01,natural causes diff --git a/workflow_session/export.py b/workflow_session/export.py new file mode 100644 index 0000000..5a92c2f --- /dev/null +++ b/workflow_session/export.py @@ -0,0 +1,3 @@ +import datajoint as dj + +from element_session.export.nwb import session_to_nwb From c48bdf6270243abdfe70c1ef66c4d9c28503067e Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 28 Feb 2022 13:02:54 -0600 Subject: [PATCH 35/46] rename apt_requirements --- ....txt => apt_requirements.txt~6b9528a (rename apt_requirements) | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apt_requirements.txt/apt-requirements.txt => apt_requirements.txt~6b9528a (rename apt_requirements) (100%) diff --git a/apt_requirements.txt/apt-requirements.txt b/apt_requirements.txt~6b9528a (rename apt_requirements) similarity index 100% rename from apt_requirements.txt/apt-requirements.txt rename to apt_requirements.txt~6b9528a (rename apt_requirements) From 38749c19405ef54e4cdc57e2526fbf780e576c33 Mon Sep 17 00:00:00 2001 From: Chris Broz Date: Mon, 28 Feb 2022 13:05:13 -0600 Subject: [PATCH 36/46] rename typo --- ....txt~6b9528a (rename apt_requirements) => apt_requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apt_requirements.txt~6b9528a (rename apt_requirements) => apt_requirements.txt (100%) diff --git a/apt_requirements.txt~6b9528a (rename apt_requirements) b/apt_requirements.txt similarity index 100% rename from apt_requirements.txt~6b9528a (rename apt_requirements) rename to apt_requirements.txt From 223d8b9e48ba5b5beac56eee6d137875de710e0c Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Tue, 1 Mar 2022 16:11:24 -0600 Subject: [PATCH 37/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- Dockerfile.test | 4 ++-- docker-compose-test.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile.test b/Dockerfile.test index 5351e15..c9e963c 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -8,14 +8,14 @@ USER anaconda WORKDIR /main/workflow-session # Option 1 - Install DataJoint's remote fork of the workflow and elements -# RUN git clone https://github.com/datajoint/workflow-session.git /main/workflow-session +# RUN git clone https://github.com/datajoint/workflow-session.git /main/ # Option 2 - Install user's remote fork of element and workflow # or an unreleased version of the element # RUN pip install git+https://github.com/cbroz1/element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git -# RUN git clone https://github.com//workflow-session.git /main/workflow-session +# RUN git clone https://github.com//workflow-session.git /main/ # Option 3 - Install user's local fork of element and workflow RUN mkdir /main/element-lab diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 28cbe7d..7312259 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -21,7 +21,7 @@ services: env_file: .env image: workflow_session:0.0.0b2 environment: - - DJ_HOST=workflow-session_db_1 + - DJ_HOST=db - DJ_USER=root - DJ_PASS=simple - DATABASE_PREFIX=test_ From ab6720e0e1c3033d7016039ec15b8b0bd11d0c96 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Tue, 1 Mar 2022 16:49:55 -0600 Subject: [PATCH 38/46] Apply suggestions from code review: revise import formatting Co-authored-by: Kabilar Gunalan --- workflow_session/pipeline.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index dc9d301..e52eef1 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -5,12 +5,12 @@ from element_session import session from element_animal.subject import Subject -# from element_animal.genotyping import Sequence, BreedingPair, Cage,\ -# SubjectCaging, GenotypeTest -from element_lab.lab import Source, Lab, Protocol, User, Project, ProjectUser -from element_lab.lab import ProjectKeywords, ProjectPublication, ProjectSourceCode -from element_session.session import Session, SessionDirectory, SessionExperimenter -from element_session.session import SessionNote, ProjectSession +from element_animal.genotyping import Sequence, BreedingPair, Cage,\ + SubjectCaging, GenotypeTest +from element_lab.lab import Source, Lab, Protocol, User, Project, ProjectUser, \ + ProjectKeywords, ProjectPublication, ProjectSourceCode +from element_session.session import Session, SessionDirectory, SessionExperimenter, \ + SessionNote, ProjectSession if 'custom' not in dj.config: dj.config['custom'] = {} From 51e3f7a7efaee884f31f499ff52ba6ee789f122a Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Tue, 1 Mar 2022 16:57:41 -0600 Subject: [PATCH 39/46] Apply suggestions from code review: activate genotyping shema Co-authored-by: Kabilar Gunalan --- workflow_session/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow_session/pipeline.py b/workflow_session/pipeline.py index e52eef1..dc56017 100644 --- a/workflow_session/pipeline.py +++ b/workflow_session/pipeline.py @@ -22,7 +22,7 @@ 'ProjectUser', 'Session', 'SessionDirectory', 'SessionExperimenter', 'SessionNote', 'ProjectSession'] -# Activate "lab", "subject", "session" schema ------------- +# Activate "lab", "subject", "session", "genotyping" schemas ------------- lab.activate(db_prefix + 'lab') @@ -31,4 +31,4 @@ Experimenter = lab.User session.activate(db_prefix + 'session', linking_module=__name__) -# genotyping.activate(db_prefix + 'genotyping', linking_module=__name__) +genotyping.activate(db_prefix + 'genotyping', db_prefix + 'subject', linking_module=__name__) From c10c7020278f65f87ce083ae2d07c919f4f4fcb1 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Thu, 3 Mar 2022 11:16:29 -0600 Subject: [PATCH 40/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- Dockerfile.test | 10 +++------- user_data/lab/sources.csv | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Dockerfile.test b/Dockerfile.test index c9e963c..e676e28 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,10 +1,6 @@ FROM datajoint/djlab:py3.8-debian -USER root -RUN apt-get update -y -RUN apt-get install git -y - -USER anaconda +USER anaconda:anaconda WORKDIR /main/workflow-session # Option 1 - Install DataJoint's remote fork of the workflow and elements @@ -12,7 +8,7 @@ WORKDIR /main/workflow-session # Option 2 - Install user's remote fork of element and workflow # or an unreleased version of the element -# RUN pip install git+https://github.com/cbroz1/element-lab.git +# RUN pip install git+https://github.com//element-lab.git # RUN pip install git+https://github.com//element-animal.git # RUN pip install git+https://github.com//element-session.git # RUN git clone https://github.com//workflow-session.git /main/ @@ -31,5 +27,5 @@ COPY --chown=anaconda:anaconda ./workflow-session /main/workflow-session # RUN rm -f /main/workflow-session/dj_local_conf.json # Install the workflow -RUN pip install /main/workflow-session +RUN pip install -e /main/workflow-session RUN pip install -r /main/workflow-session/requirements_test.txt diff --git a/user_data/lab/sources.csv b/user_data/lab/sources.csv index 43c5cee..dc9eafb 100644 --- a/user_data/lab/sources.csv +++ b/user_data/lab/sources.csv @@ -1,2 +1,2 @@ source, source_name, contact_details, source_description -Provider1, Example Provider, +44 1632 960663 / Example@Provider.com, UK-based supplier of lab subjcts mus musculus \ No newline at end of file +Provider1, Example Provider, +44 1632 960663 / Example@Provider.com, UK-based supplier of lab subjects mus musculus \ No newline at end of file From 18f5a19d6166c11e79d467cb923b3e0e23a13436 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Thu, 3 Mar 2022 11:17:30 -0600 Subject: [PATCH 41/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- Dockerfile.dev | 6 +----- README.md | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index a259816..1f2038c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,10 +1,6 @@ FROM datajoint/djlab:py3.8-debian -USER root -RUN apt-get update -y -RUN apt-get install git -y - -USER anaconda +USER anaconda:anaconda RUN mkdir /main/element-lab \ /main/element-animal \ diff --git a/README.md b/README.md index 2e9d1e5..2ff505c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository provides demonstrations for: Setting up a workflow using different elements (see [pipeline.py](workflow_session/pipeline.py)) ## Workflow architecture -The lab and experiment subject management workflow presented here uses components from two DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. +The lab and experiment subject management workflow presented here uses components from three DataJoint elements (element-lab, element-animal and element-session) assembled together into a functional workflow. ### element-lab From 4542ed56d6ea2ff5b356fa2d7aaf6529fd0fa9f9 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Fri, 4 Mar 2022 11:10:11 -0600 Subject: [PATCH 42/46] Apply all suggestions from code review Co-authored-by: Kabilar Gunalan --- Dockerfile.dev | 3 +++ Dockerfile.test | 3 +++ docker-compose-test.yaml | 2 +- workflow_session/ingest.py | 10 +++------- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 1f2038c..9751737 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,6 +2,9 @@ FROM datajoint/djlab:py3.8-debian USER anaconda:anaconda +COPY ./workflow-session/apt_requirements.txt /tmp/ +RUN /entrypoint.sh echo "Installed dependencies." + RUN mkdir /main/element-lab \ /main/element-animal \ /main/element-session \ diff --git a/Dockerfile.test b/Dockerfile.test index e676e28..5975f27 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -1,6 +1,9 @@ FROM datajoint/djlab:py3.8-debian USER anaconda:anaconda + +COPY ./workflow-session/apt_requirements.txt /tmp/ +RUN /entrypoint.sh echo "Installed dependencies." WORKDIR /main/workflow-session # Option 1 - Install DataJoint's remote fork of the workflow and elements diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 7312259..3c4b7f4 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -19,7 +19,7 @@ services: context: ../ dockerfile: ./workflow-session/Dockerfile.test env_file: .env - image: workflow_session:0.0.0b2 + image: workflow_session_test:0.0.0b2 environment: - DJ_HOST=db - DJ_USER=root diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index 59daaae..c845669 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -13,22 +13,19 @@ def ingest_general(csvs, tables, :param tables: list of datajoint tables with () :param verbose: print number inserted (i.e., table length change) """ - for insert, table in zip(csvs, tables): - with open(insert, newline='') as f: + for csv_filepath, table in zip(csvs, tables): + with open(csv_filepath, newline='') as f: data = list(csv.DictReader(f, delimiter=',')) if verbose: prev_len = len(table) table.insert(data, skip_duplicates=skip_duplicates, - # Ignore Extra because some CSVs feed/mult tables + # Ignore extra fields because some CSVs feed multiple tables ignore_extra_fields=True) if verbose: insert_len = len(table) - prev_len # report length change print(f'\n---- Inserting {insert_len} entry(s) ' + f'into {table.table_name} ----') - # Future enhancement: permit embedded lists - # Currently requires a csv to be listed multiple times - # if feeding multiple tables. Could instead take lists of lists. def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', @@ -87,7 +84,6 @@ def ingest_subjects(subject_csv_path='./user_data/subject/subjects.csv', ingest_general(csvs, tables, skip_duplicates=skip_duplicates, verbose=verbose) - # TODO: add allele and genotyping data def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', From a471eafb12d1d5301f6912e35d318135c328f3af Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 7 Mar 2022 07:16:33 -0600 Subject: [PATCH 43/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- README.md | 8 ++++---- tests/__init__.py | 40 +------------------------------------- tests/test_ingest.py | 6 +----- workflow_session/ingest.py | 7 ++++--- 4 files changed, 10 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 2ff505c..39361af 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The lab and experiment subject management workflow presented here uses component ### element-lab ![element-lab]( -https://github.com/datajoint/element-lab/raw/main/images/element_lab_diagram.svg) +https://github.com/datajoint/element-lab/raw/main/images/lab_diagram.svg) ### element-animal @@ -37,11 +37,11 @@ This workflow serves as an example of the upstream part of a typical data workfl ## Installation instructions -+ The installation instructions can be found at [datajoint-elements/install.md]( - https://github.com/datajoint/datajoint-elements/blob/main/install.md). ++ The installation instructions can be found in the [datajoint-elements repository]( + https://github.com/datajoint/datajoint-elements/blob/main/gh-pages/docs/install.md). ## Interacting with the DataJoint workflow + Please refer to the following workflow-specific [Jupyter notebooks](/notebooks) for an in-depth explanation of how to run the -workflow ([1-Explore_Workflow.ipynb](notebooks/1_Explore_Workflow.ipynb). +workflow ([1-Explore_Workflow.ipynb](notebooks/1_Explore_Workflow.ipynb)). diff --git a/tests/__init__.py b/tests/__init__.py index 3abb04f..c9e0193 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -21,7 +21,7 @@ pathlib.Path('./tests/user_data/session').mkdir(exist_ok=True) pathlib.Path('./tests/user_data/subject').mkdir(exist_ok=True) -# ------------------ GENERAL FUCNTION ------------------ +# ------------------ GENERAL FUNCTIONS ------------------ def write_csv(content, path): @@ -295,41 +295,3 @@ def ingest_sessions(ingest_lab, ingest_subjects, sessions_csv): return -""" -# FUTURE FIXTURES, pending sharable example data -lab_sources_content=["source, source_name, contact_details,\ - source_description"] -lab_sources_csv_path = pathlib.Path('./tests/user_data/lab/sources.csv') -write_csv(lab_sources_content,lab_sources_csv_path) -yeild lab_sources_content,lab_sources_csv_path -lab_sources_csv_path.unlink() -subject_allele_content=["allele, allele_standard_name,zygosity"] -subject_allele_csv_path = pathlib.Path('./tests/user_data/subject/allele.csv') -write_csv(subject_allele_content,subject_allele_csv_path) -yeild subject_allele_content,subject_allele_csv_path -subject_allele_csv_path.unlink() -subject_genotyping_content=[] -subject_genotyping_csv_path = pathlib.Path('./tests/user_data/subject/\ - genotyping.csv') -write_csv(subject_genotyping_content,subject_genotyping_csv_path) -yeild subject_genotyping_content,subject_genotyping_csv_path -subject_genotyping_csv_path.unlink() -subject_line_content=["line, line_description, target_phenotype, is_active"] -subject_line_csv_path = pathlib.Path('./tests/user_data/subject/line.csv') -write_csv(subject_line_content,subject_line_csv_path) -yeild subject_line_content,subject_line_csv_path -subject_line_csv_path.unlink() -subject_source_content=["allele,source_identifier,source_url,\ - expression_data_url"] -subject_source_csv_path = pathlib.Path('./tests/user_data/subject/source.csv') -write_csv(subject_source_content,subject_source_csv_path) -yeild subject_source_content,subject_source_csv_path -subject_source_csv_path.unlink() -subject_strain_content=["strain, strain_standard_name, strain_desc"] -subject_strain_csv_path = pathlib.Path('./tests/user_data/subject/strain.csv') -write_csv(subject_strain_content,subject_strain_csv_path) -yeild subject_strain_content,subject_strain_csv_path -subject_strain_csv_path.unlink() -subject_subjects_csv_path = pathlib.Path('./tests/user_data/subject/\ - subjects.csv') -""" diff --git a/tests/test_ingest.py b/tests/test_ingest.py index b532263..0c76ecc 100644 --- a/tests/test_ingest.py +++ b/tests/test_ingest.py @@ -1,6 +1,6 @@ '''Tests ingestion into schema tables: Lab, Subject, Session 1. Assert length of populating data from __init__ - 2. Assert exact matches of inserted data fore key tables + 2. Assert exact matches of inserted data for key tables ''' __all__ = ['dj_config', 'pipeline', 'lab_csv', 'lab_project_csv', 'lab_user_csv', @@ -51,8 +51,6 @@ def test_ingest_lab(pipeline, ingest_lab, assert (lab.Protocol & {'protocol': protocol_values[0]} ).fetch1('protocol_type') == protocol_values[1] - # Does not have example data: - # assert len(lab.Source()) == 0 def test_ingest_subjects(pipeline, subjects_csv, subjects_part_csv, ingest_subjects): @@ -77,8 +75,6 @@ def test_ingest_subjects(pipeline, subjects_csv, subjects_part_csv, ingest_subje assert (subject.Subject.User & {'subject': subject_values[0]} ).fetch1('user') == subject_values[2] - # Does not have example data: - # assert len(genotyping.Sequence()) == 0 def test_ingest_sessions(pipeline, sessions_csv, ingest_sessions): diff --git a/workflow_session/ingest.py b/workflow_session/ingest.py index c845669..3d0c0df 100644 --- a/workflow_session/ingest.py +++ b/workflow_session/ingest.py @@ -41,10 +41,11 @@ def ingest_lab(lab_csv_path='./user_data/lab/labs.csv', By default, uses data from workflow_session/user_data/lab/ :param lab_csv_path: relative path of lab csv :param project_csv_path: relative path of project csv - :param pubs_csv_path: relative path of publication csv - :param keyw_csv_path: relative path of keyword csv + :param publication_csv_path: relative path of publication csv + :param keyword_csv_path: relative path of keyword csv :param protocol_csv_path: relative path of protocol csv :param users_csv_path: relative path of users csv + :param project_user_csv_path: relative path of project users csv :param skip_duplicates=True: datajoint insert function param :param verbose: print number inserted (i.e., table length change) """ @@ -91,7 +92,7 @@ def ingest_sessions(session_csv_path='./user_data/session/sessions.csv', """ Inserts data from a sessions csv into corresponding session schema tables By default, uses data from workflow_session/user_data/session/ - :param session_csv_path: relative path of subject csv + :param session_csv_path: relative path of session csv :param skip_duplicates=True: datajoint insert function param :param verbose: print number inserted (i.e., table length change) """ From 8d1654ea26499156945e1f9d0a3c6f63ad15e084 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 7 Mar 2022 07:17:47 -0600 Subject: [PATCH 44/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- notebooks/1_Explore_Workflow.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index f33fbe4..803afa0 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -1498,7 +1498,7 @@ "id": "c47691a0-b016-4092-a5ad-fefff93c54dd", "metadata": {}, "source": [ - "For more documentation of insert, please refer to [DataJoint Docs](https://docs.datajoint.io/python/manipulation/1-Insert.html) and [DataJoint playground](https://playground.datajoint.io/)" + "For more documentation of insert, please refer to [DataJoint Docs](https://docs.datajoint.org/python/manipulation/1-Insert.html) and [DataJoint CodeBook](https://codebook.datajoint.io/)" ] }, { @@ -1506,7 +1506,7 @@ "id": "13f8a8ed-2656-46d8-82ba-cdf130c4873e", "metadata": {}, "source": [ - "## Insert into Manual and Lookup tables with Graphical User Interface" + "## Insert into Manual and Lookup tables with a Graphical User Interface" ] }, { @@ -1514,7 +1514,7 @@ "id": "4775dd80-8a54-47b7-a9ba-99995db9ff1a", "metadata": {}, "source": [ - "DataJoint also provides a Graphical User Interface [DataJoint Labbook](https://github.com/datajoint/datajoint-labbook) to support manual data insertions into DataJoint workflows. ![DataJoint Labbook preview](https://github.com/datajoint/datajoint-labbook/blob/master/docs/sphinx/_static/images/walkthroughDemoOptimized.gif)" + "DataJoint also provides a graphical user interface ([DataJoint LabBook](https://github.com/datajoint/datajoint-labbook)) to support manual data insertions into DataJoint workflows. ![DataJoint LabBook preview](https://github.com/datajoint/datajoint-labbook/blob/master/docs/sphinx/_static/images/walkthroughDemoOptimized.gif)" ] } ], From f79f3e4a8e3f4e85f8d94c8fd6d2a818d03b229f Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 7 Mar 2022 09:31:30 -0600 Subject: [PATCH 45/46] Apply suggestions from code review Co-authored-by: Kabilar Gunalan --- tests/test_pipeline_generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pipeline_generation.py b/tests/test_pipeline_generation.py index 79c10b5..323a3ea 100644 --- a/tests/test_pipeline_generation.py +++ b/tests/test_pipeline_generation.py @@ -24,7 +24,7 @@ def test_generate_pipeline(pipeline): # test connection Subject -> schema children session_tbl, _, subject_line_tbl, subject_protocol_tbl, subject_source_tbl, \ - subject_strain_tbl, subject_user_tbl, subject_cull_table, subject_death_tbl,\ + subject_strain_tbl, subject_user_tbl, subject_cull_tbl, subject_death_tbl,\ subject_zygotsity_tbl = subject.Subject.children(as_objects=True) assert session_tbl.full_table_name == session.Session.full_table_name assert subject_line_tbl.full_table_name == subject.Subject.Line.full_table_name @@ -33,7 +33,7 @@ def test_generate_pipeline(pipeline): assert subject_source_tbl.full_table_name == subject.Subject.Source.full_table_name assert subject_strain_tbl.full_table_name == subject.Subject.Strain.full_table_name assert subject_user_tbl.full_table_name == subject.Subject.User.full_table_name - assert subject_cull_table.full_table_name == \ + assert subject_cull_tbl.full_table_name == \ subject.SubjectCullMethod.full_table_name assert subject_death_tbl.full_table_name == subject.SubjectDeath.full_table_name assert subject_zygotsity_tbl.full_table_name == subject.Zygosity.full_table_name From 967502d29f5479e07798288c65cb22c7809d8e83 Mon Sep 17 00:00:00 2001 From: Chris Brozdowski Date: Mon, 7 Mar 2022 11:14:58 -0600 Subject: [PATCH 46/46] Update notebooks/1_Explore_Workflow.ipynb Co-authored-by: Kabilar Gunalan --- notebooks/1_Explore_Workflow.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/notebooks/1_Explore_Workflow.ipynb b/notebooks/1_Explore_Workflow.ipynb index 803afa0..cc3fba4 100644 --- a/notebooks/1_Explore_Workflow.ipynb +++ b/notebooks/1_Explore_Workflow.ipynb @@ -1076,7 +1076,6 @@ "id": "c510fe4d-09ed-472f-830f-4401bd6830d0", "metadata": {}, "source": [ - "(Workflow needs continued development to import geotyping tables)" ] }, {