labdata 0.1.7__tar.gz → 0.1.9__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 (62) hide show
  1. {labdata-0.1.7 → labdata-0.1.9}/PKG-INFO +1 -1
  2. labdata-0.1.9/labdata/__init__.py +15 -0
  3. {labdata-0.1.7 → labdata-0.1.9}/labdata/cli.py +405 -26
  4. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/caiman.py +34 -12
  5. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/ec2.py +166 -18
  6. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/ephys.py +178 -37
  7. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/pose.py +55 -6
  8. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/schedulers.py +151 -36
  9. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/singularity.py +63 -10
  10. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/suite2p.py +21 -4
  11. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/utils.py +269 -41
  12. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/admin.py +12 -0
  13. labdata-0.1.9/labdata/dashboard/ai_utils.py +326 -0
  14. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/compute.py +10 -0
  15. labdata-0.1.9/labdata/dashboard/console.py +356 -0
  16. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/decisiontask.py +160 -12
  17. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/explorer/cellsegmentation.py +99 -13
  18. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/explorer/spikesorting.py +90 -12
  19. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/index.py +140 -4
  20. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/notes.py +12 -0
  21. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/procedures.py +34 -0
  22. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/schema_graph.py +15 -0
  23. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/segmentation.py +28 -3
  24. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/sorting.py +223 -128
  25. labdata-0.1.9/labdata/dashboard/video.py +618 -0
  26. {labdata-0.1.7 → labdata-0.1.9}/labdata/export.py +95 -4
  27. {labdata-0.1.7 → labdata-0.1.9}/labdata/rules/__init__.py +37 -10
  28. {labdata-0.1.7 → labdata-0.1.9}/labdata/rules/ephys.py +126 -23
  29. {labdata-0.1.7 → labdata-0.1.9}/labdata/rules/imaging.py +215 -40
  30. {labdata-0.1.7 → labdata-0.1.9}/labdata/rules/utils.py +66 -12
  31. {labdata-0.1.7 → labdata-0.1.9}/labdata/s3.py +19 -1
  32. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/ephys.py +227 -76
  33. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/general.py +227 -30
  34. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/histology.py +14 -1
  35. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/onephoton.py +30 -4
  36. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/procedures.py +6 -0
  37. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/tasks.py +6 -0
  38. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/twophoton.py +136 -22
  39. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/video.py +74 -13
  40. {labdata-0.1.7 → labdata-0.1.9}/labdata/stacks.py +44 -2
  41. {labdata-0.1.7 → labdata-0.1.9}/labdata/utils.py +81 -6
  42. {labdata-0.1.7 → labdata-0.1.9}/labdata/widgets.py +148 -30
  43. {labdata-0.1.7 → labdata-0.1.9}/labdata.egg-info/PKG-INFO +1 -1
  44. {labdata-0.1.7 → labdata-0.1.9}/labdata.egg-info/SOURCES.txt +1 -0
  45. labdata-0.1.9/labdata.egg-info/entry_points.txt +3 -0
  46. {labdata-0.1.7 → labdata-0.1.9}/pyproject.toml +2 -1
  47. labdata-0.1.7/labdata/__init__.py +0 -11
  48. labdata-0.1.7/labdata/dashboard/console.py +0 -166
  49. labdata-0.1.7/labdata/dashboard/video.py +0 -119
  50. labdata-0.1.7/labdata.egg-info/entry_points.txt +0 -2
  51. {labdata-0.1.7 → labdata-0.1.9}/LICENSE +0 -0
  52. {labdata-0.1.7 → labdata-0.1.9}/README.md +0 -0
  53. {labdata-0.1.7 → labdata-0.1.9}/labdata/compute/__init__.py +0 -0
  54. {labdata-0.1.7 → labdata-0.1.9}/labdata/copy.py +0 -0
  55. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/__init__.py +0 -0
  56. {labdata-0.1.7 → labdata-0.1.9}/labdata/dashboard/explorer/__init__.py +0 -0
  57. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/__init__.py +0 -0
  58. {labdata-0.1.7 → labdata-0.1.9}/labdata/schema/utils.py +0 -0
  59. {labdata-0.1.7 → labdata-0.1.9}/labdata.egg-info/dependency_links.txt +0 -0
  60. {labdata-0.1.7 → labdata-0.1.9}/labdata.egg-info/requires.txt +0 -0
  61. {labdata-0.1.7 → labdata-0.1.9}/labdata.egg-info/top_level.txt +0 -0
  62. {labdata-0.1.7 → labdata-0.1.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Package to manage data in experimental neuroscience labs
5
5
  Author-email: Joao Couto <jpcouto@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/jcouto/labdata
@@ -0,0 +1,15 @@
1
+ VERSION = "0.1.9"
2
+
3
+ from .utils import *
4
+ from .copy import copy_to_upload_server
5
+ from .s3 import copy_to_s3
6
+
7
+ _project = (prefs.get('database', {}) or {}).get('database.project', None)
8
+ if 'LABDATA_DATABASE_PROJECT' in os.environ.keys():
9
+ if len(os.environ['LABDATA_DATABASE_PROJECT']):
10
+ _project = os.environ['LABDATA_DATABASE_PROJECT']
11
+
12
+ plugins = load_plugins(project=_project)
13
+ for _name, _mod in plugins.items():
14
+ globals()[_name] = _mod
15
+
@@ -2,6 +2,40 @@ from .utils import *
2
2
  import argparse
3
3
 
4
4
  class CLI_parser():
5
+ '''Command-line interface dispatcher for labdata.
6
+
7
+ Each public method corresponds to a ``labdata <command>`` sub-command.
8
+ Instantiating the class parses ``sys.argv`` and calls the matching method.
9
+
10
+ Sub-commands
11
+ ------------
12
+ dashboard
13
+ Launch the Streamlit dashboard or a Dash explorer.
14
+ subjects
15
+ List subjects, optionally filtered by user, name, or sex.
16
+ sessions
17
+ List sessions and datasets for one or more subjects.
18
+ get
19
+ Download dataset files from S3 to local storage.
20
+ put
21
+ Copy raw data files to the upload server (triggers an UploadJob).
22
+ clean
23
+ Delete local files that are confirmed uploaded (checksums verified).
24
+ run
25
+ Queue or launch an analysis compute job.
26
+ task
27
+ Execute a single ``ComputeTask`` by job ID (called by workers).
28
+ task_reset
29
+ Reset a failed/stuck compute task so it can be retried.
30
+ upload
31
+ Process pending ``UploadJob`` entries (server-side, admin use).
32
+ logpipe
33
+ Pipe stdout into a ``ComputeTask.task_log`` entry.
34
+ run_container
35
+ Launch a local Apptainer container interactively.
36
+ build_container
37
+ Build Apptainer ``.sif`` images from definition files.
38
+ '''
5
39
  def __init__(self):
6
40
  parser = argparse.ArgumentParser(
7
41
  description = f'{tcolor["y"]("labdata")} - tools to manage data in an experimental neuroscience lab',
@@ -35,7 +69,7 @@ Server commands (don't run on experimental computers):
35
69
  ''')
36
70
 
37
71
  parser.add_argument('command',
38
- help= 'type: labdata2 <command> -h for help')
72
+ help= 'type: labdata <command> -h for help')
39
73
 
40
74
  args = parser.parse_args(sys.argv[1:2])
41
75
  command = args.command.replace('-','_') # can use - in command
@@ -46,9 +80,45 @@ Server commands (don't run on experimental computers):
46
80
  getattr(self,command)() # Runs the following parser
47
81
 
48
82
  def dashboard(self):
83
+ '''Launch the labdata web dashboard or an interactive data explorer.
84
+
85
+ Without flags, opens the main Streamlit dashboard (metadata insertion,
86
+ session browser, admin panel). With ``--spike-sorting`` or
87
+ ``--cell-segmentation`` opens a Dash-based interactive explorer instead.
88
+
89
+ Usage
90
+ -----
91
+ ::
92
+
93
+ labdata dashboard
94
+ labdata dashboard -p <project>
95
+ labdata dashboard --spike-sorting -f <subject_filter>
96
+ labdata dashboard --cell-segmentation -f <subject_filter>
97
+ labdata dashboard --admin
98
+ labdata dashboard --console
99
+
100
+ Options
101
+ -------
102
+ -f, --filter-name
103
+ Filter subjects by name substring.
104
+ -u, --user
105
+ Filter by lab member username.
106
+ -p, --project
107
+ Project name (overrides preference file).
108
+ --spike-sorting
109
+ Open the Dash spike-sorting explorer instead of Streamlit.
110
+ --cell-segmentation
111
+ Open the Dash cell-segmentation explorer instead of Streamlit.
112
+ --admin
113
+ Include the admin tab (storage stats, project switcher).
114
+ --console
115
+ Include the IPython console tab.
116
+ --debug
117
+ Enable Dash debug mode for error tracebacks.
118
+ '''
49
119
  parser = argparse.ArgumentParser(
50
- description = 'Open the interactive explorer or graphical interface.',
51
- usage = '''labdata dashboard -f <FILTER>''')
120
+ description='Open the interactive explorer or graphical interface.',
121
+ usage='labdata dashboard -f <FILTER>')
52
122
 
53
123
  parser.add_argument('-f','--filter-name',
54
124
  default = None,
@@ -91,20 +161,51 @@ Server commands (don't run on experimental computers):
91
161
  open_browser = True)
92
162
  else:
93
163
  import subprocess as sub
164
+ import os
94
165
  main_webpage_path = Path(__file__).parent / 'dashboard' / 'index.py'
95
- command = f'streamlit run {str(main_webpage_path)}'
166
+ command = f'streamlit run --server.showEmailPrompt=False --browser.gatherUsageStats=False {str(main_webpage_path)}'
167
+ if 'LABDATA_VERBOSE' in os.environ.keys():
168
+ print(f'Running streamlit dashboard: {command}')
96
169
  if not args.project is None:
97
170
  command += f' project={args.project}'
98
171
  if args.console:
99
172
  command += ' console=1'
100
173
  if args.admin:
101
174
  command += ' admin=1'
102
- sub.run(command.split(' '))
175
+ sub.run(command,shell=True)
103
176
 
104
177
  def subjects(self):
178
+ '''List subjects registered in the database.
179
+
180
+ Prints subjects grouped by responsible lab member, with optional
181
+ filters and an optional column showing total raw data size.
182
+
183
+ Usage
184
+ -----
185
+ ::
186
+
187
+ labdata subjects
188
+ labdata subjects -u <user>
189
+ labdata subjects -f <name_substring>
190
+ labdata subjects -s M
191
+ labdata subjects --include-size
192
+
193
+ Options
194
+ -------
195
+ -u, --user
196
+ Filter by lab member username.
197
+ -f, --filter-name
198
+ Filter by subject name substring (SQL ``LIKE`` match).
199
+ -s, --filter-sex
200
+ Filter by sex (``M``, ``F``, or ``U``).
201
+ -p, --project
202
+ Project name.
203
+ --include-size
204
+ Show total raw data size (TB) and file count per subject.
205
+ '''
105
206
  parser = argparse.ArgumentParser(
106
- description = 'List sessions and datatypes',
107
- usage = '''labdata subject -u <USER> -f <FILTER>''')
207
+ description='List sessions and datatypes',
208
+ usage='labdata subject -u <USER> -f <FILTER>')
108
209
 
109
210
  project = _get_project()
110
211
  parser.add_argument('-p','--project',
@@ -178,9 +279,32 @@ Server commands (don't run on experimental computers):
178
279
  print('')
179
280
 
180
281
  def sessions(self):
282
+ '''List sessions and datasets for one or more subjects.
283
+
284
+ Prints sessions grouped by subject, showing dataset type, name, and
285
+ (optionally) size. For ephys datasets, also shows how many probes have
286
+ been sorted.
287
+
288
+ Usage
289
+ -----
290
+ ::
291
+
292
+ labdata sessions -a <subject>
293
+ labdata sessions -a <subject1> <subject2>
294
+ labdata sessions -a <subject> --include-size
295
+
296
+ Options
297
+ -------
298
+ -a, --subject
299
+ One or more subject names.
300
+ -p, --project
301
+ Project name.
302
+ --include-size
303
+ Show total raw data size (TB/GB) and file count.
304
+ '''
181
305
  parser = argparse.ArgumentParser(
182
- description = 'List sessions and datatypes',
183
- usage = '''labdata sessions -a <SUBJECT>''')
306
+ description='List sessions and datatypes',
307
+ usage='labdata sessions -a <SUBJECT>')
184
308
  parser = self._add_default_arguments(parser,1, include_project = True)
185
309
  parser.add_argument('--include-size', action = 'store_true',
186
310
  default = False)
@@ -228,9 +352,35 @@ Server commands (don't run on experimental computers):
228
352
  print(f'\t\t {pre} {t["dataset_type"]} - {t["dataset_name"]} {extra}')
229
353
 
230
354
  def get(self):
355
+ '''Download dataset files from S3 to local storage.
356
+
357
+ Resolves the matching ``File`` entries via ``Dataset.DataFiles`` for
358
+ the requested subjects/sessions/datatypes and calls ``File.get()``
359
+ to download any files not already present locally.
360
+
361
+ Usage
362
+ -----
363
+ ::
364
+
365
+ labdata get -a <subject>
366
+ labdata get -a <subject> -s <session>
367
+ labdata get -a <subject> -s <session> -d ephys
368
+ labdata get -a <subject1> <subject2> -s <session>
369
+
370
+ Options
371
+ -------
372
+ -a, --subject
373
+ One or more subject names.
374
+ -s, --session
375
+ One or more session names.
376
+ -d, --datatype
377
+ Filter by dataset type (e.g. ``ephys``, ``twophoton``).
378
+ -p, --project
379
+ Project name.
380
+ '''
231
381
  parser = argparse.ArgumentParser(
232
- description = 'Download data from one or multiple sessions',
233
- usage = '''labdata sessions -a <SUBJECTS> -s <SESSIONS>''')
382
+ description='Download data from one or multiple sessions',
383
+ usage='labdata get -a <SUBJECTS> -s <SESSIONS>')
234
384
  parser = self._add_default_arguments(parser,3, include_project = True)
235
385
 
236
386
  # TODO: Add an argument to include files that match a pattern..
@@ -268,9 +418,45 @@ Server commands (don't run on experimental computers):
268
418
  (schema.File() & (schema.Dataset.DataFiles() & keys).proj()).get()
269
419
 
270
420
  def put(self):
421
+ '''Copy raw data files to the upload server and create an UploadJob.
422
+
423
+ Opens a Qt file-copy widget that checksums source files, copies them to
424
+ the designated upload server path, and registers an ``UploadJob`` in the
425
+ database for the requested upload rule. **This does not upload to the
426
+ cloud** — that happens when ``labdata upload`` is later run on the server.
427
+
428
+ Usage
429
+ -----
430
+ ::
431
+
432
+ labdata put <path/to/data/folder>
433
+ labdata put <path/to/data/folder> -r ephys -a <subject> -s <session>
434
+ labdata put <file1> <file2> -r twophoton
435
+
436
+ Options
437
+ -------
438
+ filepaths
439
+ One or more local file paths or a single folder (all files inside
440
+ the folder are selected recursively).
441
+ -r, --rule
442
+ Upload rule to apply on the server (e.g. ``ephys``, ``two_photon``,
443
+ ``miniscope``, ``fixed_brain``). Default is ``none`` (copy only).
444
+ -a, --subject
445
+ Subject name to associate with the upload job.
446
+ -s, --session
447
+ Session name to associate with the upload job.
448
+ -t, --datatype-name
449
+ Dataset type name.
450
+ -p, --project
451
+ Project name.
452
+ --overwrite
453
+ Overwrite files that already exist on the server.
454
+ --ask
455
+ Prompt for confirmation before copying each file.
456
+ '''
271
457
  parser = argparse.ArgumentParser(
272
- description = 'Copies data to the server to be uploaded [THIS DOES NOT UPLOAD TO THE CLOUD]',
273
- usage = '''labdata put -a <SUBJECT> -s <SESSION>''')
458
+ description='Copies data to the server to be uploaded [THIS DOES NOT UPLOAD TO THE CLOUD]',
459
+ usage='labdata put -a <SUBJECT> -s <SESSION>')
274
460
  from .rules import rulesmap
275
461
  rules_help = ','.join([tcolor['g'](r) for r in rulesmap.keys()])
276
462
  parser = self._add_default_arguments(parser,include_project = True)
@@ -322,9 +508,34 @@ Server commands (don't run on experimental computers):
322
508
  app.exit()
323
509
  #sys.exit(app.exec_())
324
510
  def clean(self):
511
+ '''Delete local copies of files that are confirmed uploaded to S3.
512
+
513
+ Compares MD5 checksums of local files against the ``File`` table.
514
+ Files that match are deleted; files not yet confirmed in the database
515
+ are kept. Use ``--dry-run`` to preview what would be deleted.
516
+
517
+ Usage
518
+ -----
519
+ ::
520
+
521
+ labdata clean
522
+ labdata clean --dry-run
523
+ labdata clean -f ephys
524
+ labdata clean --only-processed
525
+
526
+ Options
527
+ -------
528
+ -f, --filter
529
+ Only consider files whose path contains this string (repeatable).
530
+ --dry-run
531
+ Print what would be deleted without actually deleting.
532
+ --only-processed
533
+ Only delete files listed in ``ProcessedFile`` (original raw files
534
+ that were transformed by an upload rule).
535
+ '''
325
536
  parser = argparse.ArgumentParser(
326
- description = 'Releases local storage space.',
327
- usage = '''labdata clean -f "ephys"''')
537
+ description='Releases local storage space.',
538
+ usage='labdata clean -f "ephys"')
328
539
  parser.add_argument('-f','--filter',action = 'store',default = [], type = str, nargs = '+')
329
540
  parser.add_argument('--dry-run',action = 'store_true', default = False)
330
541
  parser.add_argument('--only-processed',action = 'store_true', default = False)
@@ -335,9 +546,57 @@ Server commands (don't run on experimental computers):
335
546
  print(deleted,kept)
336
547
 
337
548
  def run(self):
549
+ '''Queue or launch an analysis compute job.
550
+
551
+ Creates ``ComputeTask`` entries for the matching datasets and submits
552
+ them to the selected execution target (local Apptainer, SLURM, or EC2).
553
+ If ``-j`` is given, re-submits an existing job without creating a new one.
554
+
555
+ Usage
556
+ -----
557
+ ::
558
+
559
+ labdata run <analysis> -a <subject> -s <session>
560
+ labdata run <analysis> -a <subject> -t slurm
561
+ labdata run <analysis> -a <subject> -t slurm -- <analysis-specific-args>
562
+ labdata run <analysis> --queue
563
+ labdata run <analysis> -j <job_id>
564
+
565
+ Options
566
+ -------
567
+ analysis
568
+ Name of the registered analysis (e.g. ``spks``, ``suite2p``,
569
+ ``caiman``, ``deeplabcut``, ``populate``).
570
+ -a, --subject
571
+ One or more subject names.
572
+ -s, --session
573
+ One or more session names.
574
+ -d, --datatype
575
+ Filter by dataset type.
576
+ -j, --job
577
+ Re-use an existing ``ComputeTask`` job ID instead of creating one.
578
+ -t, --target
579
+ Execution target: ``local``, ``slurm``, or a named remote from
580
+ the preference file. Defaults to ``prefs["compute"]["default_target"]``.
581
+ -p, --project
582
+ Project name.
583
+ --force-submit
584
+ Submit even if an identical task command already exists.
585
+ --multisession
586
+ Combine all matched datasets into a single multi-session job.
587
+ --keep-files
588
+ Preserve intermediate files after the job completes.
589
+ --queue
590
+ Print the current job queue for all targets and exit.
591
+ --delete-computes
592
+ Delete all ``ComputeTask`` entries for this analysis (careful).
593
+ --
594
+ Everything after ``--`` is forwarded as secondary arguments to the
595
+ analysis class (e.g. algorithm flags, parameter set selection).
596
+ '''
338
597
  parser = argparse.ArgumentParser(
339
- description = 'Allocates or runs an analysis',
340
- usage = f'''labdata run <ANALYSIS> -a <SUBJECT> -s <SESSION>
598
+ description='Allocates or runs an analysis',
599
+ usage=f'''labdata run <ANALYSIS> -a <SUBJECT> -s <SESSION>
341
600
 
342
601
  Available analysis are
343
602
  {tcolor['g'](', '.join(prefs["compute"]["analysis"].keys()))}''')
@@ -414,9 +673,37 @@ Available analysis are
414
673
  run_analysis(target,jobids, obj, project = args.project)
415
674
 
416
675
  def task_reset(self):
676
+ '''Reset a failed or stuck compute task so it can be retried.
677
+
678
+ Sets ``task_waiting=1`` and ``task_status="WAITING"`` for the specified
679
+ job(s) so they will be picked up by the next ``labdata task`` call.
680
+ With ``--resubmit``, immediately re-launches the job on the same or a
681
+ specified target.
682
+
683
+ Usage
684
+ -----
685
+ ::
686
+
687
+ labdata task_reset <job_id>
688
+ labdata task_reset <job_id1> <job_id2>
689
+ labdata task_reset <job_id> --resubmit
690
+ labdata task_reset <job_id> --resubmit -t slurm
691
+ labdata task_reset --clear-all
692
+
693
+ Options
694
+ -------
695
+ job_id
696
+ One or more ``ComputeTask.job_id`` values.
697
+ -t, --target
698
+ Execution target for ``--resubmit`` (default: from job record).
699
+ --resubmit
700
+ After resetting, immediately re-launch the job.
701
+ --clear-all
702
+ Delete **all** compute tasks (requires confirmation).
703
+ '''
417
704
  parser = argparse.ArgumentParser(
418
- description = 'Reset a task in the ComputeTask so the job can be ran again',
419
- usage = '''labdata task_reset <JOB_ID> ''')
705
+ description='Reset a task in the ComputeTask so the job can be ran again',
706
+ usage='labdata task_reset <JOB_ID>')
420
707
  parser.add_argument('job_id', action = 'store', type = int, nargs='+')
421
708
  parser.add_argument('-t','--target', action = 'store', default = None, type = str)
422
709
  parser.add_argument('--resubmit', action = 'store_true', default = False)
@@ -450,9 +737,32 @@ Available analysis are
450
737
  run_analysis(target,[job_id],obj)
451
738
 
452
739
  def task(self):
740
+ '''Execute a single ``ComputeTask`` by job ID.
741
+
742
+ This is the command run by worker nodes (inside an Apptainer container
743
+ or on a SLURM job) to actually perform the analysis. It instantiates
744
+ the correct ``BaseCompute`` subclass for the job and calls ``compute()``.
745
+
746
+ You normally do not call this directly — use ``labdata run`` to queue
747
+ and submit jobs instead.
748
+
749
+ Usage
750
+ -----
751
+ ::
752
+
753
+ labdata task <job_id>
754
+ labdata task <job_id> -p <project>
755
+
756
+ Options
757
+ -------
758
+ job_id
759
+ ``ComputeTask.job_id`` to execute.
760
+ -p, --project
761
+ Project name (used when the worker has no preference file project set).
762
+ '''
453
763
  parser = argparse.ArgumentParser(
454
- description = 'Runs a ComputeTask',
455
- usage = '''labdata task <JOB_ID> ''')
764
+ description='Runs a ComputeTask',
765
+ usage='labdata task <JOB_ID>')
456
766
  parser.add_argument('job_id',action = 'store',default = None,type = int)
457
767
  project = _get_project()
458
768
  parser.add_argument('-p','--project',
@@ -468,9 +778,42 @@ Available analysis are
468
778
  task.compute()
469
779
 
470
780
  def upload(self):
781
+ '''Process pending ``UploadJob`` entries (server-side / admin command).
782
+
783
+ Fetches waiting upload jobs assigned to this host, applies the
784
+ appropriate upload rule (compression + S3 upload), and updates the
785
+ database. Run this on the upload server, not on experimental computers.
786
+
787
+ Usage
788
+ -----
789
+ ::
790
+
791
+ labdata upload
792
+ labdata upload <job_id>
793
+ labdata upload --queue
794
+ labdata upload --reset-failed
795
+ labdata upload --all-hosts -n 4
796
+
797
+ Options
798
+ -------
799
+ job_id
800
+ Specific ``UploadJob.job_id`` values to process (optional).
801
+ When omitted, all waiting jobs for this host are processed.
802
+ -p, --project
803
+ Project name to filter jobs.
804
+ -n, --n-jobs
805
+ Number of parallel upload workers. Default is ``DEFAULT_N_JOBS``.
806
+ --queue
807
+ Print the current upload queue (working, failed, waiting, recent
808
+ completed jobs) and exit without processing.
809
+ --all-hosts
810
+ Process jobs assigned to any host, not just this machine.
811
+ --reset-failed
812
+ Reset all ``FAILED`` jobs to ``WAITING`` before processing.
813
+ '''
471
814
  parser = argparse.ArgumentParser(
472
- description = 'Runs an UploadTask',
473
- usage = '''labdata upload <JOB_ID> (optional) ''')
815
+ description='Runs an UploadTask',
816
+ usage='labdata upload <JOB_ID> (optional)')
474
817
  parser.add_argument('job_id',action = 'store',default = [], type = int, nargs = '*')
475
818
  parser.add_argument('--all-hosts',action = 'store_true',default = False)
476
819
  parser.add_argument('--reset-failed',action = 'store_true',default = False)
@@ -546,9 +889,34 @@ Available analysis are
546
889
  tasks = process_upload_jobs(key)
547
890
 
548
891
  def logpipe(self):
892
+ '''Pipe stdin into a ``ComputeTask.task_log`` entry.
893
+
894
+ Reads lines from stdin (intended to be piped from an analysis process)
895
+ and periodically flushes them to the ``task_log`` field of the given
896
+ ``ComputeTask``. This lets container jobs write their stdout to the
897
+ database for remote monitoring.
898
+
899
+ Typical usage (inside an sbatch script)::
900
+
901
+ labdata task <job_id> 2>&1 | labdata logpipe <job_id>
902
+
903
+ Usage
904
+ -----
905
+ ::
906
+
907
+ labdata logpipe <job_id>
908
+ labdata logpipe <job_id> -i 10
909
+
910
+ Options
911
+ -------
912
+ job_id
913
+ ``ComputeTask.job_id`` to write logs to.
914
+ -i, --refresh-period
915
+ How often (seconds) to flush the log to the database. Default 5.
916
+ '''
549
917
  parser = argparse.ArgumentParser(
550
- description = 'Sends the stdout to a log',
551
- usage = '''labdata logpipe <JOB_ID> ''')
918
+ description='Sends the stdout to a log',
919
+ usage='labdata logpipe <JOB_ID>')
552
920
  parser.add_argument('job_id',action = 'store',default = None,type = int)
553
921
  parser.add_argument('-i','--refresh-period',action = 'store',default = 5., type = float)
554
922
  args = parser.parse_args(sys.argv[2:])
@@ -569,7 +937,8 @@ Available analysis are
569
937
  return
570
938
  else:
571
939
  print(f'Appending stdout to ComputeTask [{job_id}] ')
572
- def handle_line(line,log, tic):
940
+ def handle_line(line, log, tic):
941
+ '''Append a line to the running log buffer and periodically flush to the DB.'''
573
942
  if not line is None:
574
943
  if log is None:
575
944
  log = line
@@ -739,5 +1108,15 @@ def _get_project():
739
1108
  project = str(os.environ['LABDATA_DATABASE_PROJECT'])
740
1109
  return project
741
1110
 
1111
+ def main_deprecated():
1112
+ import sys
1113
+ print("Warning: 'labdata2' is deprecated, use 'labdata' instead.", file=sys.stderr)
1114
+ main()
1115
+
742
1116
  def main():
1117
+ '''Entry point for the ``labdata`` command-line tool.
1118
+
1119
+ Instantiates ``CLI_parser``, which reads ``sys.argv`` and dispatches to
1120
+ the appropriate sub-command method.
1121
+ '''
743
1122
  CLI_parser()
@@ -2,21 +2,27 @@ from ..utils import *
2
2
  from .utils import BaseCompute
3
3
  from .suite2p import Suite2pCompute
4
4
  class CaimanCompute(Suite2pCompute):
5
+ '''Cell segmentation compute class using CaImAn.
6
+
7
+ Works for both one-photon (miniscope) and two-photon datasets. Runs
8
+ inside the ``labdata-caiman`` Apptainer container (CPU-only).
9
+
10
+ The compute pipeline:
11
+
12
+ 1. Identify files and determine dataset type (``Miniscope`` or ``TwoPhoton``).
13
+ 2. Copy the ``zarr.zip`` stack to the scratch directory.
14
+ 3. Run CaImAn CNMF/CNMF-E with the specified parameters.
15
+ 4. Delete memory-mapped intermediate files.
16
+ 5. Upload the HDF5 results file to S3 and insert into ``CellSegmentation``.
17
+
18
+ Parameters are stored in ``CellSegmentationParams`` (shared with
19
+ ``Suite2pCompute`` using the same part tables).
20
+ '''
5
21
  container = 'labdata-caiman'
6
22
  cuda = False
7
23
  name = 'caiman'
8
24
  url = 'http://github.com/flatironinstitute/CaImAn'
9
- def __init__(self,job_id, project = None, allow_s3 = None, **kwargs):
10
- '''
11
- This class runs Caiman on a dataset, which can be used for both 1p and 2p data. The ComputeTask will:
12
-
13
- 1. **File Identification and Dataset Type Check**: Identify the files and determine the type of dataset (Miniscope or TwoPhoton).
14
- 2. **File Copy to Scratch**: Copy only the necessary file(s) to a scratch folder for processing.
15
- 3. **Caiman Execution**: Execute Caiman on the copied file or folder. Using the parameters specified.
16
- 4. **Cleanup and Result Integration**: Delete the memory-mapped files generated during processing and integrate the results into the CellSegmentation table.
17
-
18
- This class includes a handler for the CLI.
19
- '''
25
+ def __init__(self, job_id, project=None, allow_s3=None, **kwargs):
20
26
  super(Suite2pCompute,self).__init__(job_id, project = project, allow_s3 = allow_s3) # takes BaseCompute init
21
27
  self.file_filters = ['.zarr.zip'] # this only runs on zarr.zip for the moment.
22
28
  # default parameters
@@ -359,7 +365,23 @@ class CaimanCompute(Suite2pCompute):
359
365
  print(f'[{self.name} job] Kept the temporary folder {temporary_folder}.')
360
366
  self.schema.CellSegmentationMetrics().populate(dkey, display_progress = True)
361
367
 
362
- def setup_cluster(n_cpus,cluster = None):
368
+ def setup_cluster(n_cpus, cluster=None):
369
+ '''Start (or restart) a CaImAn multiprocessing cluster.
370
+
371
+ If ``cluster`` is not ``None``, the existing cluster is stopped first.
372
+
373
+ Parameters
374
+ ----------
375
+ n_cpus : int
376
+ Number of parallel worker processes.
377
+ cluster : object or None, optional
378
+ Existing CaImAn cluster object to stop before starting a new one.
379
+
380
+ Returns
381
+ -------
382
+ object
383
+ The newly created CaImAn cluster (``dview``).
384
+ '''
363
385
  from caiman import stop_server, cluster
364
386
  #%% start a cluster for parallel processing (if a cluster already exists it will be closed and a new session will be opened)
365
387
  if not cluster is None: # 'locals' contains list of current local variables