11import logging
2+ import warnings
23from pathlib import Path
3- from typing import Iterable
4+ from typing import Iterable , Tuple
45
6+ import h5py
57import numpy as np
68import pandas as pd
79import datetime
1012import pynwb
1113from pynwb .base import TimeSeries , Images
1214from pynwb .behavior import BehavioralEvents
13- from pynwb import ProcessingModule
15+ from pynwb import ProcessingModule , NWBFile
1416from pynwb .image import ImageSeries , GrayscaleImage , IndexSeries
1517from pynwb .ophys import DfOverF , ImageSegmentation , OpticalChannel , Fluorescence
1618
1719import allensdk .brain_observatory .roi_masks as roi
20+ from allensdk .brain_observatory .nwb .nwb_utils import (get_column_name )
1821from allensdk .brain_observatory .running_speed import RunningSpeed
1922from allensdk .brain_observatory import dict_to_indexed_array
2023from allensdk .brain_observatory .behavior .image_api import Image
2528
2629log = logging .getLogger ("allensdk.brain_observatory.nwb" )
2730
31+ CELL_SPECIMEN_COL_DESCRIPTIONS = {
32+ 'cell_specimen_id' : 'Unified id of segmented cell across experiments (after'
33+ ' cell matching)' ,
34+ 'height' : 'Height of ROI in pixels' ,
35+ 'width' : 'Width of ROI in pixels' ,
36+ 'mask_image_plane' : 'Which image plane an ROI resides on. Overlapping ROIs '
37+ 'are stored on different mask image planes.' ,
38+ 'max_correction_down' : 'Max motion correction in down direction in pixels' ,
39+ 'max_correction_left' : 'Max motion correction in left direction in pixels' ,
40+ 'max_correction_up' : 'Max motion correction in up direction in pixels' ,
41+ 'max_correction_right' : 'Max motion correction in right direction in '
42+ 'pixels' ,
43+ 'valid_roi' : 'Indicates if cell classification found the ROI to be a cell '
44+ 'or not' ,
45+ 'x' : 'x position of ROI in Image Plane in pixels (top left corner)' ,
46+ 'y' : 'y position of ROI in Image Plane in pixels (top left corner)'
47+ }
48+
49+ def check_nwbfile_version (nwbfile_path : str ,
50+ desired_minimum_version : str ,
51+ warning_msg : str ):
52+
53+ with h5py .File (nwbfile_path , 'r' ) as f :
54+ # nwb 2.x files store version as an attribute
55+ try :
56+ nwb_version = str (f .attrs ["nwb_version" ]).split ("." )
57+ except KeyError :
58+ # nwb 1.x files store version as dataset
59+ try :
60+ nwb_version = str (f ["nwb_version" ][...].astype (str ))
61+ # Stored in the form: `NWB-x.y.z`
62+ nwb_version = nwb_version .split ("-" )[1 ].split ("." )
63+ except (KeyError , IndexError ):
64+ nwb_version = None
65+
66+ if nwb_version is None :
67+ warnings .warn (f"'{ nwbfile_path } ' doesn't appear to be a valid "
68+ f"Neurodata Without Borders (*.nwb) format file as "
69+ f"neither a 'nwb_version' field nor dataset could "
70+ f"be found!" )
71+ else :
72+ if tuple (nwb_version ) < tuple (desired_minimum_version .split ("." )):
73+ warnings .warn (warning_msg )
74+
2875
2976def read_eye_dlc_tracking_ellipses (input_path : Path ) -> dict :
3077 """Reads eye tracking ellipse fit data from an h5 file.
@@ -424,10 +471,13 @@ def add_stimulus_presentations(nwbfile, stimulus_table, tag='stimulus_time_inter
424471 """
425472 stimulus_table = stimulus_table .copy ()
426473 ts = nwbfile .modules ['stimulus' ].get_data_interface ('timestamps' )
427- stimulus_names = stimulus_table ['stimulus_name' ].unique ()
474+ possible_names = {'stimulus_name' , 'image_name' }
475+ stimulus_name_column = get_column_name (stimulus_table .columns ,
476+ possible_names )
477+ stimulus_names = stimulus_table [stimulus_name_column ].unique ()
428478
429479 for stim_name in sorted (stimulus_names ):
430- specific_stimulus_table = stimulus_table [stimulus_table ['stimulus_name' ] == stim_name ]
480+ specific_stimulus_table = stimulus_table [stimulus_table [stimulus_name_column ] == stim_name ]
431481 # Drop columns where all values in column are NaN
432482 cleaned_table = specific_stimulus_table .dropna (axis = 1 , how = 'all' )
433483 # For columns with mixed strings and NaNs, fill NaNs with 'N/A'
@@ -447,6 +497,7 @@ def add_stimulus_presentations(nwbfile, stimulus_table, tag='stimulus_time_inter
447497
448498 for row in cleaned_table .itertuples (index = False ):
449499 row = row ._asdict ()
500+
450501 presentation_interval .add_interval (** row , tags = tag , timeseries = ts )
451502
452503 nwbfile .add_time_intervals (presentation_interval )
@@ -709,7 +760,28 @@ def add_task_parameters(nwbfile, task_parameters):
709760 nwbfile .add_lab_meta_data (nwb_task_parameters )
710761
711762
712- def add_cell_specimen_table (nwbfile , cell_specimen_table ):
763+ def add_cell_specimen_table (nwbfile : NWBFile ,
764+ cell_specimen_table : pd .DataFrame ):
765+ """
766+ This function takes the cell specimen table and writes the ROIs
767+ contained within. It writes these to a new NWB imaging plane
768+ based off the previously supplied metadata
769+ Parameters
770+ ----------
771+ nwbfile: NWBFile
772+ this is the in memory NWBFile currently being written to which ROI data
773+ is added
774+ cell_specimen_table: pd.DataFrame
775+ this is the DataFrame containing the cells segmented from a ophys
776+ experiment, stored in json file and loaded.
777+ example: /home/nicholasc/projects/allensdk/allensdk/test/
778+ brain_observatory/behavior/cell_specimen_table_789359614.json
779+
780+ Returns
781+ -------
782+ nwbfile: NWBFile
783+ The altered in memory NWBFile object that now has a specimen table
784+ """
713785 cell_roi_table = cell_specimen_table .reset_index ().set_index ('cell_roi_id' )
714786
715787 # Device:
@@ -769,12 +841,21 @@ def add_cell_specimen_table(nwbfile, cell_specimen_table):
769841 description = "Segmented rois" ,
770842 imaging_plane = imaging_plane )
771843
772- for c in [c for c in cell_roi_table .columns if c not in ['id' , 'mask_matrix' ]]:
773- plane_segmentation .add_column (c , c )
774-
844+ for col_name in cell_roi_table .columns :
845+ # the columns 'image_mask', 'pixel_mask', and 'voxel_mask' are already defined
846+ # in the nwb.ophys::PlaneSegmentation Object
847+ if col_name not in ['id' , 'mask_matrix' , 'image_mask' , 'pixel_mask' , 'voxel_mask' ]:
848+ # This builds the columns with name of column and description of column
849+ # both equal to the column name in the cell_roi_table
850+ plane_segmentation .add_column (col_name ,
851+ CELL_SPECIMEN_COL_DESCRIPTIONS .get (col_name ,
852+ "No Description Available" ))
853+
854+ # go through each roi and add it to the plan segmentation object
775855 for cell_roi_id , row in cell_roi_table .iterrows ():
776856 sub_mask = np .array (row .pop ('image_mask' ))
777- curr_roi = roi .create_roi_mask (fov_width , fov_height , [(fov_width - 1 ), 0 , (fov_height - 1 ), 0 ], roi_mask = sub_mask )
857+ curr_roi = roi .create_roi_mask (fov_width , fov_height , [(fov_width - 1 ), 0 , (fov_height - 1 ), 0 ],
858+ roi_mask = sub_mask )
778859 mask = curr_roi .get_mask_plane ()
779860 csid = row .pop ('cell_specimen_id' )
780861 row ['cell_specimen_id' ] = - 1 if csid is None else csid
0 commit comments