LCNE-patchseq-analysis 0.1.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.flake8 +2 -1
  2. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.gitignore +2 -1
  3. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/PKG-INFO +12 -3
  4. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/README.md +1 -1
  5. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/source/conf.py +1 -0
  6. lcne_patchseq_analysis-0.3.0/notebook/demo.ipynb +194 -0
  7. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/pyproject.toml +13 -1
  8. lcne_patchseq_analysis-0.3.0/src/LCNE_patchseq_analysis/__init__.py +3 -0
  9. lcne_patchseq_analysis-0.3.0/src/LCNE_patchseq_analysis/data_util/__init__.py +1 -0
  10. lcne_patchseq_analysis-0.3.0/src/LCNE_patchseq_analysis/data_util/ephys.py +123 -0
  11. lcne_patchseq_analysis-0.3.0/src/LCNE_patchseq_analysis/data_util/lims.py +78 -0
  12. lcne_patchseq_analysis-0.3.0/src/LCNE_patchseq_analysis/data_util/metadata.py +183 -0
  13. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/src/LCNE_patchseq_analysis.egg-info/PKG-INFO +12 -3
  14. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/src/LCNE_patchseq_analysis.egg-info/SOURCES.txt +1 -0
  15. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/src/LCNE_patchseq_analysis.egg-info/requires.txt +11 -1
  16. lcne_patchseq_analysis-0.1.0/notebook/demo.ipynb +0 -705
  17. lcne_patchseq_analysis-0.1.0/src/LCNE_patchseq_analysis/__init__.py +0 -2
  18. lcne_patchseq_analysis-0.1.0/src/LCNE_patchseq_analysis/data_util/__init__.py +0 -2
  19. lcne_patchseq_analysis-0.1.0/src/LCNE_patchseq_analysis/data_util/ephys.py +0 -1
  20. lcne_patchseq_analysis-0.1.0/src/LCNE_patchseq_analysis/data_util/metadata.py +0 -62
  21. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  22. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  23. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/ISSUE_TEMPLATE/user-story.md +0 -0
  24. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/workflows/init.yml +0 -0
  25. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/workflows/tag_and_publish.yml +0 -0
  26. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/.github/workflows/test_and_lint.yml +0 -0
  27. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/LICENSE +0 -0
  28. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/Makefile +0 -0
  29. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/make.bat +0 -0
  30. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/source/_static/dark-logo.svg +0 -0
  31. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/source/_static/favicon.ico +0 -0
  32. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/source/_static/light-logo.svg +0 -0
  33. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/docs/source/index.rst +0 -0
  34. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/environment/Dockerfile +0 -0
  35. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/environment/postInstall +0 -0
  36. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/setup.cfg +0 -0
  37. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/setup.py +0 -0
  38. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/src/LCNE_patchseq_analysis.egg-info/dependency_links.txt +0 -0
  39. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/src/LCNE_patchseq_analysis.egg-info/top_level.txt +0 -0
  40. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/tests/__init__.py +0 -0
  41. {lcne_patchseq_analysis-0.1.0 → lcne_patchseq_analysis-0.3.0}/tests/test_example.py +0 -0
@@ -4,6 +4,7 @@ exclude =
4
4
  __pycache__,
5
5
  build,
6
6
  .venv,
7
- venv
7
+ venv,
8
+ code/
8
9
  max-complexity = 10
9
10
  max-line-length = 100
@@ -140,4 +140,5 @@ dmypy.json
140
140
 
141
141
  .vscode
142
142
  metadata.yml
143
- data
143
+ data
144
+ LIMS_credentials.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: LCNE-patchseq-analysis
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: Generated from aind-library-template
5
5
  Author: Allen Institute for Neural Dynamics
6
6
  Author-email: Han Hou <han.hou@alleninstitute.org>
@@ -11,7 +11,6 @@ Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
12
  Requires-Dist: numpy
13
13
  Requires-Dist: pandas
14
- Requires-Dist: openpyxl
15
14
  Requires-Dist: matplotlib
16
15
  Provides-Extra: dev
17
16
  Requires-Dist: black; extra == "dev"
@@ -21,13 +20,23 @@ Requires-Dist: interrogate; extra == "dev"
21
20
  Requires-Dist: isort; extra == "dev"
22
21
  Requires-Dist: Sphinx; extra == "dev"
23
22
  Requires-Dist: furo; extra == "dev"
23
+ Provides-Extra: pipeline
24
+ Requires-Dist: black; extra == "pipeline"
25
+ Requires-Dist: coverage; extra == "pipeline"
26
+ Requires-Dist: flake8; extra == "pipeline"
27
+ Requires-Dist: interrogate; extra == "pipeline"
28
+ Requires-Dist: isort; extra == "pipeline"
29
+ Requires-Dist: Sphinx; extra == "pipeline"
30
+ Requires-Dist: furo; extra == "pipeline"
31
+ Requires-Dist: openpyxl; extra == "pipeline"
32
+ Requires-Dist: pg8000; extra == "pipeline"
24
33
 
25
34
  # LCNE-patchseq-analysis
26
35
 
27
36
  [![License](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
28
37
  ![Code Style](https://img.shields.io/badge/code%20style-black-black)
29
38
  [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
30
- ![Interrogate](https://img.shields.io/badge/interrogate-100.0%25-brightgreen)
39
+ ![Interrogate](https://img.shields.io/badge/interrogate-80.0%25-yellow)
31
40
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?logo=codecov)
32
41
  ![Python](https://img.shields.io/badge/python->=3.9-blue?logo=python)
33
42
 
@@ -3,7 +3,7 @@
3
3
  [![License](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
4
4
  ![Code Style](https://img.shields.io/badge/code%20style-black-black)
5
5
  [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
6
- ![Interrogate](https://img.shields.io/badge/interrogate-100.0%25-brightgreen)
6
+ ![Interrogate](https://img.shields.io/badge/interrogate-80.0%25-yellow)
7
7
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen?logo=codecov)
8
8
  ![Python](https://img.shields.io/badge/python->=3.9-blue?logo=python)
9
9
 
@@ -1,4 +1,5 @@
1
1
  """Configuration file for the Sphinx documentation builder."""
2
+
2
3
  #
3
4
  # For the full list of built-in configuration values, see the documentation:
4
5
  # https://www.sphinx-doc.org/en/master/usage/configuration.html
@@ -0,0 +1,194 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 11,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stdout",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "The autoreload extension is already loaded. To reload it, use:\n",
13
+ " %reload_ext autoreload\n"
14
+ ]
15
+ }
16
+ ],
17
+ "source": [
18
+ "%load_ext autoreload\n",
19
+ "%autoreload 2\n",
20
+ "import logging\n",
21
+ "logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "execution_count": 12,
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "from LCNE_patchseq_analysis.data_util.metadata import read_brian_spreadsheet, cross_check_metadata"
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "markdown",
35
+ "metadata": {},
36
+ "source": [
37
+ "## Load patchseq metadata and perform cross check"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": 18,
43
+ "metadata": {},
44
+ "outputs": [
45
+ {
46
+ "name": "stderr",
47
+ "output_type": "stream",
48
+ "text": [
49
+ "INFO: Reading metadata from C:\\Users\\han.hou\\Downloads\\IVSCC_LC_summary.xlsx...\n",
50
+ "INFO: Querying and adding LIMS data...\n",
51
+ "INFO: Merged LIMS to spreadsheet, total 390 rows: 146 in both, 125 in spreadsheet only, 119 in LIMS only.\n",
52
+ "\n",
53
+ "INFO: \n",
54
+ "INFO: --------------------------------------------------\n",
55
+ "INFO: Cross-checking metadata between tab_xyz and master tables...\n",
56
+ "INFO: Source columns: ['x_tab_xyz', 'y_tab_xyz', 'z_tab_xyz', 'Annotated structure_tab_xyz', 'notes_tab_xyz']\n",
57
+ "INFO: Master columns: ['x_tab_master', 'y_tab_master', 'z_tab_master', 'Annotated structure_tab_master', 'notes_tab_master']\n",
58
+ "WARNING: Found 7 inconsistencies between x_tab_xyz and x_tab_master:\n",
59
+ "WARNING: Date jem-id_cell_specimen x_tab_master x_tab_xyz\n",
60
+ "2023-09-01 Dbh-Cre_KH212;RCL-H2B-GFP-692026.10.10.02 10534.982420 10151.01953\n",
61
+ "2023-08-20 Dbh-Cre_KH212;RCL-H2B-GFP-692023.08.06.01 10541.875980 10702.28320\n",
62
+ "2023-08-20 Dbh-Cre_KH212;RCL-H2B-GFP-692023.08.06.02 10702.283200 10761.00195\n",
63
+ "2023-06-02 Dbh-Cre_KH212;RCL-H2B-GFP-676766.10.06.03 10521.757810 10541.87598\n",
64
+ "2023-03-15 C57BL6J-665266.11.06.03 10451.809570 10534.98242\n",
65
+ "2023-01-20 Ndnf-IRES2-dgCre;Ai14-659663.11.06.03 10391.497070 10521.75781\n",
66
+ "2023-01-20 Ndnf-IRES2-dgCre;Ai14-659663.11.06.04 9531.198242 10451.80957\n",
67
+ "WARNING: \n",
68
+ "WARNING: Found 7 inconsistencies between y_tab_xyz and y_tab_master:\n",
69
+ "WARNING: Date jem-id_cell_specimen y_tab_master y_tab_xyz\n",
70
+ "2023-09-01 Dbh-Cre_KH212;RCL-H2B-GFP-692026.10.10.02 4183.531250 3701.974609\n",
71
+ "2023-08-20 Dbh-Cre_KH212;RCL-H2B-GFP-692023.08.06.01 4110.681641 3840.954834\n",
72
+ "2023-08-20 Dbh-Cre_KH212;RCL-H2B-GFP-692023.08.06.02 3840.954834 4288.832031\n",
73
+ "2023-06-02 Dbh-Cre_KH212;RCL-H2B-GFP-676766.10.06.03 4256.657715 4110.681641\n",
74
+ "2023-03-15 C57BL6J-665266.11.06.03 4402.110352 4183.531250\n",
75
+ "2023-01-20 Ndnf-IRES2-dgCre;Ai14-659663.11.06.03 4161.165039 4256.657715\n",
76
+ "2023-01-20 Ndnf-IRES2-dgCre;Ai14-659663.11.06.04 2449.594727 4402.110352\n",
77
+ "WARNING: \n",
78
+ "WARNING: Found 5 inconsistencies between z_tab_xyz and z_tab_master:\n",
79
+ "WARNING: Date jem-id_cell_specimen z_tab_master z_tab_xyz\n",
80
+ "2023-09-01 Dbh-Cre_KH212;RCL-H2B-GFP-692026.10.10.02 4984.0 4824.0\n",
81
+ "2023-08-20 Dbh-Cre_KH212;RCL-H2B-GFP-692023.08.06.01 5034.0 4727.0\n",
82
+ "2023-06-02 Dbh-Cre_KH212;RCL-H2B-GFP-676766.10.06.03 4889.0 5034.0\n",
83
+ "2023-03-15 C57BL6J-665266.11.06.03 4889.0 4984.0\n",
84
+ "2023-01-20 Ndnf-IRES2-dgCre;Ai14-659663.11.06.04 4265.0 4889.0\n",
85
+ "WARNING: \n",
86
+ "INFO: All good between Annotated structure_tab_xyz and Annotated structure_tab_master!\n",
87
+ "INFO: All good between notes_tab_xyz and notes_tab_master!\n",
88
+ "INFO: \n",
89
+ "INFO: --------------------------------------------------\n",
90
+ "INFO: Cross-checking metadata between tab_ephys_fx and master tables...\n",
91
+ "INFO: Source columns: ['failed_electrode_0_tab_ephys_fx', 'failed_no_seal_tab_ephys_fx', 'failed_bad_rs_tab_ephys_fx']\n",
92
+ "INFO: Master columns: ['failed_electrode_0_tab_master', 'failed_no_seal_tab_master', 'failed_bad_rs_tab_master']\n",
93
+ "INFO: All good between failed_electrode_0_tab_ephys_fx and failed_electrode_0_tab_master!\n",
94
+ "WARNING: Found 6 inconsistencies between failed_no_seal_tab_ephys_fx and failed_no_seal_tab_master:\n",
95
+ "WARNING: Date jem-id_cell_specimen failed_no_seal_tab_master failed_no_seal_tab_ephys_fx\n",
96
+ "2024-04-03 Dbh-Cre_KH212;RCL-H2B-GFP-724916.11.06.02 1.0 0.0\n",
97
+ "2023-04-19 Slc17a6-IRES-Cre;Ai14-670829.11.06.02 1.0 0.0\n",
98
+ "2022-11-17 Slc17a6-IRES-Cre;Ai14-651168.10.06.03 1.0 0.0\n",
99
+ "2022-11-15 Dbh-Cre_KH212;RCL-Sun1sfGFP-neo-650884.09.06.05 1.0 0.0\n",
100
+ "2022-11-02 Rbp4-Cre_KL100;Ai14-650443.10.06.02 1.0 0.0\n",
101
+ "2022-10-27 C57BL6J-647687.09.06.01 1.0 0.0\n",
102
+ "WARNING: \n",
103
+ "WARNING: Found 2 inconsistencies between failed_bad_rs_tab_ephys_fx and failed_bad_rs_tab_master:\n",
104
+ "WARNING: Date jem-id_cell_specimen failed_bad_rs_tab_master failed_bad_rs_tab_ephys_fx\n",
105
+ "2024-03-20 C57BL6J-722426.10.06.01 1.0 0.0\n",
106
+ "2024-03-20 C57BL6J-722426.10.06.03 1.0 0.0\n",
107
+ "WARNING: \n",
108
+ "INFO: \n",
109
+ "INFO: --------------------------------------------------\n",
110
+ "INFO: Cross-checking metadata between lims and master tables...\n",
111
+ "INFO: Source columns: ['cell_specimen_id_lims', 'ephys_roi_id_lims', 'ephys_qc_lims', 'storage_directory_lims']\n",
112
+ "INFO: Master columns: ['cell_specimen_id_tab_master', 'ephys_roi_id_tab_master', 'ephys_qc_tab_master', 'storage_directory_tab_master']\n",
113
+ "INFO: All good between cell_specimen_id_lims and cell_specimen_id_tab_master!\n",
114
+ "INFO: All good between ephys_roi_id_lims and ephys_roi_id_tab_master!\n",
115
+ "INFO: All good between ephys_qc_lims and ephys_qc_tab_master!\n",
116
+ "INFO: All good between storage_directory_lims and storage_directory_tab_master!\n"
117
+ ]
118
+ }
119
+ ],
120
+ "source": [
121
+ "dfs = read_brian_spreadsheet()\n",
122
+ "for source in [\"tab_xyz\", \"tab_ephys_fx\", \"lims\"]:\n",
123
+ " df_inconsistencies = cross_check_metadata(dfs[\"df_merged\"], source)"
124
+ ]
125
+ },
126
+ {
127
+ "cell_type": "markdown",
128
+ "metadata": {},
129
+ "source": [
130
+ "### ❌ Oh no! These inconsistencies must be caused by manually copying and pasting across the tabs!!!"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "markdown",
135
+ "metadata": {},
136
+ "source": [
137
+ "## Quick overview using pygwalker"
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "code",
142
+ "execution_count": 14,
143
+ "metadata": {},
144
+ "outputs": [],
145
+ "source": [
146
+ "!pip install pygwalker --quiet"
147
+ ]
148
+ },
149
+ {
150
+ "cell_type": "code",
151
+ "execution_count": 15,
152
+ "metadata": {},
153
+ "outputs": [
154
+ {
155
+ "ename": "NameError",
156
+ "evalue": "name 'df' is not defined",
157
+ "output_type": "error",
158
+ "traceback": [
159
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
160
+ "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
161
+ "Cell \u001b[1;32mIn[15], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mpygwalker\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mas\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mpyg\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m walker \u001b[38;5;241m=\u001b[39m pyg\u001b[38;5;241m.\u001b[39mwalk(\u001b[43mdf\u001b[49m)\n\u001b[0;32m 3\u001b[0m walker\n",
162
+ "\u001b[1;31mNameError\u001b[0m: name 'df' is not defined"
163
+ ]
164
+ }
165
+ ],
166
+ "source": [
167
+ "import pygwalker as pyg\n",
168
+ "walker = pyg.walk(df)\n",
169
+ "walker"
170
+ ]
171
+ }
172
+ ],
173
+ "metadata": {
174
+ "kernelspec": {
175
+ "display_name": "patchseq_pipeline",
176
+ "language": "python",
177
+ "name": "python3"
178
+ },
179
+ "language_info": {
180
+ "codemirror_mode": {
181
+ "name": "ipython",
182
+ "version": 3
183
+ },
184
+ "file_extension": ".py",
185
+ "mimetype": "text/x-python",
186
+ "name": "python",
187
+ "nbconvert_exporter": "python",
188
+ "pygments_lexer": "ipython3",
189
+ "version": "3.9.21"
190
+ }
191
+ },
192
+ "nbformat": 4,
193
+ "nbformat_minor": 2
194
+ }
@@ -20,7 +20,6 @@ dynamic = ["version"]
20
20
  dependencies = [
21
21
  'numpy',
22
22
  'pandas',
23
- 'openpyxl',
24
23
  'matplotlib',
25
24
  ]
26
25
 
@@ -35,6 +34,18 @@ dev = [
35
34
  'furo'
36
35
  ]
37
36
 
37
+ pipeline = [
38
+ 'black',
39
+ 'coverage',
40
+ 'flake8',
41
+ 'interrogate',
42
+ 'isort',
43
+ 'Sphinx',
44
+ 'furo',
45
+ 'openpyxl',
46
+ 'pg8000',
47
+ ]
48
+
38
49
  [tool.setuptools.packages.find]
39
50
  where = ["src"]
40
51
 
@@ -57,6 +68,7 @@ exclude = '''
57
68
  | _build
58
69
  | build
59
70
  | dist
71
+ | code
60
72
  )/
61
73
  | .gitignore
62
74
  )
@@ -0,0 +1,3 @@
1
+ """Init package"""
2
+
3
+ __version__ = "0.3.0"
@@ -0,0 +1 @@
1
+ """Utils for accessing data"""
@@ -0,0 +1,123 @@
1
+ """Ephys-related data utils"""
2
+
3
+ import concurrent.futures
4
+ import logging
5
+ import os
6
+ import subprocess
7
+
8
+ import pandas as pd
9
+ from tqdm import tqdm
10
+
11
+ from LCNE_patchseq_analysis.data_util.metadata import read_brian_spreadsheet
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ s3_bucket = "s3://aind-scratch-data/aind-patchseq-data/raw"
16
+
17
+
18
+ def sync_directory(local_dir, destination, if_copy=False):
19
+ """
20
+ Sync the local directory with the given S3 destination using aws s3 sync.
21
+ Returns a status string based on the command output.
22
+ """
23
+ try:
24
+ if if_copy:
25
+ # Run aws s3 cp command and capture the output
26
+ result = subprocess.run(
27
+ ["aws", "s3", "cp", local_dir, destination], capture_output=True, text=True
28
+ )
29
+ else:
30
+ # Run aws s3 sync command and capture the output
31
+ result = subprocess.run(
32
+ ["aws", "s3", "sync", local_dir, destination], capture_output=True, text=True
33
+ )
34
+ output = result.stdout + result.stderr
35
+
36
+ # Check output: if "upload:" appears, files were sent;
37
+ # otherwise, assume that nothing needed uploading.
38
+ if "upload:" in output:
39
+ logger.info(f"Uploaded {local_dir} to {destination}!")
40
+ return "successfully uploaded"
41
+ else:
42
+ logger.info(output)
43
+ logger.info(f"Already exists, skip {local_dir}.")
44
+ return "already exists, skip"
45
+ except Exception as e:
46
+ return f"error during sync: {e}"
47
+
48
+
49
+ def upload_one(row, s3_bucket):
50
+ """Process a single row: normalize the path, check existence,
51
+ and perform (or simulate) the sync.
52
+ """
53
+ # Check if the storage_directory_combined value is null.
54
+ if pd.isnull(row["storage_directory_combined"]):
55
+ logger.info("The path is null")
56
+ status = "the path is null"
57
+ path = None
58
+ else:
59
+ # Normalize the path and prepend a backslash.
60
+ path = "\\" + os.path.normpath(row["storage_directory_combined"])
61
+ roi_name = os.path.basename(path)
62
+
63
+ # Check if the local path exists.
64
+ if not os.path.exists(path):
65
+ logger.info(f"Cannot find the path: {path}")
66
+ status = "cannot find the path"
67
+ else:
68
+ logger.info(f"Syncing {path} to {s3_bucket}/{roi_name}...")
69
+ status = sync_directory(path, s3_bucket + "/" + roi_name)
70
+ return {"storage_directory": path, "status": status}
71
+
72
+
73
+ def upload_raw_from_isilon_to_s3_batch(df, s3_bucket=s3_bucket, max_workers=10):
74
+ """Upload raw data from Isilon to S3, using the metadata dataframe in parallel."""
75
+ results = []
76
+
77
+ # Create a thread pool to process rows in parallel.
78
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
79
+ # Submit each row for processing.
80
+ futures = [executor.submit(upload_one, row, s3_bucket) for idx, row in df.iterrows()]
81
+
82
+ # Collect the results as they complete.
83
+ for future in tqdm(
84
+ concurrent.futures.as_completed(futures), total=len(futures), desc="Uploading..."
85
+ ):
86
+ results.append(future.result())
87
+
88
+ logger.info(f"Uploaded {len(results)} files to {s3_bucket} in parallel...")
89
+ logger.info(
90
+ f'Successful uploads: {len([r for r in results if r["status"] == "successfully uploaded"])}'
91
+ )
92
+ logger.info(f'Skiped: {len([r for r in results if r["status"] == "already exists, skip"])}')
93
+ logger.info(
94
+ f'Error during sync: {len([r for r in results if r["status"] == "error during sync"])}'
95
+ )
96
+ logger.info(
97
+ "Cannot find on Isilon: "
98
+ f'{len([r for r in results if r["status"] == "cannot find the path"])}'
99
+ )
100
+ logger.info(f'Null path: {len([r for r in results if r["status"] == "the path is null"])}')
101
+
102
+ return pd.DataFrame(results)
103
+
104
+
105
+ def trigger_patchseq_upload(metadata_path=os.path.expanduser(R"~\Downloads\IVSCC_LC_summary.xlsx")):
106
+ # Generate a list of isilon paths
107
+ dfs = read_brian_spreadsheet(file_path=metadata_path, add_lims=True)
108
+ df_merged = dfs["df_merged"]
109
+
110
+ # Upload raw data
111
+ upload_raw_from_isilon_to_s3_batch(df_merged, s3_bucket=s3_bucket, max_workers=10)
112
+
113
+ # Also save df_merged as csv and upload to s3
114
+ df_merged.to_csv("df_metadata_merged.csv", index=False)
115
+ sync_directory("df_metadata_merged.csv", s3_bucket + "/df_metadata_merged.csv", if_copy=True)
116
+
117
+
118
+ if __name__ == "__main__":
119
+
120
+ # Set logger level
121
+ logging.basicConfig(level=logging.INFO)
122
+
123
+ trigger_patchseq_upload(os.path.expanduser(R"~\Downloads\IVSCC_LC_summary.xlsx"))
@@ -0,0 +1,78 @@
1
+ """Utilities for querying the LIMS database.
2
+
3
+ From Brian
4
+ """
5
+
6
+ import json
7
+
8
+ import pandas as pd # pandas will be needed to work in a dataframe
9
+ import pg8000 # pg8000 access SQL databases
10
+
11
+ # code from Agata
12
+ # these are nice functions to open LIMS, make a query and then close LIMS after
13
+
14
+
15
+ def _connect(user, host, database, password, port):
16
+ conn = pg8000.connect(user=user, host=host, database=database, password=password, port=port)
17
+ return conn, conn.cursor()
18
+
19
+
20
+ def _select(cursor, query):
21
+ cursor.execute(query)
22
+ columns = [d[0] for d in cursor.description]
23
+ return [dict(zip(columns, c)) for c in cursor.fetchall()]
24
+
25
+
26
+ def limsquery(query, user, host, database, password, port):
27
+ """A function that takes a string containing a SQL query, connects to the LIMS database
28
+ and outputs the result."""
29
+ conn, cursor = _connect(user, host, database, password, port)
30
+ try:
31
+ results = _select(cursor, query)
32
+ finally:
33
+ cursor.close()
34
+ conn.close()
35
+ return results
36
+
37
+
38
+ # this last function will take our query results and put them in a dataframe
39
+ # so that they are easy to work with
40
+ def get_lims_dataframe(query):
41
+ """Return a dataframe with lims query"""
42
+
43
+ # Get credentials from json
44
+ with open("LIMS_credentials.json") as f:
45
+ credentials = json.load(f)
46
+
47
+ result = limsquery(query, **credentials)
48
+ try:
49
+ data_df = pd.DataFrame(data=result, columns=result[0].keys())
50
+ except IndexError:
51
+ print("Could not find results for your query.")
52
+ data_df = pd.DataFrame()
53
+ return data_df
54
+
55
+
56
+ # Query for LCNE patchseq experiments
57
+ def get_lims_LCNE_patchseq():
58
+ lims_query = """
59
+ SELECT
60
+ s.id AS specimen_id,
61
+ s.name AS specimen_name,
62
+ proj.code,
63
+ err.id AS ephys_roi_id,
64
+ err.workflow_state AS Ephys_QC,
65
+ s.patched_cell_container,
66
+ err.storage_directory
67
+ FROM ephys_roi_results AS err
68
+ JOIN specimens AS s ON s.ephys_roi_result_id = err.id
69
+ JOIN projects AS proj ON s.project_id = proj.id
70
+ WHERE proj.code = 'mIVSCC-MET-R01_LC';
71
+ """
72
+ lims_df = get_lims_dataframe(lims_query)
73
+ return lims_df
74
+
75
+
76
+ if __name__ == "__main__":
77
+ lims_df = get_lims_LCNE_patchseq()
78
+ print(lims_df.head())