1
1
import importlib
2
2
import inspect
3
3
from datetime import datetime
4
- from glob import glob
5
4
from pathlib import Path
6
5
from typing import List , Tuple
7
6
8
- import cv2
9
7
import datajoint as dj
10
8
import numpy as np
11
- from element_interface .utils import find_full_path , find_root_directory
9
+ from element_interface .utils import find_full_path , find_root_directory , memoized_result
12
10
13
11
schema = dj .schema ()
14
12
@@ -185,6 +183,7 @@ def key_source(self):
185
183
186
184
def make (self , key ):
187
185
"""Populates the RecordingInfo table."""
186
+ import cv2
188
187
189
188
file_paths = (VideoRecording .File & key ).fetch ("file_path" )
190
189
@@ -301,33 +300,37 @@ def make(self, key):
301
300
# update processing_output_dir
302
301
FacemapTask .update1 ({** key , "facemap_output_dir" : output_dir .as_posix ()})
303
302
303
+ output_dir = find_full_path (get_facemap_root_data_dir (), output_dir )
304
+
304
305
if task_mode == "trigger" :
305
306
from facemap .process import run as facemap_run
306
307
307
308
params = (FacemapTask & key ).fetch1 ("facemap_params" )
308
309
310
+ valid_args = inspect .getfullargspec (facemap_run ).args
311
+ params = {k : v for k , v in params .items () if k in valid_args }
312
+
309
313
video_files = (FacemapTask * VideoRecording .File & key ).fetch ("file_path" )
314
+ # video files are sequentially acquired, not simultaneously
310
315
video_files = [
311
- [
312
- find_full_path (get_facemap_root_data_dir (), video_file ).as_posix ()
313
- for video_file in video_files
314
- ]
316
+ [find_full_path (get_facemap_root_data_dir (), video_file ).as_posix ()]
317
+ for video_file in video_files
315
318
]
316
319
317
- output_dir = find_full_path (get_facemap_root_data_dir (), output_dir )
318
- facemap_run (
319
- video_files ,
320
- sbin = params ["sbin" ],
321
- proc = params ,
322
- savepath = output_dir .as_posix (),
323
- motSVD = params ["motSVD" ],
324
- movSVD = params ["movSVD" ],
325
- )
320
+ @memoized_result (uniqueness_dict = params , output_directory = output_dir )
321
+ def _run_facemap_process ():
322
+ facemap_run (
323
+ filenames = video_files ,
324
+ savepath = output_dir .as_posix (),
325
+ ** params ,
326
+ )
326
327
327
- _ , creation_time = get_loader_result (key , FacemapTask )
328
- key = {** key , "processing_time" : creation_time }
328
+ _run_facemap_process ()
329
329
330
- self .insert1 (key )
330
+ results_proc_fp = next (output_dir .glob ("*_proc.npy" ))
331
+ creation_time = datetime .fromtimestamp (results_proc_fp .stat ().st_ctime )
332
+
333
+ self .insert1 ({** key , "processing_time" : creation_time })
331
334
332
335
333
336
@schema
@@ -358,54 +361,54 @@ class Region(dj.Part):
358
361
359
362
definition = """
360
363
-> master
361
- roi_no : int # Region number
364
+ roi_no : int # Region number (roi_no=0 is FullSVD if exists)
362
365
---
363
- roi_name='' : varchar(16) # user-friendly name of the roi
364
- xrange : longblob # 1d np.array - x pixel indices
365
- yrange : longblob # 1d np.array - y pixel indices
366
- xrange_bin : longblob # 1d np.array - binned x pixel indices
367
- yrange_bin : longblob # 1d np.array - binned y pixel indices
368
- motion : longblob # 1d np.array - absolute motion energies (nframes)
366
+ roi_name='' : varchar(16) # user-friendly name of the roi
367
+ xrange=null : longblob # 1d np.array - x pixel indices
368
+ yrange=null : longblob # 1d np.array - y pixel indices
369
+ xrange_bin=null : longblob # 1d np.array - binned x pixel indices
370
+ yrange_bin=null : longblob # 1d np.array - binned y pixel indices
371
+ motion=null : longblob # 1d np.array - absolute motion energies (nframes)
369
372
"""
370
373
371
374
class MotionSVD (dj .Part ):
372
375
"""Components of the SVD from motion video.
373
376
374
377
Attributes:
375
378
master.Region (foreign key): Primary key from FacialSignal.Region.
376
- pc_no (int): Principle component (PC) number.
377
- singular_value (float, optional): singular value corresponding to the PC .
378
- motmask (longblob): PC (y, x).
379
- projection (longblob): projections onto the principle component (nframes).
379
+ component_id (int): component number.
380
+ singular_value (float, optional): singular value corresponding to the component .
381
+ motmask (longblob): (y, x).
382
+ projection (longblob): projections onto the component (nframes).
380
383
"""
381
384
382
385
definition = """
383
386
-> master.Region
384
- pc_no : int # principle component (PC) number
387
+ component_id : int # component number
385
388
---
386
- singular_value=null : float # singular value corresponding to the PC
387
- motmask : longblob # PC (y, x)
388
- projection : longblob # projections onto the principle component (nframes)
389
+ singular_value=null : float # singular value corresponding to the component
390
+ motmask : longblob # (y, x)
391
+ projection : longblob # projections onto the component (nframes)
389
392
"""
390
393
391
394
class MovieSVD (dj .Part ):
392
395
"""Components of the SVD from movie video.
393
396
394
397
Attributes:
395
398
master.Region (foreign key): Primary key of the FacialSignal.Region table.
396
- pc_no (int): principle component (PC) number.
397
- singular_value (float, optional): Singular value corresponding to the PC .
398
- movmask (longblob): PC (y, x)
399
- projection (longblob): Projections onto the principle component (nframes).
399
+ component_id (int): component number.
400
+ singular_value (float, optional): Singular value corresponding to the component .
401
+ movmask (longblob): (y, x)
402
+ projection (longblob): Projections onto the component (nframes).
400
403
"""
401
404
402
405
definition = """
403
406
-> master.Region
404
- pc_no : int # principle component (PC) number
407
+ component_id : int # component number
405
408
---
406
- singular_value=null : float # singular value corresponding to the PC
407
- movmask : longblob # PC (y, x)
408
- projection : longblob # projections onto the principle component (nframes)
409
+ singular_value=null : float # singular value corresponding to the component
410
+ movmask : longblob # (y, x)
411
+ projection : longblob # projections onto the component (nframes)
409
412
"""
410
413
411
414
class Summary (dj .Part ):
@@ -414,121 +417,127 @@ class Summary(dj.Part):
414
417
Attributes:
415
418
master (foreign key): Primary key from FacialSignal.
416
419
sbin (int): Spatial bin size.
417
- avgframe (longblob): 2d np.array - average binned frame.
418
- avgmotion (longblob): 2d nd.array - average binned motion frame.
420
+ avgframe (longblob): 2d np.array (y, x) - average binned frame
421
+ avgmotion (longblob): 2d nd.array (y, x) - average binned motion frame
419
422
"""
420
423
421
424
definition = """
422
425
-> master
423
426
---
424
427
sbin : int # spatial bin size
425
- avgframe : longblob # 2d np.array - average binned frame
426
- avgmotion : longblob # 2d nd.array - average binned motion frame
428
+ avgframe : longblob # 2d np.array (y, x) - average binned frame
429
+ avgmotion : longblob # 2d nd.array (y, x) - average binned motion frame
427
430
"""
428
431
429
432
def make (self , key ):
430
433
"""Populates the FacialSignal table by transferring the results from default
431
434
Facemap outputs to the database."""
432
435
433
- dataset , _ = get_loader_result (key , FacemapTask )
434
- # Only motion SVD region type is supported.
435
- dataset ["rois" ] = [x for x in dataset ["rois" ] if x ["rtype" ] == "motion SVD" ]
436
+ output_dir = (FacemapTask & key ).fetch1 ("facemap_output_dir" )
437
+ output_dir = find_full_path (get_facemap_root_data_dir (), output_dir )
438
+ results_proc_fp = next (output_dir .glob ("*_proc.npy" ))
439
+ dataset = np .load (results_proc_fp , allow_pickle = True ).item ()
436
440
437
- self .insert1 (key )
441
+ region_entries , motion_svd_entries , movie_svd_entries = [], [], []
442
+ motions = dataset ["motion" ].copy ()
438
443
439
- self .Region .insert (
440
- [
444
+ motion_svd_rois = []
445
+ if dataset ["fullSVD" ]:
446
+ region_entries .append (
441
447
dict (
442
448
key ,
443
- roi_no = i ,
444
- xrange = dataset ["rois" ][i ]["xrange" ],
445
- yrange = dataset ["rois" ][i ]["yrange" ],
446
- xrange_bin = (
447
- dataset ["rois" ][i ]["xrange_bin" ]
448
- if "xrange_bin" in dataset ["rois" ][i ]
449
- else None
450
- ),
451
- yrange_bin = (
452
- dataset ["rois" ][i ]["yrange_bin" ]
453
- if "yrange_bin" in dataset ["rois" ][i ]
454
- else None
455
- ),
456
- motion = dataset ["motion" ][i + 1 ],
449
+ roi_no = 0 ,
450
+ roi_name = "FullSVD" ,
451
+ xrange = np .arange (dataset ["Lx" ][0 ]),
452
+ yrange = np .arange (dataset ["Ly" ][0 ]),
453
+ motion = motions .pop (),
454
+ )
455
+ )
456
+ motion_svd_rois .append (0 )
457
+ # Region
458
+ if dataset ["rois" ] is not None :
459
+ for i , roi in enumerate (dataset ["rois" ]):
460
+ roi_no = i + int (dataset ["fullSVD" ])
461
+ roi_name = f"{ roi ['rtype' ]} _{ roi ['iROI' ]} "
462
+ if roi ["rtype" ] == "motion SVD" :
463
+ motion_svd_rois .append (roi_no )
464
+ motion = motions .pop ()
465
+ else :
466
+ motion = None
467
+ region_entries .append (
468
+ dict (
469
+ key ,
470
+ roi_no = roi_no ,
471
+ roi_name = roi_name ,
472
+ xrange = roi ["xrange" ],
473
+ yrange = roi ["yrange" ],
474
+ xrange_bin = roi .get ("xrange_bin" ),
475
+ yrange_bin = roi .get ("yrange_bin" ),
476
+ motion = motion ,
477
+ )
457
478
)
458
- for i in range (len (dataset ["rois" ]))
459
- if dataset ["rois" ][i ]["rtype" ] == "motion SVD"
460
- ]
461
- )
462
-
463
479
# MotionSVD
464
480
if any (np .any (x ) for x in dataset .get ("motSVD" , [False ])):
465
- entry = [
466
- dict (
467
- key ,
468
- roi_no = roi_no ,
469
- pc_no = i ,
470
- singular_value = (
471
- dataset ["motSv" ][roi_no ][i ] if "motSv" in dataset else None
472
- ),
473
- motmask = dataset ["motMask_reshape" ][roi_no + 1 ][:, :, i ],
474
- projection = dataset ["motSVD" ][roi_no + 1 ][i ],
481
+ for roi_idx , roi_no in enumerate (motion_svd_rois ):
482
+ roi_idx += int (
483
+ not dataset ["fullSVD" ]
484
+ ) # skip the first entry if fullSVD is False
485
+ motSVD = dataset ["motSVD" ][roi_idx ]
486
+ motMask = dataset ["motMask_reshape" ][roi_idx ]
487
+ motSv = (
488
+ dataset ["motSv" ][roi_idx ]
489
+ if "motSv" in dataset
490
+ else np .full (motSVD .shape [- 1 ], np .nan )
491
+ )
492
+ motion_svd_entries .extend (
493
+ [
494
+ dict (
495
+ key ,
496
+ roi_no = roi_no ,
497
+ component_id = idx ,
498
+ singular_value = s ,
499
+ motmask = m ,
500
+ projection = p ,
501
+ )
502
+ for idx , (s , m , p ) in enumerate (zip (motSv , motMask , motSVD ))
503
+ ]
475
504
)
476
- for roi_no in range (len (dataset ["rois" ]))
477
- for i in range (dataset ["motSVD" ][roi_no + 1 ].shape [1 ])
478
- ]
479
- self .MotionSVD .insert (entry )
480
-
481
505
# MovieSVD
482
506
if any (np .any (x ) for x in dataset .get ("movSVD" , [False ])):
483
- entry = [
484
- dict (
485
- key ,
486
- roi_no = roi_no ,
487
- pc_no = i ,
488
- singular_value = (
489
- dataset ["movSv" ][roi_no ][i ] if "movSv" in dataset else None
490
- ),
491
- movmask = dataset ["movMask_reshape" ][roi_no + 1 ][:, :, i ],
492
- projection = dataset ["movSVD" ][roi_no + 1 ][i ],
507
+ for roi_idx , roi_no in enumerate (motion_svd_rois ):
508
+ roi_idx += int (
509
+ not dataset ["fullSVD" ]
510
+ ) # skip the first entry if fullSVD is False
511
+ movSVD = dataset ["movSVD" ][roi_idx ]
512
+ movMask = dataset ["movMask_reshape" ][roi_idx ]
513
+ movSv = (
514
+ dataset ["movSv" ][roi_idx ]
515
+ if "movSv" in dataset
516
+ else np .full (movSVD .shape [- 1 ], np .nan )
517
+ )
518
+ motion_svd_entries .extend (
519
+ [
520
+ dict (
521
+ key ,
522
+ roi_no = roi_no ,
523
+ component_id = idx ,
524
+ singular_value = s ,
525
+ motmask = m ,
526
+ projection = p ,
527
+ )
528
+ for idx , (s , m , p ) in enumerate (zip (movSv , movMask , movSVD ))
529
+ ]
493
530
)
494
- for roi_no in range (len (dataset ["rois" ]))
495
- for i in range (dataset ["movSVD" ][roi_no + 1 ].shape [1 ])
496
- ]
497
- self .MovieSVD .insert (entry )
498
531
499
- # Summary
532
+ self .insert1 (key )
533
+ self .Region .insert (region_entries )
534
+ self .MotionSVD .insert (motion_svd_entries )
535
+ self .MovieSVD .insert (movie_svd_entries )
500
536
self .Summary .insert1 (
501
537
dict (
502
538
key ,
503
539
sbin = dataset ["sbin" ],
504
- avgframe = dataset ["avgframe" ][ 0 ],
505
- avgmotion = dataset ["avgmotion" ][ 0 ],
540
+ avgframe = dataset ["avgframe_reshape" ],
541
+ avgmotion = dataset ["avgmotion_reshape" ],
506
542
)
507
543
)
508
-
509
-
510
- # ---------------- HELPER FUNCTIONS ----------------
511
-
512
-
513
- def get_loader_result (
514
- key : dict , table : dj .user_tables .TableMeta
515
- ) -> Tuple [np .array , datetime ]:
516
- """Retrieve the facemap analysis results.
517
-
518
- Args:
519
- key (dict): A primary key for an entry in the provided table.
520
- table (dj.Table): DataJoint user table from which loaded results are retrieved (i.e. FacemapTask).
521
-
522
- Returns:
523
- loaded_dataset (np.array): The results of the facemap analysis.
524
- creation_time (datetime): Date and time that the results files were created.
525
- """
526
- output_dir = (table & key ).fetch1 ("facemap_output_dir" )
527
-
528
- output_path = find_full_path (get_facemap_root_data_dir (), output_dir )
529
- output_file = glob (output_path .as_posix () + "/*_proc.npy" )[0 ]
530
-
531
- loaded_dataset = np .load (output_file , allow_pickle = True ).item ()
532
- creation_time = datetime .fromtimestamp (Path (output_file ).stat ().st_ctime )
533
-
534
- return loaded_dataset , creation_time
0 commit comments