labdata 0.1.24__tar.gz → 0.1.25__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.
- {labdata-0.1.24 → labdata-0.1.25}/PKG-INFO +1 -1
- {labdata-0.1.24 → labdata-0.1.25}/labdata/__init__.py +1 -1
- {labdata-0.1.24 → labdata-0.1.25}/labdata/cli.py +244 -18
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/ephys.py +4 -3
- labdata-0.1.25/labdata/compute/schedulers.py +1029 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/copy.py +29 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/activity.py +1 -1
- labdata-0.1.25/labdata/dashboard/ephys/__init__.py +1 -0
- {labdata-0.1.24/labdata/dashboard → labdata-0.1.25/labdata/dashboard/ephys}/sorting.py +94 -38
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/index.py +2 -2
- {labdata-0.1.24 → labdata-0.1.25}/labdata/rules/ephys.py +48 -16
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/ephys.py +82 -83
- {labdata-0.1.24 → labdata-0.1.25}/labdata/widgets.py +3 -1
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/PKG-INFO +1 -1
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/SOURCES.txt +5 -4
- {labdata-0.1.24 → labdata-0.1.25}/pyproject.toml +1 -1
- labdata-0.1.24/labdata/compute/schedulers.py +0 -547
- {labdata-0.1.24 → labdata-0.1.25}/LICENSE +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/README.md +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/accounts.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/__init__.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/caiman.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/ec2.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/pcluster.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/pose.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/singularity.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/spikeinterface.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/submission.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/suite2p.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/compute/utils.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/__init__.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/admin.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/ai_utils.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/compute.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/console.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/decisiontask.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/docstrings.py +0 -0
- {labdata-0.1.24/labdata/dashboard → labdata-0.1.25/labdata/dashboard/ephys}/drift.py +0 -0
- {labdata-0.1.24/labdata/dashboard → labdata-0.1.25/labdata/dashboard/ephys}/raster.py +0 -0
- /labdata-0.1.24/labdata/dashboard/ephys.py → /labdata-0.1.25/labdata/dashboard/ephys/recordings.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/events.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/imaging.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/launch.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/local_files.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/notes.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/procedures.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/reads.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/schema_graph.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/segmentation.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/sync.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/upload.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/upload_worker.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/video.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/dashboard/writes.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/ephys_io.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/ephys_signal.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/__init__.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/database.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/docker/Dockerfile.in +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/docker/entrypoint.sh +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/docker/load_database.sh +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/docker/mysql.cnf +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/docker.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/selection.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/share.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/export/users.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/rules/__init__.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/rules/imaging.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/rules/utils.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/s3.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/__init__.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/general.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/histology.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/onephoton.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/procedures.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/tasks.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/twophoton.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/utils.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/schema/video.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/stacks.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata/utils.py +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/dependency_links.txt +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/entry_points.txt +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/requires.txt +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/labdata.egg-info/top_level.txt +0 -0
- {labdata-0.1.24 → labdata-0.1.25}/setup.cfg +0 -0
|
@@ -32,7 +32,7 @@ class CLI_parser():
|
|
|
32
32
|
logpipe
|
|
33
33
|
Pipe stdout into a ``ComputeTask.task_log`` entry.
|
|
34
34
|
run_container
|
|
35
|
-
|
|
35
|
+
Open a shell or Jupyter Lab in a container, here or on a node of a remote cluster.
|
|
36
36
|
build_container
|
|
37
37
|
Build Apptainer ``.sif`` images from definition files.
|
|
38
38
|
cluster
|
|
@@ -56,7 +56,7 @@ Command to start the dashboard webpage:
|
|
|
56
56
|
|
|
57
57
|
Data manipulation commands are:
|
|
58
58
|
|
|
59
|
-
{tcolor["y"]("subjects")} List subjects
|
|
59
|
+
{tcolor["y"]("subjects")} List subjects (--add <subject> adds one)
|
|
60
60
|
{tcolor["y"]("sessions")} -a <subject> List sessions
|
|
61
61
|
{tcolor["y"]("get")} -a <subject> -s <session> Download data from one session if not already there
|
|
62
62
|
{tcolor["y"]("put")} -a <subject> -s <session> Copies a dataset to the server to be used
|
|
@@ -71,7 +71,7 @@ Data analysis commands:
|
|
|
71
71
|
Other
|
|
72
72
|
logpipe <compute_task_number> Appends stdout log to a ComputeTask
|
|
73
73
|
build-container Builds and uploads singularity/apptainer containers to S3
|
|
74
|
-
run-container
|
|
74
|
+
run-container <container> [-t <remote>] Opens a shell or Jupyter Lab in a container, here or on a cluster node
|
|
75
75
|
|
|
76
76
|
Administration commands (don't run on experimental computers):
|
|
77
77
|
{tcolor["r"]("upload")} Sends pending data to S3 (applies upload rules)
|
|
@@ -207,6 +207,8 @@ Administration commands (don't run on experimental computers):
|
|
|
207
207
|
labdata subjects -f <name_substring>
|
|
208
208
|
labdata subjects -s M
|
|
209
209
|
labdata subjects --include-size
|
|
210
|
+
labdata subjects --add <subject> --dob <YYYY-MM-DD> --sex <M|F|U> -u <user>
|
|
211
|
+
labdata subjects --add <subject> --dob <YYYY-MM-DD> --sex <M|F|U> --strain <strain> -u <user>
|
|
210
212
|
|
|
211
213
|
Options
|
|
212
214
|
-------
|
|
@@ -220,6 +222,12 @@ Administration commands (don't run on experimental computers):
|
|
|
220
222
|
Project name.
|
|
221
223
|
--include-size
|
|
222
224
|
Show total raw data size (TB) and file count per subject.
|
|
225
|
+
--add
|
|
226
|
+
Add a subject instead of listing, with ``--dob``, ``--sex``, ``--strain``
|
|
227
|
+
and ``-u`` (the lab member). At a terminal, without ``--strain`` or with
|
|
228
|
+
one that is not on record, the strain is picked from a numbered list of
|
|
229
|
+
the Strain table. A missing option prints which are missing, with the
|
|
230
|
+
strains and users on record.
|
|
223
231
|
'''
|
|
224
232
|
parser = argparse.ArgumentParser(
|
|
225
233
|
description='List sessions and datatypes',
|
|
@@ -246,7 +254,15 @@ Administration commands (don't run on experimental computers):
|
|
|
246
254
|
parser.add_argument('--include-size', action = 'store_true',
|
|
247
255
|
default = False,
|
|
248
256
|
help= 'Display the size of the raw data for each subject.')
|
|
249
|
-
|
|
257
|
+
parser.add_argument('--add', default = None, type = str, metavar = 'SUBJECT',
|
|
258
|
+
help = 'Add a subject; needs --dob, --sex and -u, and --strain or a pick from the list.')
|
|
259
|
+
parser.add_argument('--dob', default = None, type = str,
|
|
260
|
+
help = 'Date of birth of the subject to add (YYYY-MM-DD).')
|
|
261
|
+
parser.add_argument('--sex', default = None, type = str,
|
|
262
|
+
help = 'Sex of the subject to add (M, F or U).')
|
|
263
|
+
parser.add_argument('--strain', default = None, type = str,
|
|
264
|
+
help = 'Strain of the subject to add, from the Strain table; left out, pick it from a list.')
|
|
265
|
+
|
|
250
266
|
args = parser.parse_args(sys.argv[2:])
|
|
251
267
|
from .utils import load_project_schema
|
|
252
268
|
from datajoint.errors import AccessError
|
|
@@ -256,6 +272,9 @@ Administration commands (don't run on experimental computers):
|
|
|
256
272
|
txt = f'No permission to user {prefs["database"]["database.user"]} on schema {args.project}.'
|
|
257
273
|
print(f'{tcolor["r"](txt)}')
|
|
258
274
|
sys.exit(-1)
|
|
275
|
+
if not args.add is None:
|
|
276
|
+
self._add_subject(schema, args)
|
|
277
|
+
return
|
|
259
278
|
query = schema.Subject()
|
|
260
279
|
if not args.user in [None,'none']:
|
|
261
280
|
query = query & f'user_name = "{args.user}"'
|
|
@@ -296,6 +315,74 @@ Administration commands (don't run on experimental computers):
|
|
|
296
315
|
print(tt,flush = True)
|
|
297
316
|
print('')
|
|
298
317
|
|
|
318
|
+
def _add_subject(self, schema, args):
|
|
319
|
+
'''Insert the subject of ``labdata subjects --add`` after checking each field.'''
|
|
320
|
+
def stop(txt):
|
|
321
|
+
print(tcolor['r'](txt))
|
|
322
|
+
sys.exit(1)
|
|
323
|
+
name = args.add
|
|
324
|
+
if len(schema.Subject() & dict(subject_name = name)):
|
|
325
|
+
stop(f'Subject {name} is already in the database.')
|
|
326
|
+
# at a terminal the strain is chosen from a list; elsewhere --strain is required
|
|
327
|
+
interactive = can_prompt()
|
|
328
|
+
options = dict(dob = args.dob, sex = args.sex, u = args.user)
|
|
329
|
+
if not interactive:
|
|
330
|
+
options['strain'] = args.strain
|
|
331
|
+
missing = [('-' if k == 'u' else '--') + k for k, v in options.items() if v is None]
|
|
332
|
+
strains = list(schema.Strain().fetch('strain_name'))
|
|
333
|
+
users = schema.LabMember().fetch('user_name')
|
|
334
|
+
if len(missing):
|
|
335
|
+
stop(f'Adding {name} needs {", ".join(missing)}.\n'
|
|
336
|
+
f'Strains: {", ".join(strains)}\nUsers: {", ".join(users)}')
|
|
337
|
+
try:
|
|
338
|
+
dob = datetime.strptime(args.dob, '%Y-%m-%d').date()
|
|
339
|
+
except ValueError:
|
|
340
|
+
stop(f'Date of birth {args.dob} is expected as YYYY-MM-DD.')
|
|
341
|
+
sex = args.sex.upper()
|
|
342
|
+
if not sex in ['M', 'F', 'U']:
|
|
343
|
+
stop(f'Sex {args.sex} is expected as M, F or U.')
|
|
344
|
+
if not args.user in users:
|
|
345
|
+
stop(f'User {args.user} is not in the LabMember table. Users: {", ".join(users)}')
|
|
346
|
+
strain = args.strain
|
|
347
|
+
if not strain in strains:
|
|
348
|
+
if not interactive:
|
|
349
|
+
stop(f'Strain {strain} is not in the Strain table. Strains: {", ".join(strains)}')
|
|
350
|
+
if not strain is None:
|
|
351
|
+
print(f'Strain {strain} is not in the Strain table.')
|
|
352
|
+
strain = self._choose_strain(schema)
|
|
353
|
+
if strain is None:
|
|
354
|
+
stop(f'Canceled adding subject {name}.')
|
|
355
|
+
subject = dict(subject_name = name, subject_dob = dob, subject_sex = sex,
|
|
356
|
+
strain_name = strain, user_name = args.user)
|
|
357
|
+
try:
|
|
358
|
+
schema.Subject().insert1(subject)
|
|
359
|
+
except Exception as err:
|
|
360
|
+
stop(f'Could not add subject {name}: {err}')
|
|
361
|
+
print(f"{tcolor['g']('Added')} subject {name}\t{sex}\t{dob}\t{strain}\t{args.user}")
|
|
362
|
+
|
|
363
|
+
def _choose_strain(self, schema):
|
|
364
|
+
'''Return the strain picked at the prompt from a numbered list of the Strain table, or None.
|
|
365
|
+
|
|
366
|
+
Each strain shows the number of subjects of the project that have it.
|
|
367
|
+
'''
|
|
368
|
+
counts = schema.Strain.aggr(schema.Subject, n = 'count(subject_name)', keep_all_rows = True)
|
|
369
|
+
strains, n = counts.fetch('strain_name', 'n', order_by = 'strain_name')
|
|
370
|
+
print('Strains:')
|
|
371
|
+
for i, (strain, count) in enumerate(zip(strains, n)):
|
|
372
|
+
print(f'\t{tcolor["c"](str(i+1) + ".")} {strain} ({count} subjects)')
|
|
373
|
+
while True:
|
|
374
|
+
try:
|
|
375
|
+
answer = input(f'Strain number, 1-{len(strains)} (enter to cancel): ').strip()
|
|
376
|
+
except EOFError:
|
|
377
|
+
return None
|
|
378
|
+
if answer == '':
|
|
379
|
+
return None
|
|
380
|
+
if answer.isdigit() and 1 <= int(answer) <= len(strains):
|
|
381
|
+
return strains[int(answer) - 1]
|
|
382
|
+
if answer in strains:
|
|
383
|
+
return answer
|
|
384
|
+
print(f'{answer} is not on the list; type a number from 1 to {len(strains)}.')
|
|
385
|
+
|
|
299
386
|
def sessions(self):
|
|
300
387
|
'''List sessions and datasets for one or more subjects.
|
|
301
388
|
|
|
@@ -444,6 +531,9 @@ Administration commands (don't run on experimental computers):
|
|
|
444
531
|
database for the requested upload rule. **This does not upload to the
|
|
445
532
|
cloud** — that happens when ``labdata upload`` is later run on the server.
|
|
446
533
|
|
|
534
|
+
When Qt cannot be imported, or on Linux without a display, the files are
|
|
535
|
+
copied from the terminal with ``labdata.copy.copy_to_upload_server``.
|
|
536
|
+
|
|
447
537
|
Usage
|
|
448
538
|
-----
|
|
449
539
|
::
|
|
@@ -509,13 +599,26 @@ Administration commands (don't run on experimental computers):
|
|
|
509
599
|
w = LABDATA_PUT()
|
|
510
600
|
sys.exit(app.exec_())
|
|
511
601
|
else:
|
|
512
|
-
from .widgets import QApplication, ServerCopyWidget,QMessageBox
|
|
513
|
-
app = QApplication(sys.argv)
|
|
514
602
|
filepaths = args.filepaths
|
|
515
603
|
if len(filepaths) == 1:
|
|
516
604
|
if Path(filepaths[0]).is_dir(): # then select all files inside the folder
|
|
517
605
|
filepaths = list(Path(filepaths[0]).rglob('**/*'))
|
|
518
606
|
filepaths = list(filter(lambda f: f.is_file(),filepaths))
|
|
607
|
+
# without Qt or a display, copy from the terminal with copy_to_upload_server
|
|
608
|
+
reason = None
|
|
609
|
+
if sys.platform.startswith('linux') and not (os.environ.get('DISPLAY') or
|
|
610
|
+
os.environ.get('WAYLAND_DISPLAY')):
|
|
611
|
+
reason = 'There is no display for the copy window'
|
|
612
|
+
else:
|
|
613
|
+
try:
|
|
614
|
+
from .widgets import QApplication, ServerCopyWidget,QMessageBox
|
|
615
|
+
except ImportError as err:
|
|
616
|
+
reason = f'Qt is not available ({err})'
|
|
617
|
+
if not reason is None:
|
|
618
|
+
print(f'{reason}; copying from the terminal.', flush = True)
|
|
619
|
+
self._put_from_terminal(filepaths, args)
|
|
620
|
+
return
|
|
621
|
+
app = QApplication(sys.argv)
|
|
519
622
|
try:
|
|
520
623
|
w = ServerCopyWidget(src_filepaths = filepaths,
|
|
521
624
|
upload_rule = args.rule,
|
|
@@ -531,6 +634,44 @@ Administration commands (don't run on experimental computers):
|
|
|
531
634
|
raise(OSError(f"{tcolor['r']('Upload failed')} {err}."))
|
|
532
635
|
app.exit()
|
|
533
636
|
#sys.exit(app.exec_())
|
|
637
|
+
|
|
638
|
+
def _put_from_terminal(self, filepaths, args):
|
|
639
|
+
'''Copy files to the upload server with ``copy_to_upload_server``, without the Qt window.
|
|
640
|
+
|
|
641
|
+
``labdata put`` uses this when Qt cannot be imported or there is no display.
|
|
642
|
+
'''
|
|
643
|
+
from .copy import copy_to_upload_server
|
|
644
|
+
filepaths = [Path(f).resolve() for f in filepaths]
|
|
645
|
+
if not len(filepaths):
|
|
646
|
+
print(tcolor['r']('No files to copy.'))
|
|
647
|
+
return
|
|
648
|
+
missing = [str(f) for f in filepaths if not f.is_file()]
|
|
649
|
+
if len(missing):
|
|
650
|
+
raise OSError(f"{tcolor['r']('Upload failed')}: these are not files {missing}.")
|
|
651
|
+
names = dict(subject_name = args.subject,
|
|
652
|
+
session_name = args.session,
|
|
653
|
+
dataset_name = args.datatype,
|
|
654
|
+
dataset_type = args.datatype_name)
|
|
655
|
+
names = {k: v[0] for k, v in names.items() if not v is None}
|
|
656
|
+
rule = args.rule[0] if not args.rule is None else None
|
|
657
|
+
nbytes = sum(f.stat().st_size for f in filepaths)
|
|
658
|
+
txt = f"{len(filepaths)} files ({nbytes/1e9:.2f} GB) to {prefs.get('upload_path')}"
|
|
659
|
+
if args.ask:
|
|
660
|
+
if input(f'Copy {txt}? [y/N] ').strip().lower() not in ('y', 'yes'):
|
|
661
|
+
print(tcolor['r']('Canceled the copy to the upload server.'))
|
|
662
|
+
return
|
|
663
|
+
print(f'Copying {txt}.', flush = True)
|
|
664
|
+
try:
|
|
665
|
+
res = copy_to_upload_server(filepaths,
|
|
666
|
+
job_rule = rule,
|
|
667
|
+
overwrite = args.overwrite,
|
|
668
|
+
project = args.project,
|
|
669
|
+
**names)
|
|
670
|
+
except Exception as err:
|
|
671
|
+
raise OSError(f"{tcolor['r']('Upload failed')} {err}.") from err
|
|
672
|
+
print(f"{tcolor['g']('Copied')} {len(res)} files: upload job {res[0]['job_id']} "
|
|
673
|
+
f"waits for labdata upload on {prefs.get('upload_host')}.", flush = True)
|
|
674
|
+
|
|
534
675
|
def clean(self):
|
|
535
676
|
'''Delete local copies of files that are confirmed uploaded to S3.
|
|
536
677
|
|
|
@@ -2305,26 +2446,112 @@ the preference file, or set them with LABDATA_DATABASE_PROJECT for the project.
|
|
|
2305
2446
|
print(f"Build the image with: docker build -t {args.tag or '<tag>'} {args.docker}")
|
|
2306
2447
|
|
|
2307
2448
|
def run_container(self):
|
|
2449
|
+
'''Open a shell or Jupyter Lab in a container, here or on a node of a remote cluster.
|
|
2450
|
+
|
|
2451
|
+
Usage
|
|
2452
|
+
-----
|
|
2453
|
+
::
|
|
2454
|
+
|
|
2455
|
+
labdata run-container <container>
|
|
2456
|
+
labdata run-container <container> --jupyter
|
|
2457
|
+
labdata run-container <container> -t <remote> [--partition gpu] [--walltime 2:00:00]
|
|
2458
|
+
labdata run-container <container> -t <remote> --jupyter
|
|
2459
|
+
labdata run-container -t <remote>
|
|
2460
|
+
labdata run-container -t <remote> --attach <slurm job id>
|
|
2461
|
+
|
|
2462
|
+
With ``-t`` naming a remote of the preference file, the command submits
|
|
2463
|
+
a SLURM job there, waits until it starts and opens a shell in the
|
|
2464
|
+
container on its node, or with ``--jupyter`` starts Jupyter Lab there
|
|
2465
|
+
and tunnels it to this computer. The session has the environment of a
|
|
2466
|
+
labdata job on that remote (project, credentials, ``pre_cmds``). Ending
|
|
2467
|
+
the session ends the job; a lost connection leaves it running, to
|
|
2468
|
+
reconnect with ``--attach``. Without a container name on a remote the
|
|
2469
|
+
shell opens on the node itself.
|
|
2470
|
+
|
|
2471
|
+
Options
|
|
2472
|
+
-------
|
|
2473
|
+
container
|
|
2474
|
+
Container name: the .sif file in the containers folder, without extension.
|
|
2475
|
+
-t, --target
|
|
2476
|
+
``local`` (default), or a remote of the preference file.
|
|
2477
|
+
--jupyter
|
|
2478
|
+
Start Jupyter Lab instead of a shell.
|
|
2479
|
+
--writable, --cuda
|
|
2480
|
+
Local only: open the container writable, give it the GPUs. On a
|
|
2481
|
+
remote the container gets the GPUs when the node has them.
|
|
2482
|
+
--partition, --ncpus, --gpus, --memory, --walltime, --exclusive, --no-exclusive
|
|
2483
|
+
Resources of the job on a remote. The defaults come from the
|
|
2484
|
+
``interactive`` entry of the remote's ``analysis_options``; without
|
|
2485
|
+
it the job takes a whole node for 4 hours.
|
|
2486
|
+
--attach
|
|
2487
|
+
SLURM job id of a running job to open the session in.
|
|
2488
|
+
--keep
|
|
2489
|
+
Leave the job running when the session ends.
|
|
2490
|
+
-p, --project
|
|
2491
|
+
Project of the session on a remote.
|
|
2308
2492
|
'''
|
|
2309
|
-
|
|
2310
|
-
'''
|
|
2493
|
+
remotes = list((prefs['compute'].get('remotes') or {}).keys())
|
|
2311
2494
|
parser = argparse.ArgumentParser(
|
|
2312
|
-
description = '
|
|
2313
|
-
usage = '''labdata run-container <container> ''')
|
|
2495
|
+
description = 'Open a shell or Jupyter Lab in a container, here or on a node of a remote',
|
|
2496
|
+
usage = '''labdata run-container <container> [-t <remote>] [--jupyter]''')
|
|
2314
2497
|
parser.add_argument('container_name',
|
|
2315
2498
|
action = 'store',
|
|
2316
|
-
type = str,
|
|
2317
|
-
help = 'Container name (the .sif file in the
|
|
2499
|
+
type = str, nargs = '?', default = None,
|
|
2500
|
+
help = 'Container name (the .sif file in the containers folder, without extension).')
|
|
2318
2501
|
parser.add_argument('-t','--target',action = 'store', default = 'local', type = str,
|
|
2319
|
-
help = 'Where to run it: local or
|
|
2502
|
+
help = f'Where to run it: local, or a remote [{tcolor["y"](", ".join(remotes))}].')
|
|
2320
2503
|
parser.add_argument('--jupyter', action = 'store_true', default = False,
|
|
2321
2504
|
help = 'Start Jupyter Lab in the container instead of a shell.')
|
|
2322
2505
|
parser.add_argument('--writable', action = 'store_true', default = False,
|
|
2323
|
-
help = 'Open the container writable (for sandbox containers).')
|
|
2506
|
+
help = 'Open the container writable (for sandbox containers, local only).')
|
|
2324
2507
|
parser.add_argument('--cuda',action = 'store_true', default = False,
|
|
2325
|
-
help = 'Give the container access to the GPUs.')
|
|
2508
|
+
help = 'Give the container access to the GPUs (local only; on a remote when the node has them).')
|
|
2509
|
+
parser.add_argument('--partition', action = 'store', default = None, type = str,
|
|
2510
|
+
help = 'SLURM partition of the job on a remote.')
|
|
2511
|
+
parser.add_argument('--ncpus', action = 'store', default = None, type = int,
|
|
2512
|
+
help = 'CPUs of the job on a remote.')
|
|
2513
|
+
parser.add_argument('--gpus', action = 'store', default = None, type = str,
|
|
2514
|
+
help = 'GPUs of the job on a remote (SLURM --gpus).')
|
|
2515
|
+
parser.add_argument('--memory', action = 'store', default = None, type = str,
|
|
2516
|
+
help = 'Memory of the job on a remote, e.g. 32G.')
|
|
2517
|
+
parser.add_argument('--walltime', action = 'store', default = None, type = str,
|
|
2518
|
+
help = 'Time limit of the job on a remote (default 4:00:00).')
|
|
2519
|
+
parser.add_argument('--exclusive', action = 'store_const', const = True, dest = 'exclusive', default = None,
|
|
2520
|
+
help = 'Take the whole node (the default on a remote).')
|
|
2521
|
+
parser.add_argument('--no-exclusive', action = 'store_const', const = False, dest = 'exclusive',
|
|
2522
|
+
help = 'Share the node with other jobs.')
|
|
2523
|
+
parser.add_argument('--attach', action = 'store', default = None, type = int,
|
|
2524
|
+
help = 'SLURM job id (not a ComputeTask id) of a running job to open the session in.')
|
|
2525
|
+
parser.add_argument('--keep', action = 'store_true', default = False,
|
|
2526
|
+
help = 'Leave the job running when the session ends.')
|
|
2527
|
+
parser = self._add_default_arguments(parser, level = 0, include_project = True)
|
|
2326
2528
|
|
|
2327
2529
|
args = parser.parse_args(sys.argv[2:])
|
|
2530
|
+
if args.target in remotes:
|
|
2531
|
+
from .compute.schedulers import run_container_remote
|
|
2532
|
+
try:
|
|
2533
|
+
run_container_remote(args.target,
|
|
2534
|
+
container = args.container_name,
|
|
2535
|
+
jupyter = args.jupyter,
|
|
2536
|
+
partition = args.partition,
|
|
2537
|
+
ncpus = args.ncpus,
|
|
2538
|
+
gpus = args.gpus,
|
|
2539
|
+
memory = args.memory,
|
|
2540
|
+
walltime = args.walltime,
|
|
2541
|
+
exclusive = args.exclusive,
|
|
2542
|
+
attach = args.attach,
|
|
2543
|
+
keep = args.keep,
|
|
2544
|
+
project = args.project)
|
|
2545
|
+
except (RuntimeError, ValueError) as err:
|
|
2546
|
+
print(tcolor['r'](str(err)))
|
|
2547
|
+
sys.exit(1)
|
|
2548
|
+
return
|
|
2549
|
+
if not args.target in ['local','slurm']:
|
|
2550
|
+
print(f"{tcolor['r'](args.target)} is not a target: use local or one of the remotes "
|
|
2551
|
+
f"[{', '.join(remotes)}].")
|
|
2552
|
+
sys.exit(1)
|
|
2553
|
+
if args.container_name is None:
|
|
2554
|
+
parser.error('the container name is needed here; it is optional only on a remote.')
|
|
2328
2555
|
container = args.container_name
|
|
2329
2556
|
container_file = None
|
|
2330
2557
|
cmd = None
|
|
@@ -2334,11 +2561,10 @@ the preference file, or set them with LABDATA_DATABASE_PROJECT for the project.
|
|
|
2334
2561
|
container_file = (container_store/container).with_suffix('.sif')
|
|
2335
2562
|
if not container_file.exists():
|
|
2336
2563
|
print( f"Container {tcolor['r'](container)} not found in {tcolor['r'](container_store)}")
|
|
2337
|
-
return
|
|
2338
|
-
|
|
2564
|
+
return
|
|
2565
|
+
|
|
2339
2566
|
if args.jupyter:
|
|
2340
2567
|
cmd = 'jupyter lab --ip="*"'
|
|
2341
|
-
print(args)
|
|
2342
2568
|
if not cmd is None:
|
|
2343
2569
|
from .compute.singularity import run_on_apptainer
|
|
2344
2570
|
launchcmd = run_on_apptainer(container_file,
|
|
@@ -622,9 +622,10 @@ class SpksCompute(BaseCompute):
|
|
|
622
622
|
**channel_statistics(segment.astype(np.float32)*gains)))
|
|
623
623
|
del dat
|
|
624
624
|
self.set_job_status(job_log = f'Completed {base_key}')
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
625
|
+
# every dataset of the sorting; limit number of jobs because of memory constraints
|
|
626
|
+
self.schema.UnitMetrics.populate(base_keys, display_progress = True, processes = int(max(1,np.ceil(n_jobs/2))))
|
|
627
|
+
# label and count the units under every computed criteria of the project
|
|
628
|
+
self.schema.UnitCount.populate(base_keys, display_progress = True)
|
|
628
629
|
|
|
629
630
|
def extract_waveforms(self, udict, nchannels, results_folder, n_pre_samples, n_jobs, offsets, gains):
|
|
630
631
|
'''Collect the waveforms of the units of one dataset from the filtered binary.
|