dtype='float', required=False), NWBAttributeSpec( name='microdrive', doc='whether a microdrive was used (0: not used, 1: used)', dtype='int'), NWBAttributeSpec(name='microdrive_lead', doc='um/turn', dtype='float', required=False), NWBAttributeSpec(name='microdrive_id', doc='id of microdrive', dtype='int', required=False), NWBAttributeSpec(name='help', doc='help', dtype='text', value='Information about optical fiber') ]) ns_builder = NWBNamespaceBuilder(doc=namespace + ' extensions', name=namespace, version='1.0', author='Ben Dichter', contact='*****@*****.**') specs = (subject, optical_fiber) for spec in specs: ns_builder.add_spec(ext_source, spec) ns_builder.export(ns_path)
def main(): ''' Create the specification using PyNWB helpers. Now we will create the specification ('draw up the blueprints') for the Frank Lab extension. The main entries in a spec file are Groups, Attributes and Datasets; PyNWB provides helpers that allow us to generate our spec files using these components. We primarily use the [NWBGroupSpec](https://pynwb.readthedocs.io/en/stable/pynwb.spec.html#pynwb.spec.NWBGroupSpec) # noqa class to help us create valid NWB groups. An NWB group is basically just a container that can have things like a name, attributes, datasets, and even nested groups. In fl_extension.py (where we implement this extension in Python), each of these groups will get its own Python class. We add items within a group using [NWBAttributeSpec]() and [NWBDatasetSpec](). An NWB attribute is just what it sounds like: a short piece of metadata defining some attribute, such as a "help" text. An NWB dataset is also pretty self-explanatory: it's just some data (numbers, text, etc.). As an example, the cell below describes the representation of a behavioral task, and will generate the following lines in the franklab.extensions.yaml file: ``` - neurodata_type_def: Task neurodata_type_inc: NWBDataInterface doc: a behavioral task attributes: - name: name dtype: text doc: the name of this task - name: description dtype: text doc: description of this task - name: help dtype: text doc: help doc value: help value ``` --------------------------- Task (i.e. free exploration, W-alternation, sleep) ------ A Task consists simply of two attributes: - a name (i.e. W-alternation, sleep, free exploration) - a description of the task Note that Task inherits from something called NWBDataInterface. NWBDataInterface is a group in PyNWB representing basically any kind of data, and which we can store in the NWB file in a ProcessingModule. See create_franklab_nwbfile.ipynb for a discussion of Processing Modules. --------------------------- ''' task = NWBGroupSpec(neurodata_type_def='Task', neurodata_type_inc='NWBDataInterface', doc='a behavioral task', attributes=[ NWBAttributeSpec( name='name', doc='the name of this task', dtype='text'), NWBAttributeSpec( name='description', doc='description of this task', dtype='text'), NWBAttributeSpec( name='help', doc='help doc', dtype='text', value='Behavioral Task')]) # --------------------------- # Apparatus (i.e. tracks, mazes, sleep boxes, arenas) # -------- # We represent the topology of apparatuses using a graph representation # (i.e. nodes and edges) # - Nodes represent a component of an apparatus that you'd like the ability # to refer # (e.g. W-track arms, reward wells, novel object, open field components) # - Edges represent the topological connectivity of the nodes # (i.e. there should be an edge between the left track arm and the left # reward well) # In addition, all nodes will contain x/y coordinates that allow us to # reconstruct not just # the topology, but also the spatial geometry, of the apparatuses. # # Below, we will first define the nodes and edges. Finally we will define # the Apparatus itself # as a container that holds the nodes and edges as sub-groups. # --------------------------- # Node # ----- # Abstract represention for any kind of node in the topological graph # We won't actually implement abstract nodes. Rather this is a parent group # from which our more specific types of nodes will inherit. Note that NWB # specifications have inheritance. # The quantity '*' means that we can have any number (0 or more) nodes. node = NWBGroupSpec( neurodata_type_def='Node', neurodata_type_inc='NWBDataInterface', doc='nodes in the graph', quantity='*', attributes=[NWBAttributeSpec(name='name', doc='the name of this node', dtype='text'), NWBAttributeSpec(name='help', doc='help doc', dtype='text', value='Apparatus Node')]) # Edge # ------- # Edges between any two nodes in the graph. # An edge's only dataset is the name (string) of the two nodes that the # edge connects # Note that we don't actually include the nodes themselves, just their # names, in an edge. edge = NWBGroupSpec( neurodata_type_def='Edge', neurodata_type_inc='NWBDataInterface', doc='edges in the graph', quantity='*', datasets=[ NWBDatasetSpec( doc='names of the nodes this edge connects', name='edge_nodes', dtype='text', dims=['first_node_name|second_node_name'], shape=[2])], attributes=[ NWBAttributeSpec( name='help', doc='help doc', dtype='text', value='Apparatus Edge')]) # Point Node # ----------- # A node that represents a single 2D point in space (e.g. reward well, # novel object location) point_node = NWBGroupSpec( neurodata_type_def='PointNode', neurodata_type_inc='Node', doc='node representing a point in 2D space', quantity='*', datasets=[NWBDatasetSpec(doc='x/y coordinate of this 2D point', name='coords', dtype='float', dims=['num_coords', 'x_vals|y_vals'], shape=[1, 2])], attributes=[NWBAttributeSpec(name='help', doc='help doc', dtype='text', value='Apparatus Point')]) # Segment Node # ------------- # A node that represents a linear segement in 2D space, defined by its # start and end points # (e.g. a single arm of W-track maze) segment_node = NWBGroupSpec( neurodata_type_def='SegmentNode', neurodata_type_inc='Node', doc=('node representing a 2D linear segment defined by its start and' 'end points'), quantity='*', datasets=[ NWBDatasetSpec(doc=('x/y coordinates of the start and end points ' 'of this segment'), name='coords', dtype='float', dims=['num_coords', 'x_vals|y_vals'], shape=[2, 2])], attributes=[ NWBAttributeSpec(name='help', doc='help doc', dtype='text', value='Apparatus Segment')]) # Polygon Node # ------------- # A node that represents a polygon area (e.g. open field, sleep box) # A polygon is defined by its external vertices and, optionally, by # any interior points of interest (e.g. interior wells, objects) polygon_node = NWBGroupSpec( neurodata_type_def='PolygonNode', neurodata_type_inc='Node', doc='node representing a 2D polygon area', quantity='*', datasets=[ NWBDatasetSpec( doc='x/y coordinates of the exterior points of this polygon', name='coords', dtype='float', dims=['num_coords', 'x_vals|y_vals'], shape=['null', 2]), NWBDatasetSpec( doc='x/y coordinates of interior points inside this polygon', name='interior_coords', dtype='float', quantity='?', dims=['num_coords', 'x_vals|y_vals'], shape=['null', 2])], attributes=[ NWBAttributeSpec(name='help', doc='help doc', dtype='text', value='Apparatus Polygon')]) # Apparatus # ------------- # Finally, we define the apparatus itself. # It is has two sub-groups: nodes and edges. apparatus = NWBGroupSpec( neurodata_type_def='Apparatus', neurodata_type_inc='NWBDataInterface', doc='a graph of nodes and edges', quantity='*', groups=[node, edge], attributes=[ NWBAttributeSpec(name='name', doc='the name of this apparatus', dtype='text'), NWBAttributeSpec(name='help', doc='help doc', dtype='text', value='Behavioral Apparatus')]) # ### Save the extension specification # The specification consists of two YAML (.yaml) files: one for the actual # blueprint and one for the namespace. In a world with many blueprints, we # need namespaces to effectively categorize/store them, like the drawers of # an architect's filing cabinet. namespace_builder = NWBNamespaceBuilder( f'{namespace} extensions', namespace, version='0.1.0') namespace_builder.add_spec(extension_filename, apparatus) namespace_builder.add_spec(extension_filename, task) namespace_builder.add_spec(extension_filename, point_node) namespace_builder.add_spec(extension_filename, segment_node) namespace_builder.add_spec(extension_filename, polygon_node) # Bug: NamespaceBuilder.add_spec creates the .extensions.yaml file in the # current directory (it errors if you pass in a file path containing '/' # to add_spec, above.) old_cwd = os.getcwd() os.chdir(yaml_dir) namespace_builder.export(namespace_path) os.chdir(old_cwd)
csd_virtual_electrode_vertical_positions = NWBDatasetSpec(name="virtual_electrode_y_positions", doc="Virtual vertical positions of electrodes from which CSD was calculated", attributes=[unit_attr], dtype='float32', shape=(None,)) csd_virtual_electrode_horizontal_positions = NWBDatasetSpec(name="virtual_electrode_x_positions", doc="Virtual horizontal positions of electrodes from which CSD was calculated", attributes=[unit_attr], dtype='float32', shape=(None,)) ecephys_csd_ext = NWBGroupSpec( doc="A group containing current source density (CSD) data and virtual electrode locations", groups=[csd_timeseries_group], datasets=[csd_virtual_electrode_horizontal_positions, csd_virtual_electrode_vertical_positions], neurodata_type_def="EcephysCSD", neurodata_type_inc="NWBDataInterface" ) ext_source = "ndx-aibs-ecephys.extension.yaml" ns_builder.add_spec(ext_source, ecephys_probe_ext) ns_builder.add_spec(ext_source, ecephys_egroup_ext) ns_builder.add_spec(ext_source, ecephys_specimen_ext) ns_builder.add_spec(ext_source, ecephys_eye_tracking_rig_metadata_ext) ns_builder.add_spec(ext_source, ecephys_csd_ext) namespace_path = "ndx-aibs-ecephys.namespace.yaml" ns_builder.export(namespace_path)
def generate_extended_schema(): # set up silverlab namespace ns_builder = NWBNamespaceBuilder( 'Extensions for acousto-optic lens data', 'silverlab_extended_schema', 'Silver lab data extension to NWB format for acousto-optic lens experiments', version='0.4') ns_builder.include_type('LabMetaData', namespace='core') ns_builder.include_type('TwoPhotonSeries', namespace='core') # define attributes Silver lab extension cycle_time_attr = NWBAttributeSpec( name='cycle_time', doc='time in seconds for the microscope to acquire all ROIs once ' 'and return to its starting position', dtype='float') cycles_per_trial_attr = NWBAttributeSpec( name='cycles_per_trial', doc='how many microscope cycles occur in each experimental trial', dtype="int") imaging_mode_attr = NWBAttributeSpec( name='imaging_mode', doc='the acquisition mode for the experiment; ' 'pointing = single-voxel ROIs, ' 'miniscan = 2d rectangular ROIs, ' 'volume = 3d cuboid ROIs', dtype='text') frame_size_attr = NWBAttributeSpec( name='frame_size', doc='the 2d imaging frame size in voxels', shape=(2, ), dtype='int') silverlab_api_version_attr = NWBAttributeSpec( name='silverlab_api_version', doc='For potential future backwards compatibility, ' 'store the \'version\' of this API that created the file.', dtype='text') labview_version_attr = NWBAttributeSpec( name='labview_version', doc='The version of LabVIEW the data came from', dtype='text', required=False) pockels_column_names_attr = NWBAttributeSpec( name='columns', doc='column names for the zplane pockels dataset', shape=(4, ), dtype='text') # define datasets for Silver lab extensions zplane_pockels_ds = NWBDatasetSpec( doc='pockels data set, recording calibration data ' 'for focusing at different z-planes in four columns: ' 'Z offset from focal plane (micrometres), ' 'normalised Z, ' '\'Pockels\' i.e. laser power in %, ' 'and z offset for drive motors', name='pockels', shape=(None, 4), attributes=[pockels_column_names_attr], neurodata_type_def='ZplanePockelsDataset') # define groups for Silver lab extensions silverlab_optophys_specs = NWBGroupSpec( doc='A place to store Silver lab specific optophysiology data', attributes=[ cycle_time_attr, cycles_per_trial_attr, frame_size_attr, imaging_mode_attr ], datasets=[zplane_pockels_ds], neurodata_type_def='SilverLabOptophysiology', neurodata_type_inc='LabMetaData') silverlab_metadata_specs = NWBGroupSpec( doc='A place to store Silver lab specific metadata', attributes=[silverlab_api_version_attr, labview_version_attr], neurodata_type_def='SilverLabMetaData', neurodata_type_inc='LabMetaData', ) # dimensions ordered as t, x, y [, z], like the TimeSeries data itself silverlab_pixel_time_offset_data = NWBDatasetSpec( doc='A datastructure to hold time offsets for pixels. The' 'time offsets are the acquisition time of each pixel ' 'relative to a starting time. The starting time is the ' 'start of the cycle for pre-2018 LabView versions, ' 'and the start of the trial for new versions.', name='pixel_time_offsets', shape=[(None, None), (None, None, None), (None, None, None, None)], neurodata_type_def='PixelTimeOffsets') silverlab_roi_image_specs = NWBGroupSpec( doc='An extension to PyNWB\'s TwoPhotonSeries class, designed to hold ' 'pixels from an ROI as well as the PixelTimeOffsets for them.', datasets=[silverlab_pixel_time_offset_data], neurodata_type_def='ROISeriesWithPixelTimeOffsets', neurodata_type_inc='TwoPhotonSeries') # export as schema extension ext_source = 'silverlab.ophys.yaml' ns_builder.add_spec(ext_source, silverlab_optophys_specs) ext_source = 'silverlab.metadata.yaml' ns_builder.add_spec(ext_source, silverlab_metadata_specs) ext_source = 'silverlab.roi.yaml' ns_builder.add_spec(ext_source, silverlab_roi_image_specs) ns_builder.export('silverlab.namespace.yaml')