labdata 0.0.23__tar.gz → 0.0.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.
Files changed (58) hide show
  1. {labdata-0.0.23 → labdata-0.0.25}/PKG-INFO +2 -2
  2. {labdata-0.0.23 → labdata-0.0.25}/labdata/__init__.py +1 -1
  3. {labdata-0.0.23 → labdata-0.0.25}/labdata/cli.py +1 -1
  4. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/caiman.py +8 -0
  5. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/ephys.py +15 -11
  6. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/suite2p.py +81 -33
  7. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/utils.py +2 -2
  8. labdata-0.0.25/labdata/dashboard/compute.py +112 -0
  9. labdata-0.0.25/labdata/dashboard/decisiontask.py +331 -0
  10. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/explorer/spikesorting.py +47 -13
  11. labdata-0.0.25/labdata/dashboard/index.py +225 -0
  12. labdata-0.0.25/labdata/dashboard/notes.py +184 -0
  13. labdata-0.0.25/labdata/dashboard/procedures.py +284 -0
  14. labdata-0.0.25/labdata/dashboard/segmentation.py +414 -0
  15. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/sorting.py +1 -1
  16. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/video.py +48 -12
  17. {labdata-0.0.23 → labdata-0.0.25}/labdata/rules/ephys.py +7 -1
  18. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/ephys.py +9 -5
  19. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/general.py +6 -5
  20. {labdata-0.0.23 → labdata-0.0.25}/labdata/stacks.py +18 -0
  21. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/PKG-INFO +2 -2
  22. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/SOURCES.txt +2 -0
  23. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/requires.txt +1 -1
  24. {labdata-0.0.23 → labdata-0.0.25}/pyproject.toml +1 -1
  25. labdata-0.0.23/labdata/dashboard/compute.py +0 -54
  26. labdata-0.0.23/labdata/dashboard/decisiontask.py +0 -144
  27. labdata-0.0.23/labdata/dashboard/index.py +0 -253
  28. labdata-0.0.23/labdata/dashboard/segmentation.py +0 -49
  29. {labdata-0.0.23 → labdata-0.0.25}/LICENSE +0 -0
  30. {labdata-0.0.23 → labdata-0.0.25}/README.md +0 -0
  31. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/__init__.py +0 -0
  32. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/ec2.py +0 -0
  33. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/pose.py +0 -0
  34. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/schedulers.py +0 -0
  35. {labdata-0.0.23 → labdata-0.0.25}/labdata/compute/singularity.py +0 -0
  36. {labdata-0.0.23 → labdata-0.0.25}/labdata/copy.py +0 -0
  37. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/__init__.py +0 -0
  38. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/explorer/__init__.py +0 -0
  39. {labdata-0.0.23 → labdata-0.0.25}/labdata/dashboard/explorer/cellsegmentation.py +0 -0
  40. {labdata-0.0.23 → labdata-0.0.25}/labdata/export.py +0 -0
  41. {labdata-0.0.23 → labdata-0.0.25}/labdata/rules/__init__.py +0 -0
  42. {labdata-0.0.23 → labdata-0.0.25}/labdata/rules/imaging.py +0 -0
  43. {labdata-0.0.23 → labdata-0.0.25}/labdata/rules/utils.py +0 -0
  44. {labdata-0.0.23 → labdata-0.0.25}/labdata/s3.py +0 -0
  45. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/__init__.py +0 -0
  46. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/histology.py +0 -0
  47. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/onephoton.py +0 -0
  48. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/procedures.py +0 -0
  49. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/tasks.py +0 -0
  50. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/twophoton.py +0 -0
  51. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/utils.py +0 -0
  52. {labdata-0.0.23 → labdata-0.0.25}/labdata/schema/video.py +0 -0
  53. {labdata-0.0.23 → labdata-0.0.25}/labdata/utils.py +0 -0
  54. {labdata-0.0.23 → labdata-0.0.25}/labdata/widgets.py +0 -0
  55. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/dependency_links.txt +0 -0
  56. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/entry_points.txt +0 -0
  57. {labdata-0.0.23 → labdata-0.0.25}/labdata.egg-info/top_level.txt +0 -0
  58. {labdata-0.0.23 → labdata-0.0.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labdata
3
- Version: 0.0.23
3
+ Version: 0.0.25
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
@@ -23,7 +23,7 @@ Requires-Dist: dash; extra == "dashboard"
23
23
  Requires-Dist: dash_bootstrap_components; extra == "dashboard"
24
24
  Requires-Dist: dash-extensions; extra == "dashboard"
25
25
  Requires-Dist: plotly; extra == "dashboard"
26
- Requires-Dist: streamlit>1.34; extra == "dashboard"
26
+ Requires-Dist: streamlit>1.54; extra == "dashboard"
27
27
  Requires-Dist: streamlit-bokeh; extra == "dashboard"
28
28
  Requires-Dist: scipy; extra == "dashboard"
29
29
  Requires-Dist: scikit-image; extra == "dashboard"
@@ -1,4 +1,4 @@
1
- VERSION = "0.0.23"
1
+ VERSION = "0.0.25"
2
2
 
3
3
  from .utils import *
4
4
  from .copy import copy_to_upload_server
@@ -566,7 +566,7 @@ Available analysis are
566
566
  if log is None:
567
567
  log = line
568
568
  else:
569
- if log.endswith('\n'):
569
+ if log.endswith('\n') or log.endswith('\r'):
570
570
  log += line
571
571
  else:
572
572
  log += '\n' + line
@@ -163,6 +163,8 @@ class CaimanCompute(Suite2pCompute):
163
163
  else:
164
164
  frame_rate = (self.schema.Miniscope() & self.dataset_key).fetch1('frame_rate')
165
165
  is_two_photon = False
166
+ if len((self.schema.File & dset).check_if_files_local()[0]) == 0:
167
+ self.files_existed = False
166
168
  dat = dset.open()
167
169
  parameters = (self.schema.CellSegmentationParams & f'parameter_set_num = {self.parameter_set_num}').fetch1()
168
170
 
@@ -271,6 +273,9 @@ class CaimanCompute(Suite2pCompute):
271
273
  selectiondict = []
272
274
  for icell,roi in enumerate(roi_masks):
273
275
  roi_pixels, roi_pixels_values = get_roi_pixels(roi)
276
+ if len(roi_pixels)>5000:
277
+ print(f"[CaimanCompute] {icell} had an roi with {len(roi_pixels)} pixels. Skipping.")
278
+ continue
274
279
  roidict.append(dict(planekey,
275
280
  roi_num = icell,
276
281
  roi_pixels = roi_pixels,
@@ -320,6 +325,9 @@ class CaimanCompute(Suite2pCompute):
320
325
  print(f'[{self.name} job] Removing the temporary folder.')
321
326
  import shutil
322
327
  shutil.rmtree(temporary_folder)
328
+ if not self.files_existed:
329
+ for f in (self.schema.File & dset).get():
330
+ os.unlink(f)
323
331
  else:
324
332
  print(f'[{self.name} job] Kept the temporary folder {temporary_folder}.')
325
333
 
@@ -21,7 +21,7 @@ class SpksCompute(BaseCompute):
21
21
  self.file_filters = ['.ap.']
22
22
  # default parameters
23
23
  self.parameters = dict(algorithm_name = 'spks_kilosort4.0',
24
- motion_correction = True,
24
+ motion_correction = 1,
25
25
  low_pass = 300.,
26
26
  high_pass = 13000.)
27
27
  self.parameter_keys = ['motion_correction','low_pass','high_pass','thresholds','remove_cross_duplicates',
@@ -151,7 +151,7 @@ class SpksCompute(BaseCompute):
151
151
  if not args.thresholds is None:
152
152
  self.parameters['thresholds'] = args.thresholds
153
153
  if self.parameters['motion_correction'] < 2:
154
- self.parameters['motion_correction'] = bool(self.parameters['motion_correction'])
154
+ self.parameters['motion_correction'] = self.parameters['motion_correction']
155
155
  self.probe = args.probe
156
156
  if not self.probe is None:
157
157
  self.parameters['probe'] = int(self.probe) # submit a single probe
@@ -188,8 +188,8 @@ class SpksCompute(BaseCompute):
188
188
  datasets += (self.schema.EphysRecording()& k).proj('subject_name','session_name','dataset_name').fetch(as_dict = True)
189
189
 
190
190
  if not parameter_set_num is None:
191
- datasets = ((self.schema.EphysRecording() & datasets) -
192
- (self.schema.SpikeSorting() & f'parameter_set_num = {parameter_set_num}')).proj(
191
+ datasets = (self.schema.EphysRecording & ((self.schema.EphysRecording.ProbeSetting() & datasets) -
192
+ (self.schema.SpikeSorting() & f'parameter_set_num = {parameter_set_num}'))).proj(
193
193
  'subject_name',
194
194
  'session_name',
195
195
  'dataset_name').fetch(as_dict = True)
@@ -314,7 +314,9 @@ class SpksCompute(BaseCompute):
314
314
  # send the files to AWS # this will skip the raw data binary files
315
315
  dataset = dict(**self.dataset_key[0])
316
316
  dataset['dataset_name'] = f'spike_sorting/imec{probe_num}/{self.parameter_set_num}/{self.parameters["algorithm_name"]}'
317
- src = [a for a in Path(results_folder).rglob('*') if not str(a).endswith('.bin')] # get the files minus the .bin
317
+ src = [a for a in Path(results_folder).rglob('*') if not (str(a).endswith('.bin') or
318
+ str(a).endswith('.dat') or
319
+ str(a).endswith('.hdf5'))] # get the files minus the .bin/.dat
318
320
  new_src = [Path(prefs['local_paths'][0])/a for a in self.schema.AnalysisFile().generate_filepaths(src,dataset)]
319
321
  from tqdm.auto import tqdm
320
322
  import shutil
@@ -445,7 +447,7 @@ class SpksCompute(BaseCompute):
445
447
  shuffle = True)
446
448
  from tqdm.auto import tqdm
447
449
  # print(f'Collecting waveforms and saving for dataset:{idset}.')
448
- # save these to zarr to be compressed faster
450
+ # save these to zarr to be compressed faster?
449
451
  if self.use_hdf5: # zarr not implemented yet.
450
452
  import h5py as h5
451
453
  with h5.File(Path(results_folder)/f'waveforms{idset}.hdf5','w') as wavefile:
@@ -475,6 +477,7 @@ class SpksCompute(BaseCompute):
475
477
  # insert the syncs
476
478
  if len(event):
477
479
  # Add stream
480
+ print(f'Inserting {len(event)} events.')
478
481
  self.schema.DatasetEvents.insert1(dict(dataset_key,
479
482
  stream_name = stream_name),
480
483
  skip_duplicates = True,
@@ -504,10 +507,10 @@ class SpksCompute(BaseCompute):
504
507
  offset_samples = offset_samples - offsets[0],
505
508
  segment = np.array(dat[offset_samples : offset_samples + nsamples])))
506
509
  del dat
507
- self.set_job_status(job_log = f'Completed {base_key}')
508
- from labdata.schema import UnitMetrics
509
- # limit number of jobs because of memory constraints
510
- self.schema.UnitMetrics.populate(base_key, processes = int(max(1,np.ceil(n_jobs/2))))
510
+ self.set_job_status(job_log = f'Completed {base_key}')
511
+ from labdata.schema import UnitMetrics
512
+ # limit number of jobs because of memory constraints
513
+ self.schema.UnitMetrics.populate(base_key, display_progress = True, processes = int(max(1,np.ceil(n_jobs/2))))
511
514
 
512
515
  def extract_waveforms(self,udict, nchannels, results_folder, n_pre_samples,n_jobs, offsets):
513
516
  # extract the waveforms
@@ -575,7 +578,8 @@ def get_waveforms_from_binary(binary_file,
575
578
  n_jobs = 8):
576
579
  from tqdm.auto import tqdm
577
580
  from spks.io import map_binary
578
- dat = map_binary(binary_file,nchannels = binary_file_nchannels)
581
+ # get the spikes for kilosort from the temp_wh.dat file if available.
582
+ dat = map_binary(binary_file, nchannels = binary_file_nchannels)
579
583
  # return as generator to avoid having to use huge amounts of memory.
580
584
  res = Parallel(backend='loky',n_jobs=n_jobs,return_as = 'generator')(delayed(get_spike_waveforms)(
581
585
  dat,
@@ -3,7 +3,7 @@ from .utils import BaseCompute
3
3
 
4
4
  class Suite2pCompute(BaseCompute):
5
5
  container = 'labdata-suite2p'
6
- cuda = False
6
+ cuda = True
7
7
  name = 'suite2p'
8
8
  url = 'http://github.com/mouseland/suite2p'
9
9
  def __init__(self,job_id, project = None, allow_s3 = None, **kwargs):
@@ -163,8 +163,9 @@ class Suite2pCompute(BaseCompute):
163
163
  return datasets
164
164
 
165
165
  def _compute(self):
166
- from numba import set_num_threads # control the number of threads used by suite2p
167
- set_num_threads(DEFAULT_N_JOBS)
166
+ # control the number of threads used by suite2p this colides with FISSA, for now don't limit.
167
+ #from numba import set_num_threads
168
+ #set_num_threads(DEFAULT_N_JOBS)
168
169
  from threadpoolctl import threadpool_limits
169
170
  import suite2p
170
171
  version = f'suite2p{suite2p.version}'
@@ -173,8 +174,11 @@ class Suite2pCompute(BaseCompute):
173
174
  version+= f';fissa{fissa.__version__}'
174
175
 
175
176
  seskeys = (self.schema.TwoPhoton() & self.dataset_key).proj().fetch(as_dict = True)
177
+ self.files_existed = True
178
+ if len((self.schema.File & (self.schema.TwoPhoton & seskeys)).check_if_files_local()[0]) == 0:
179
+ self.files_existed = False
176
180
  if len(seskeys):
177
- rec_files = (self.schema.File & (self.schema.TwoPhoton & seskeys)).get()
181
+ rec_filesorig = (self.schema.File & (self.schema.TwoPhoton & seskeys)).get()
178
182
  ses_par = (self.schema.TwoPhoton() & seskeys).fetch(as_dict = True)
179
183
  else:
180
184
  raise(NotImplemented("Only two-photon segmentation is implemented using this compute."))
@@ -188,20 +192,20 @@ class Suite2pCompute(BaseCompute):
188
192
  savedir = Path(prefs['scratch_path'])/f'suite2p_temporary_{rand}'
189
193
  savedir.mkdir(exist_ok=True,parents=True)
190
194
 
191
- ops = suite2p.default_ops()
195
+ ops = suite2p.default_settings()
192
196
  for p in self.parameters.keys():
193
197
  if p in ops.keys():
194
198
  ops[p] = self.parameters[p]
195
199
 
196
200
  # handle different file types
197
- sbx_files = [f for f in rec_files if str(f).endswith('.sbx')]
198
- zarr_files = [f for f in rec_files if str(f).endswith('.zarr.zip')]
201
+ sbx_files = [f for f in rec_filesorig if str(f).endswith('.sbx')]
202
+ zarr_files = [f for f in rec_filesorig if str(f).endswith('.zarr.zip')]
199
203
  if len(zarr_files):
200
204
  print('Converting zarr file to tiff.')
201
205
  rec_files = [savedir/'rawdata.tif']
202
206
  from tifffile import TiffWriter
203
207
  with TiffWriter(str(rec_files[0]), bigtiff=True, append=True) as tif:
204
- from tqdm import tqdm
208
+ from tqdm.auto import tqdm
205
209
  for ifile,zarrfile in enumerate(zarr_files):
206
210
  fd = open_zarr(zarrfile)
207
211
  for o,f in tqdm(chunk_indices(len(fd),512),
@@ -213,7 +217,7 @@ class Suite2pCompute(BaseCompute):
213
217
  ops['input_format'] = 'sbx'
214
218
  rec_files = sbx_files
215
219
  else:
216
- raise(ValueError(f'No valid file format found in {rec_files}.'))
220
+ raise(ValueError(f'No valid file format found in {rec_filesorig}.'))
217
221
 
218
222
  ops['fs'] = fs
219
223
  ops['nplanes'] = nplanes
@@ -221,15 +225,19 @@ class Suite2pCompute(BaseCompute):
221
225
  ops['fast_disk'] = str(savedir)
222
226
  ops['save_folder'] = str(savedir/'suite2p')
223
227
  ops['combined'] = False # do not combine planes
224
- db = {
225
- 'data_path': [s.parent for s in rec_files],
226
- }
228
+ ops['save_path0'] = str(savedir/'suite2p')
229
+ ops['data_path'] = [s.parent for s in rec_files]
230
+ ops['file_list'] = rec_files
231
+ ops['reg_tif'] = True
227
232
  for o in ops.keys():
228
- print(f' {o} : {ops[o]}')
229
-
233
+ print(f'[suite2p] {o} : {ops[o]}', flush=True)
234
+
230
235
  with threadpool_limits(limits=DEFAULT_N_JOBS, user_api='blas'):
231
- suite2p.run_s2p(ops=ops, db = db)
232
-
236
+ suite2p.run_s2p(settings=dict(fs = ops['fs'],
237
+ tau = ops['tau'],
238
+ diameter = ops['diameter'],
239
+ torch_device = ops['torch_device']),
240
+ db = ops)
233
241
  from labdata.stacks import compute_projections
234
242
  planefolders = natsorted([a.parent for a in savedir.rglob('*F.npy')
235
243
  if not 'combined' in str(a)])
@@ -268,6 +276,22 @@ class Suite2pCompute(BaseCompute):
268
276
  roi_pixels_values = s['lam']))
269
277
  # extract and denoise with FISSA!
270
278
  if self.parameters['use_fissa']:
279
+ # this is a work around. Save the binary to a tiff file and extract with low-memory mode
280
+ if str(rec_files[0]).endswith('rawdata.tif'):
281
+ if rec_files[0].exists():
282
+ os.unlink(rec_files[0])
283
+ print('Deleting the previously generated tiff file.')
284
+ #correctedfname = savedir/'motion_corrected'
285
+ #correctedfname.mkdir(exist_ok=True)
286
+ #from tifffile import imwrite
287
+ #from tqdm.auto import tqdm
288
+ #chunks = chunk_indices(len(binary),1056)
289
+ #chunkfiles = []
290
+ #for ichunk,(o,f) in tqdm(enumerate(chunks), total = len(chunks),
291
+ # desc = f"Concatenating motion corrected data:"):
292
+ # chunkfiles.append(correctedfname/'chunk{ichunk}.tif')
293
+ # imwrite(chunkfiles[-1],np.array(binary[o:f]).reshape((-1,*binary.shape[-2:])))
294
+ #with threadpool_limits(limits=DEFAULT_N_JOBS, user_api='blas'):
271
295
  dff,spks = extract_dff_fissa(stats,binary,dims,
272
296
  batch_size=ops['batch_size'],
273
297
  tau=ops['tau'],
@@ -286,8 +310,14 @@ class Suite2pCompute(BaseCompute):
286
310
  dims = [a for a in dims]))
287
311
  cellcount[isession] += len(rois) # count cells in all planes
288
312
  projdicts.extend([dict(planekey, proj_name = n,
289
- proj_im = pi) for n,pi in zip(['mean','std','max','lcorr'],
290
- [mean_proj,std_proj,max_proj,corr_proj])])
313
+ proj_im = pi) for n,pi in zip(['mean',
314
+ 'std',
315
+ 'max',
316
+ 'lcorr'],
317
+ [mean_proj,
318
+ std_proj,
319
+ max_proj,
320
+ corr_proj])])
291
321
  for icell,roi in enumerate(rois):
292
322
  roidicts.append(dict(planekey,**roi))
293
323
  if not dff is None:
@@ -320,23 +350,40 @@ class Suite2pCompute(BaseCompute):
320
350
  # CellSegmentation.MotionCorrection.insert1(dict(planekey,
321
351
  # motion_block_size = 0,
322
352
  # displacement = motion))
323
- self.schema.CellSegmentation.Projection.insert(projdicts,allow_direct_insert = True)
324
- self.schema.CellSegmentation.ROI.insert(roidicts ,allow_direct_insert = True)
353
+ self.schema.CellSegmentation.Projection.insert(projdicts,
354
+ allow_direct_insert = True)
355
+ self.schema.CellSegmentation.ROI.insert(roidicts,
356
+ allow_direct_insert = True)
325
357
  # Insert traces in parallel to prevent timeout errors from mysql
326
-
327
- parallel_insert(self.schema.schema_project,'CellSegmentation.Traces',tracesdicts, n_jobs = DEFAULT_N_JOBS,
328
- skip_duplicates = True, ignore_extra_fields = True)
329
- parallel_insert(self.schema.schema_project,'CellSegmentation.RawTraces',rawtracesdicts, n_jobs = DEFAULT_N_JOBS,
330
- skip_duplicates = True, ignore_extra_fields = True)
331
- parallel_insert(self.schema.schema_project,'CellSegmentation.Deconvolved',deconvdicts, n_jobs = DEFAULT_N_JOBS,
332
- skip_duplicates = True, ignore_extra_fields = True)
333
- self.schema.CellSegmentation.Selection.insert(selectiondicts,allow_direct_insert = True)
358
+ parallel_insert(self.schema.schema_project,
359
+ 'CellSegmentation.Traces',
360
+ tracesdicts,
361
+ n_jobs = DEFAULT_N_JOBS,
362
+ skip_duplicates = True,
363
+ ignore_extra_fields = True)
364
+ parallel_insert(self.schema.schema_project,
365
+ 'CellSegmentation.RawTraces',
366
+ rawtracesdicts,
367
+ n_jobs = DEFAULT_N_JOBS,
368
+ skip_duplicates = True,
369
+ ignore_extra_fields = True)
370
+ parallel_insert(self.schema.schema_project,
371
+ 'CellSegmentation.Deconvolved',
372
+ deconvdicts,
373
+ n_jobs = DEFAULT_N_JOBS,
374
+ skip_duplicates = True,
375
+ ignore_extra_fields = True)
376
+ self.schema.CellSegmentation.Selection.insert(selectiondicts,
377
+ allow_direct_insert = True)
334
378
  if not self.keep_intermediate:
335
- print(f'[{self.name} job] Removing the temporary folder.')
379
+ print(f'[{self.name} job] Removing the temporary folder {savedir}.')
336
380
  import shutil
337
381
  shutil.rmtree(savedir)
382
+ if not self.files_existed:
383
+ for f in rec_filesorig:
384
+ os.unlink(f)
338
385
  else:
339
- print(f'[{self.name} job] Kept the temporary folder {temporary_folder}.')
386
+ print(f'[{self.name} job] Kept the temporary folder {savedir}.')
340
387
 
341
388
 
342
389
  def extract_dff_fissa(stats,binary,dims,fs, batch_size,tau):
@@ -361,13 +408,14 @@ def extract_dff_fissa(stats,binary,dims,fs, batch_size,tau):
361
408
  import fissa
362
409
  from tqdm.auto import tqdm
363
410
  roiidx = np.where([r.sum()>4 for r in tqdm(rois,desc = 'Using only cells with more than 4 pixels') ])[0]
364
- fissa_res = fissa.Experiment(images=[binary],rois=[[rois[i] for i in roiidx]],
411
+ fissa_res = fissa.Experiment(images = [binary],
412
+ rois = [[rois[i] for i in roiidx]],
365
413
  ncores_separation = DEFAULT_N_JOBS,
366
- ncores_preparation = DEFAULT_N_JOBS)
414
+ ncores_preparation = DEFAULT_N_JOBS,
415
+ verbosity = 2)
367
416
 
368
417
  res = fissa_res.separate()
369
418
  fissa_res.calc_deltaf(freq=fs)
370
-
371
419
  dff = np.zeros((num_rois,
372
420
  len(fissa_res.deltaf_result[0][0][0])),dtype = 'float32')
373
421
  for iroi,r in zip(roiidx,fissa_res.deltaf_result):
@@ -146,7 +146,7 @@ def run_analysis(target, jobids, compute_obj, project = None):
146
146
 
147
147
  if container_file.exists():
148
148
 
149
- cmd += ' 2>&1 | ' + run_on_apptainer(container_file,
149
+ cmd += ' | ' + run_on_apptainer(container_file,
150
150
  command = f'labdata2 logpipe {jid}',
151
151
  dry_run = True)
152
152
  else:
@@ -223,7 +223,7 @@ def run_analysis(target, jobids, compute_obj, project = None):
223
223
  cuda = compute_obj.cuda,
224
224
  dry_run = True)
225
225
  # generate slurm cmd and launch
226
- cmd += ' 2>&1 | ' + run_on_apptainer(container_file,
226
+ cmd += ' | ' + run_on_apptainer(container_file,
227
227
  command = f'labdata2 logpipe {j}',
228
228
  dry_run = True)
229
229
  opts = dict()
@@ -0,0 +1,112 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import streamlit as st
4
+
5
+ STATES = ['COMPLETED', 'WORKING', 'WAITING', 'FAILED', 'CANCELED','ON SERVER']
6
+ STATE_COLORS = ['#01D186', '#48CAE4', '#F3A21C', '#FF5733', '#F3A21C','#F3A21C']
7
+
8
+ def _jobs_panel(table, status_field, target_field, cols, delete_key):
9
+
10
+ import datajoint as dj
11
+ import altair as alt
12
+
13
+ rows = table.fetch(as_dict=True)
14
+ if not rows:
15
+ st.write('### There are no jobs in the table')
16
+ return
17
+
18
+ df = pd.DataFrame(rows)[[c for c in cols if c in pd.DataFrame(rows).columns]]
19
+
20
+ # counts via aggr
21
+ counts = {r[status_field]: r['n'] for r in
22
+ dj.U(status_field).aggr(table, n='count(*)').fetch(as_dict=True)}
23
+ metric_cols = st.columns(len(STATES))
24
+ for col, t in zip(metric_cols, STATES):
25
+ col.metric(t, counts.get(t, 0))
26
+
27
+ # bar chart per target queue
28
+ df = df.set_index('job_id')
29
+ if target_field in df.columns:
30
+ targets = [d if d is not None else 'unknown' for d in df[target_field].values]
31
+ df[target_field] = targets
32
+ qcounts = []
33
+ for t in np.unique(targets):
34
+ status_vals = df[status_field].values
35
+ target_vals = df[target_field].values
36
+ for tt, c in zip(STATES, STATE_COLORS):
37
+ qcounts.append({'Task status': f'{tt}|{t}',
38
+ 'Jobs': int(sum(1 for a, tv in zip(status_vals, target_vals)
39
+ if tt in a and tv == t)),
40
+ 'color': c})
41
+ qcounts = [q for q in qcounts if q['Jobs'] > 0]
42
+ ch = (alt.Chart(pd.DataFrame(qcounts)).mark_bar().encode(
43
+ y='Task status:N',
44
+ x='Jobs:Q',
45
+ color=alt.Color('color:N').scale(None)
46
+ ).configure_axis(labelLimit=3000))
47
+ st.altair_chart(ch, width="stretch")
48
+
49
+ # filtered table
50
+ selected_states = st.multiselect('Select status:', STATES, default=STATES[1:], key=f'{delete_key}_filter')
51
+ idx = np.zeros(len(df), dtype=bool)
52
+ for s in selected_states:
53
+ idx = idx | np.array([s in a for a in df[status_field].values])
54
+
55
+ display_df = df.iloc[idx].copy()
56
+ display_df.insert(0, 'Select', False)
57
+ edited = st.data_editor(
58
+ display_df,
59
+ column_config={'Select': st.column_config.CheckboxColumn(required=True)},
60
+ disabled=[c for c in display_df.columns if c != 'Select'],
61
+ width="stretch",
62
+ key=f'{delete_key}_editor',
63
+ )
64
+ selected_jobs = edited[edited['Select'] == True].index.tolist()
65
+ if selected_jobs:
66
+ st.caption(f'{len(selected_jobs)} job(s) selected')
67
+ if st.button('Delete selected', type='primary', key=f'{delete_key}_btn'):
68
+ st.session_state[f'{delete_key}_pending'] = selected_jobs
69
+ if st.session_state.get(f'{delete_key}_pending'):
70
+ jobs_to_delete = st.session_state[f'{delete_key}_pending']
71
+ st.warning(f'Are you sure you want to delete {len(jobs_to_delete)} job(s)? This cannot be undone and scheduled jobs might fail.')
72
+ col_yes, col_no, _ = st.columns([1, 1, 4])
73
+ if col_yes.button('Yes, delete', type='primary', key=f'{delete_key}_yes'):
74
+ (table & [dict(job_id=j) for j in jobs_to_delete]).delete(safemode=False)
75
+ st.session_state.pop(f'{delete_key}_pending', None)
76
+ st.success(f'Deleted {len(jobs_to_delete)} job(s).')
77
+ st.rerun()
78
+ if col_no.button('Cancel', key=f'{delete_key}_no'):
79
+ st.session_state.pop(f'{delete_key}_pending', None)
80
+ st.rerun()
81
+
82
+
83
+ def compute_tab(schema=None):
84
+ if schema is None:
85
+ from labdata import load_project_schema
86
+ schema = load_project_schema()
87
+
88
+ tab_compute, tab_upload = st.tabs(['Compute tasks', 'Upload jobs'])
89
+
90
+ with tab_compute:
91
+ _jobs_panel(
92
+ table=schema.ComputeTask,
93
+ status_field='task_status',
94
+ target_field='task_target',
95
+ cols=['job_id', 'task_name', 'task_status', 'task_host', 'task_target',
96
+ 'task_waiting', 'task_log', 'subject_name', 'session_name',
97
+ 'task_starttime', 'task_endtime'],
98
+ delete_key='compute',
99
+ )
100
+
101
+ with tab_upload:
102
+ all_projects = st.toggle('Show all projects', value=False, key='upload_all_projects')
103
+ upload_table = schema.UploadJob if all_projects else schema.UploadJob & dict(project_name=schema.schema_project)
104
+ _jobs_panel(
105
+ table=upload_table,
106
+ status_field='job_status',
107
+ target_field='job_host',
108
+ cols=['job_id', 'job_status', 'job_host', 'job_rule', 'job_waiting',
109
+ 'job_log', 'subject_name', 'session_name', 'dataset_name',
110
+ 'upload_storage', 'job_starttime', 'job_endtime'],
111
+ delete_key='upload',
112
+ )