labdata 0.1.8__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 (60) hide show
  1. {labdata-0.1.8 → labdata-0.1.9}/PKG-INFO +1 -1
  2. {labdata-0.1.8 → labdata-0.1.9}/labdata/__init__.py +1 -1
  3. {labdata-0.1.8 → labdata-0.1.9}/labdata/cli.py +400 -24
  4. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/caiman.py +34 -12
  5. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/ec2.py +166 -18
  6. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/ephys.py +178 -37
  7. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/pose.py +55 -6
  8. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/schedulers.py +151 -36
  9. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/singularity.py +63 -10
  10. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/suite2p.py +21 -4
  11. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/utils.py +267 -40
  12. {labdata-0.1.8 → 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.8 → 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.8 → labdata-0.1.9}/labdata/dashboard/decisiontask.py +76 -12
  17. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/explorer/cellsegmentation.py +99 -13
  18. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/explorer/spikesorting.py +90 -12
  19. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/index.py +120 -3
  20. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/notes.py +12 -0
  21. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/procedures.py +34 -0
  22. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/schema_graph.py +15 -0
  23. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/segmentation.py +24 -0
  24. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/sorting.py +12 -1
  25. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/video.py +64 -0
  26. {labdata-0.1.8 → labdata-0.1.9}/labdata/export.py +95 -4
  27. {labdata-0.1.8 → labdata-0.1.9}/labdata/rules/__init__.py +37 -10
  28. {labdata-0.1.8 → labdata-0.1.9}/labdata/rules/ephys.py +126 -23
  29. {labdata-0.1.8 → labdata-0.1.9}/labdata/rules/imaging.py +215 -40
  30. {labdata-0.1.8 → labdata-0.1.9}/labdata/rules/utils.py +66 -12
  31. {labdata-0.1.8 → labdata-0.1.9}/labdata/s3.py +19 -1
  32. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/ephys.py +227 -76
  33. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/general.py +227 -30
  34. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/histology.py +14 -1
  35. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/onephoton.py +28 -2
  36. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/procedures.py +6 -0
  37. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/tasks.py +6 -0
  38. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/twophoton.py +136 -22
  39. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/video.py +74 -13
  40. {labdata-0.1.8 → labdata-0.1.9}/labdata/stacks.py +44 -2
  41. {labdata-0.1.8 → labdata-0.1.9}/labdata/utils.py +81 -6
  42. {labdata-0.1.8 → labdata-0.1.9}/labdata/widgets.py +148 -30
  43. {labdata-0.1.8 → labdata-0.1.9}/labdata.egg-info/PKG-INFO +1 -1
  44. {labdata-0.1.8 → 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.8 → labdata-0.1.9}/pyproject.toml +2 -1
  47. labdata-0.1.8/labdata/dashboard/console.py +0 -166
  48. labdata-0.1.8/labdata.egg-info/entry_points.txt +0 -2
  49. {labdata-0.1.8 → labdata-0.1.9}/LICENSE +0 -0
  50. {labdata-0.1.8 → labdata-0.1.9}/README.md +0 -0
  51. {labdata-0.1.8 → labdata-0.1.9}/labdata/compute/__init__.py +0 -0
  52. {labdata-0.1.8 → labdata-0.1.9}/labdata/copy.py +0 -0
  53. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/__init__.py +0 -0
  54. {labdata-0.1.8 → labdata-0.1.9}/labdata/dashboard/explorer/__init__.py +0 -0
  55. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/__init__.py +0 -0
  56. {labdata-0.1.8 → labdata-0.1.9}/labdata/schema/utils.py +0 -0
  57. {labdata-0.1.8 → labdata-0.1.9}/labdata.egg-info/dependency_links.txt +0 -0
  58. {labdata-0.1.8 → labdata-0.1.9}/labdata.egg-info/requires.txt +0 -0
  59. {labdata-0.1.8 → labdata-0.1.9}/labdata.egg-info/top_level.txt +0 -0
  60. {labdata-0.1.8 → 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.8
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
@@ -1,4 +1,4 @@
1
- VERSION = "0.1.8"
1
+ VERSION = "0.1.9"
2
2
 
3
3
  from .utils import *
4
4
  from .copy import copy_to_upload_server
@@ -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,
@@ -105,9 +175,37 @@ Server commands (don't run on experimental computers):
105
175
  sub.run(command,shell=True)
106
176
 
107
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
+ '''
108
206
  parser = argparse.ArgumentParser(
109
- description = 'List sessions and datatypes',
110
- usage = '''labdata subject -u <USER> -f <FILTER>''')
207
+ description='List sessions and datatypes',
208
+ usage='labdata subject -u <USER> -f <FILTER>')
111
209
 
112
210
  project = _get_project()
113
211
  parser.add_argument('-p','--project',
@@ -181,9 +279,32 @@ Server commands (don't run on experimental computers):
181
279
  print('')
182
280
 
183
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
+ '''
184
305
  parser = argparse.ArgumentParser(
185
- description = 'List sessions and datatypes',
186
- usage = '''labdata sessions -a <SUBJECT>''')
306
+ description='List sessions and datatypes',
307
+ usage='labdata sessions -a <SUBJECT>')
187
308
  parser = self._add_default_arguments(parser,1, include_project = True)
188
309
  parser.add_argument('--include-size', action = 'store_true',
189
310
  default = False)
@@ -231,9 +352,35 @@ Server commands (don't run on experimental computers):
231
352
  print(f'\t\t {pre} {t["dataset_type"]} - {t["dataset_name"]} {extra}')
232
353
 
233
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
+ '''
234
381
  parser = argparse.ArgumentParser(
235
- description = 'Download data from one or multiple sessions',
236
- usage = '''labdata sessions -a <SUBJECTS> -s <SESSIONS>''')
382
+ description='Download data from one or multiple sessions',
383
+ usage='labdata get -a <SUBJECTS> -s <SESSIONS>')
237
384
  parser = self._add_default_arguments(parser,3, include_project = True)
238
385
 
239
386
  # TODO: Add an argument to include files that match a pattern..
@@ -271,9 +418,45 @@ Server commands (don't run on experimental computers):
271
418
  (schema.File() & (schema.Dataset.DataFiles() & keys).proj()).get()
272
419
 
273
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
+ '''
274
457
  parser = argparse.ArgumentParser(
275
- description = 'Copies data to the server to be uploaded [THIS DOES NOT UPLOAD TO THE CLOUD]',
276
- 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>')
277
460
  from .rules import rulesmap
278
461
  rules_help = ','.join([tcolor['g'](r) for r in rulesmap.keys()])
279
462
  parser = self._add_default_arguments(parser,include_project = True)
@@ -325,9 +508,34 @@ Server commands (don't run on experimental computers):
325
508
  app.exit()
326
509
  #sys.exit(app.exec_())
327
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
+ '''
328
536
  parser = argparse.ArgumentParser(
329
- description = 'Releases local storage space.',
330
- usage = '''labdata clean -f "ephys"''')
537
+ description='Releases local storage space.',
538
+ usage='labdata clean -f "ephys"')
331
539
  parser.add_argument('-f','--filter',action = 'store',default = [], type = str, nargs = '+')
332
540
  parser.add_argument('--dry-run',action = 'store_true', default = False)
333
541
  parser.add_argument('--only-processed',action = 'store_true', default = False)
@@ -338,9 +546,57 @@ Server commands (don't run on experimental computers):
338
546
  print(deleted,kept)
339
547
 
340
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
+ '''
341
597
  parser = argparse.ArgumentParser(
342
- description = 'Allocates or runs an analysis',
343
- 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>
344
600
 
345
601
  Available analysis are
346
602
  {tcolor['g'](', '.join(prefs["compute"]["analysis"].keys()))}''')
@@ -417,9 +673,37 @@ Available analysis are
417
673
  run_analysis(target,jobids, obj, project = args.project)
418
674
 
419
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
+ '''
420
704
  parser = argparse.ArgumentParser(
421
- description = 'Reset a task in the ComputeTask so the job can be ran again',
422
- 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>')
423
707
  parser.add_argument('job_id', action = 'store', type = int, nargs='+')
424
708
  parser.add_argument('-t','--target', action = 'store', default = None, type = str)
425
709
  parser.add_argument('--resubmit', action = 'store_true', default = False)
@@ -453,9 +737,32 @@ Available analysis are
453
737
  run_analysis(target,[job_id],obj)
454
738
 
455
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
+ '''
456
763
  parser = argparse.ArgumentParser(
457
- description = 'Runs a ComputeTask',
458
- usage = '''labdata task <JOB_ID> ''')
764
+ description='Runs a ComputeTask',
765
+ usage='labdata task <JOB_ID>')
459
766
  parser.add_argument('job_id',action = 'store',default = None,type = int)
460
767
  project = _get_project()
461
768
  parser.add_argument('-p','--project',
@@ -471,9 +778,42 @@ Available analysis are
471
778
  task.compute()
472
779
 
473
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
+ '''
474
814
  parser = argparse.ArgumentParser(
475
- description = 'Runs an UploadTask',
476
- usage = '''labdata upload <JOB_ID> (optional) ''')
815
+ description='Runs an UploadTask',
816
+ usage='labdata upload <JOB_ID> (optional)')
477
817
  parser.add_argument('job_id',action = 'store',default = [], type = int, nargs = '*')
478
818
  parser.add_argument('--all-hosts',action = 'store_true',default = False)
479
819
  parser.add_argument('--reset-failed',action = 'store_true',default = False)
@@ -549,9 +889,34 @@ Available analysis are
549
889
  tasks = process_upload_jobs(key)
550
890
 
551
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
+ '''
552
917
  parser = argparse.ArgumentParser(
553
- description = 'Sends the stdout to a log',
554
- usage = '''labdata logpipe <JOB_ID> ''')
918
+ description='Sends the stdout to a log',
919
+ usage='labdata logpipe <JOB_ID>')
555
920
  parser.add_argument('job_id',action = 'store',default = None,type = int)
556
921
  parser.add_argument('-i','--refresh-period',action = 'store',default = 5., type = float)
557
922
  args = parser.parse_args(sys.argv[2:])
@@ -572,7 +937,8 @@ Available analysis are
572
937
  return
573
938
  else:
574
939
  print(f'Appending stdout to ComputeTask [{job_id}] ')
575
- 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.'''
576
942
  if not line is None:
577
943
  if log is None:
578
944
  log = line
@@ -742,5 +1108,15 @@ def _get_project():
742
1108
  project = str(os.environ['LABDATA_DATABASE_PROJECT'])
743
1109
  return project
744
1110
 
1111
+ def main_deprecated():
1112
+ import sys
1113
+ print("Warning: 'labdata2' is deprecated, use 'labdata' instead.", file=sys.stderr)
1114
+ main()
1115
+
745
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
+ '''
746
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