11import importlib
22import inspect
33from datetime import datetime
4- from glob import glob
54from pathlib import Path
65from typing import List , Tuple
76
8- import cv2
97import datajoint as dj
108import 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
1210
1311schema = dj .schema ()
1412
@@ -185,6 +183,7 @@ def key_source(self):
185183
186184 def make (self , key ):
187185 """Populates the RecordingInfo table."""
186+ import cv2
188187
189188 file_paths = (VideoRecording .File & key ).fetch ("file_path" )
190189
@@ -301,33 +300,37 @@ def make(self, key):
301300 # update processing_output_dir
302301 FacemapTask .update1 ({** key , "facemap_output_dir" : output_dir .as_posix ()})
303302
303+ output_dir = find_full_path (get_facemap_root_data_dir (), output_dir )
304+
304305 if task_mode == "trigger" :
305306 from facemap .process import run as facemap_run
306307
307308 params = (FacemapTask & key ).fetch1 ("facemap_params" )
308309
310+ valid_args = inspect .getfullargspec (facemap_run ).args
311+ params = {k : v for k , v in params .items () if k in valid_args }
312+
309313 video_files = (FacemapTask * VideoRecording .File & key ).fetch ("file_path" )
314+ # video files are sequentially acquired, not simultaneously
310315 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
315318 ]
316319
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+ )
326327
327- _ , creation_time = get_loader_result (key , FacemapTask )
328- key = {** key , "processing_time" : creation_time }
328+ _run_facemap_process ()
329329
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 })
331334
332335
333336@schema
@@ -358,54 +361,54 @@ class Region(dj.Part):
358361
359362 definition = """
360363 -> master
361- roi_no : int # Region number
364+ roi_no : int # Region number (roi_no=0 is FullSVD if exists)
362365 ---
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)
369372 """
370373
371374 class MotionSVD (dj .Part ):
372375 """Components of the SVD from motion video.
373376
374377 Attributes:
375378 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).
380383 """
381384
382385 definition = """
383386 -> master.Region
384- pc_no : int # principle component (PC) number
387+ component_id : int # component number
385388 ---
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)
389392 """
390393
391394 class MovieSVD (dj .Part ):
392395 """Components of the SVD from movie video.
393396
394397 Attributes:
395398 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).
400403 """
401404
402405 definition = """
403406 -> master.Region
404- pc_no : int # principle component (PC) number
407+ component_id : int # component number
405408 ---
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)
409412 """
410413
411414 class Summary (dj .Part ):
@@ -414,121 +417,127 @@ class Summary(dj.Part):
414417 Attributes:
415418 master (foreign key): Primary key from FacialSignal.
416419 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
419422 """
420423
421424 definition = """
422425 -> master
423426 ---
424427 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
427430 """
428431
429432 def make (self , key ):
430433 """Populates the FacialSignal table by transferring the results from default
431434 Facemap outputs to the database."""
432435
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 ()
436440
437- self .insert1 (key )
441+ region_entries , motion_svd_entries , movie_svd_entries = [], [], []
442+ motions = dataset ["motion" ].copy ()
438443
439- self .Region .insert (
440- [
444+ motion_svd_rois = []
445+ if dataset ["fullSVD" ]:
446+ region_entries .append (
441447 dict (
442448 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+ )
457478 )
458- for i in range (len (dataset ["rois" ]))
459- if dataset ["rois" ][i ]["rtype" ] == "motion SVD"
460- ]
461- )
462-
463479 # MotionSVD
464480 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+ ]
475504 )
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-
481505 # MovieSVD
482506 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+ ]
493530 )
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 )
498531
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 )
500536 self .Summary .insert1 (
501537 dict (
502538 key ,
503539 sbin = dataset ["sbin" ],
504- avgframe = dataset ["avgframe" ][ 0 ],
505- avgmotion = dataset ["avgmotion" ][ 0 ],
540+ avgframe = dataset ["avgframe_reshape" ],
541+ avgmotion = dataset ["avgmotion_reshape" ],
506542 )
507543 )
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