From d9e772cb7d4370b8dd34db2237ab340bf8bbf785 Mon Sep 17 00:00:00 2001 From: Micah Woodard <110491290+micahwoodard@users.noreply.github.com> Date: Wed, 3 Jan 2024 10:28:30 -0800 Subject: [PATCH] dev: updated session for new schema (#20) * dev: updated session for new schema * dev: added unit tests * dev: passes formatting tests * dev: reworked command to stimulus mapping * dev: passes tests * dev: remove extra test files * dev: remove saved intrument and session files from tests * dev: change format of class * dev: passes tests * dev: updated docstring and assert in test --- src/aind_metadata_mapper/fib/__init__.py | 1 + src/aind_metadata_mapper/fib/session.py | 205 +++++++ tests/resources/fib/000000_ophys_rig.json | 511 ++++++++++++++++++ tests/resources/fib/000000_ophys_session.json | 108 ++++ tests/resources/fib/example_from_teensy.txt | 10 + tests/test_fib.py | 115 ++++ 6 files changed, 950 insertions(+) create mode 100644 src/aind_metadata_mapper/fib/__init__.py create mode 100644 src/aind_metadata_mapper/fib/session.py create mode 100644 tests/resources/fib/000000_ophys_rig.json create mode 100644 tests/resources/fib/000000_ophys_session.json create mode 100644 tests/resources/fib/example_from_teensy.txt create mode 100644 tests/test_fib.py diff --git a/src/aind_metadata_mapper/fib/__init__.py b/src/aind_metadata_mapper/fib/__init__.py new file mode 100644 index 00000000..2379107d --- /dev/null +++ b/src/aind_metadata_mapper/fib/__init__.py @@ -0,0 +1 @@ +"""Maps Fiber photometry metadata into a session model""" diff --git a/src/aind_metadata_mapper/fib/session.py b/src/aind_metadata_mapper/fib/session.py new file mode 100644 index 00000000..2d94b231 --- /dev/null +++ b/src/aind_metadata_mapper/fib/session.py @@ -0,0 +1,205 @@ +"""Module to write valid OptoStim and Subject schemas""" + +import datetime +import re +from dataclasses import dataclass +from pathlib import Path + +from aind_data_schema.core.session import ( + DetectorConfig, + FiberConnectionConfig, + LightEmittingDiodeConfig, + Session, + Stream, +) +from aind_data_schema.models.modalities import Modality +from aind_data_schema.models.stimulus import ( + OptoStimulation, + PulseShape, + StimulusEpoch, +) + +from aind_metadata_mapper.core import BaseEtl + + +@dataclass(frozen=True) +class ParsedInformation: + """RawImageInfo gets parsed into this data""" + + teensy_str: str + experiment_data: dict + start_datetime: datetime + + +class FIBEtl(BaseEtl): + """This class contains the methods to write OphysScreening data""" + + _dictionary_mapping = { + "o": "OptoStim10Hz", + "p": "OptoStim20Hz", + "q": "OptoStim5Hz", + } + + # Define regular expressions to extract the values + command_regex = re.compile(r"Received command (\w)") + frequency_regex = re.compile(r"OptoStim\s*([0-9.]+)") + trial_regex = re.compile(r"OptoTrialN:\s*([0-9.]+)") + pulse_regex = re.compile(r"PulseW\(um\):\s*([0-9.]+)") + duration_regex = re.compile(r"OptoDuration\(s\):\s*([0-9.]+)") + interval_regex = re.compile(r"OptoInterval\(s\):\s*([0-9.]+)") + base_regex = re.compile(r"OptoBase\(s\):\s*([0-9.]+)") + + def __init__( + self, + output_directory: Path, + teensy_str: str, + experiment_data: dict, + start_datetime: datetime, + input_source: str = "", + ): + """ + Class constructor for Base etl class. + Parameters + ---------- + input_source : Union[str, PathLike] + Can be a string or a Path + output_directory : Path + The directory where to save the json files. + user_settings: UserSettings + Variables for a particular session + """ + super().__init__(input_source, output_directory) + self.teensy_str = teensy_str + self.experiment_data = experiment_data + self.start_datetime = start_datetime + + def _transform(self, extracted_source: ParsedInformation) -> Session: + """ + Parses params from teensy string and creates ophys session model + Parameters + ---------- + extracted_source : ParsedInformation + + Returns + ------- + Session + + """ + # Process data from dictionary keys + + experiment_data = extracted_source.experiment_data + string_to_parse = extracted_source.teensy_str + start_datetime = extracted_source.start_datetime + + labtracks_id = experiment_data["labtracks_id"] + iacuc_protocol = experiment_data["iacuc"] + rig_id = experiment_data["rig_id"] + experimenter_full_name = experiment_data["experimenter_name"] + mouse_platform_name = experiment_data["mouse_platform_name"] + active_mouse_platform = experiment_data["active_mouse_platform"] + light_source_list = experiment_data["light_source"] + detector_list = experiment_data["detectors"] + fiber_connections_list = experiment_data["fiber_connections"] + session_type = experiment_data["session_type"] + notes = experiment_data["notes"] + + # Use regular expressions to extract the values + frequency_match = re.search(self.frequency_regex, string_to_parse) + trial_match = re.search(self.trial_regex, string_to_parse) + pulse_match = re.search(self.pulse_regex, string_to_parse) + duration_match = re.search(self.duration_regex, string_to_parse) + interval_match = re.search(self.interval_regex, string_to_parse) + base_match = re.search(self.base_regex, string_to_parse) + command_match = re.search(self.command_regex, string_to_parse) + + # Store the float values as variables + frequency = int(frequency_match.group(1)) + trial_num = int(trial_match.group(1)) + pulse_width = int(pulse_match.group(1)) + opto_duration = float(duration_match.group(1)) + opto_interval = float(interval_match.group(1)) + opto_base = float(base_match.group(1)) + + # maps stimulus_name from command + command = command_match.group(1) + stimulus_name = self._dictionary_mapping.get(command, "") + + # create opto stim instance + opto_stim = OptoStimulation( + stimulus_name=stimulus_name, + pulse_shape=PulseShape.SQUARE, + pulse_frequency=frequency, + number_pulse_trains=trial_num, + pulse_width=pulse_width, + pulse_train_duration=opto_duration, + pulse_train_interval=opto_interval, + baseline_duration=opto_base, + fixed_pulse_train_interval=True, # TODO: Check this is right + ) + + # create stimulus presentation instance + experiment_duration = ( + opto_base + opto_duration + (opto_interval * trial_num) + ) + end_datetime = start_datetime + datetime.timedelta( + seconds=experiment_duration + ) + stimulus_epochs = StimulusEpoch( + stimulus=opto_stim, + stimulus_start_time=start_datetime, + stimulus_end_time=end_datetime, + ) + + # create light source instance + light_source = [] + for ls in light_source_list: + diode = LightEmittingDiodeConfig(**ls) + light_source.append(diode) + + # create detector instance + detectors = [] + for d in detector_list: + camera = DetectorConfig(**d) + detectors.append(camera) + + # create fiber connection instance + fiber_connections = [] + for fc in fiber_connections_list: + cord = FiberConnectionConfig(**fc) + fiber_connections.append(cord) + + data_stream = [ + Stream( + stream_start_time=start_datetime, + stream_end_time=end_datetime, + light_sources=light_source, + stream_modalities=[Modality.FIB], + mouse_platform_name=mouse_platform_name, + active_mouse_platform=active_mouse_platform, + detectors=detectors, + fiber_connections=fiber_connections, + ) + ] + + # and finally, create ophys session + ophys_session = Session( + stimulus_epochs=[stimulus_epochs], + subject_id=labtracks_id, + iacuc_protocol=iacuc_protocol, + session_start_time=start_datetime, + session_end_time=end_datetime, + rig_id=rig_id, + experimenter_full_name=experimenter_full_name, + session_type=session_type, + notes=notes, + data_streams=data_stream, + ) + return ophys_session + + def _extract(self) -> ParsedInformation: + """Extract metadata from fib session.""" + return ParsedInformation( + teensy_str=self.teensy_str, + experiment_data=self.experiment_data, + start_datetime=self.start_datetime, + ) diff --git a/tests/resources/fib/000000_ophys_rig.json b/tests/resources/fib/000000_ophys_rig.json new file mode 100644 index 00000000..131e90bc --- /dev/null +++ b/tests/resources/fib/000000_ophys_rig.json @@ -0,0 +1,511 @@ +{ + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/ophys/ophys_rig.py", + "schema_version": "0.10.0", + "instrument_id": null, + "modification_date": "2023-12-19", + "instrument_type": "Other", + "manufacturer": { + "name": "Custom", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "temperature_control": null, + "humidity_control": null, + "optical_tables": [], + "enclosure": null, + "objectives": [], + "detectors": [ + { + "device_type": "Detector", + "name": "FLIR CMOS for Green Ch", + "serial_number": "21396991", + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": "BFS-U3-20S40M", + "path_to_cad": null, + "notes": "4*4 binning (additive), 200*200pixels, Mono16, Gain2", + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "air", + "immersion": "air", + "chroma": null, + "sensor_width": null, + "sensor_height": null, + "size_unit": "pixel", + "bit_depth": null, + "bin_mode": "none", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel" + }, + { + "device_type": "Detector", + "name": "FLIR CMOS for Red Ch", + "serial_number": "21396990", + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": "BFS-U3-20S40M", + "path_to_cad": null, + "notes": "4*4 binning (additive), 200*200pixels, Mono16, Gain2", + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "air", + "immersion": "air", + "chroma": null, + "sensor_width": null, + "sensor_height": null, + "size_unit": "pixel", + "bit_depth": null, + "bin_mode": "none", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel" + } + ], + "light_sources": [ + { + "device_type": "Light emitting diode", + "name": "470nm LED", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": "M470F3", + "path_to_cad": null, + "notes": null, + "wavelength": 470, + "wavelength_unit": "nanometer" + }, + { + "device_type": "Light emitting diode", + "name": "415nm LED", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": "M415F3", + "path_to_cad": null, + "notes": null, + "wavelength": 415, + "wavelength_unit": "nanometer" + }, + { + "device_type": "Light emitting diode", + "name": "565nm LED", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": "M565F3", + "path_to_cad": null, + "notes": null, + "wavelength": 565, + "wavelength_unit": "nanometer" + } + ], + "lenses": [ + { + "device_type": "Lens", + "name": "Nikon 10x Objective", + "serial_number": "128022336", + "manufacturer": { + "name": "Other", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "CFI Plan Apochromat Lambda D 10x", + "path_to_cad": null, + "notes": null, + "focal_length": "4", + "focal_length_unit": "millimeter", + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": null + } + ], + "fluorescence_filters": [ + { + "device_type": "Filter", + "name": "Green emission bandpass filter", + "serial_number": null, + "manufacturer": { + "name": "Semrock", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF01-520/35-25", + "path_to_cad": null, + "notes": null, + "filter_type": "Band pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Red emission bandpass filter", + "serial_number": null, + "manufacturer": { + "name": "Other", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF01-600/37-25", + "path_to_cad": null, + "notes": null, + "filter_type": "Band pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Emission Dichroic", + "serial_number": null, + "manufacturer": { + "name": "Semrock", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF562-Di03-25x36", + "path_to_cad": null, + "notes": null, + "filter_type": "Dichroic", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "dual-edge standard epi-fluorescence dichroic beamsplitter", + "serial_number": null, + "manufacturer": { + "name": "Semrock", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF493/574-Di01-25x36", + "path_to_cad": null, + "notes": "493/574 nm BrightLine® dual-edge standard epi-fluorescence dichroic beamsplitter", + "filter_type": "Multiband", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Excitation filter 410", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": "FB410-10", + "path_to_cad": null, + "notes": null, + "filter_type": "Band pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Excitation filter 470", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": null, + "path_to_cad": null, + "notes": null, + "filter_type": "Band pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Excitation filter 560", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": "FB560-10", + "path_to_cad": null, + "notes": null, + "filter_type": "Band pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "450nm, 25.2 x 35.6mm, Dichroic Longpass Filter", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": "#69-898", + "path_to_cad": null, + "notes": null, + "filter_type": "Long pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "500nm, 25.2 x 35.6mm, Dichroic Longpass Filter", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": "#69-899", + "path_to_cad": null, + "notes": null, + "filter_type": "Long pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": "millimeter", + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + } + ], + "motorized_stages": [], + "scanning_stages": [], + "additional_devices": [ + { + "device_type": "AdditionalImagingDevice", + "name": "Rotary encoder#1", + "serial_number": "01", + "manufacturer": { + "name": "Other", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "notes": null, + "type": "Other" + }, + { + "device_type": "AdditionalImagingDevice", + "name": "Rotary encoder#2", + "serial_number": null, + "manufacturer": { + "name": "Other", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "notes": null, + "type": "Other" + } + ], + "calibration_date": null, + "calibration_data": null, + "com_ports": [], + "daqs": [ + { + "device_type": "DAQ Device", + "name": "USB DAQ", + "serial_number": null, + "manufacturer": { + "name": "National Instruments", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "026exqw73" + }, + "model": "USB-6009", + "path_to_cad": null, + "notes": "To record behavior events and licks via AnalogInput node in Bonsai", + "data_interface": "USB", + "computer_name": "W10DTJK7N0M3", + "channels": [ + { + "channel_name": "AI0", + "device_name": "Rotary encoder#1", + "channel_type": "Analog Input", + "port": 0, + "channel_index": 0, + "sample_rate": "1000", + "sample_rate_unit": "hertz", + "event_based_sampling": false + }, + { + "channel_name": "AI1", + "device_name": "Rotary encoder#2", + "channel_type": "Analog Input", + "port": 1, + "channel_index": 1, + "sample_rate": "1000", + "sample_rate_unit": "hertz", + "event_based_sampling": false + } + ] + } + ], + "notes": "fip_ophys_rig_428_FIP1_1" +} \ No newline at end of file diff --git a/tests/resources/fib/000000_ophys_session.json b/tests/resources/fib/000000_ophys_session.json new file mode 100644 index 00000000..4d92dac3 --- /dev/null +++ b/tests/resources/fib/000000_ophys_session.json @@ -0,0 +1,108 @@ +{ + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", + "schema_version": "0.1.0", + "experimenter_full_name": [ + "john doe" + ], + "session_start_time": "1999-10-04T00:00:00", + "session_end_time": "1999-10-04T00:00:48", + "session_type": "Foraging_Photometry", + "iacuc_protocol": "2115", + "rig_id": "ophys_rig", + "calibrations": [], + "maintenance": [], + "subject_id": "000000", + "animal_weight_prior": null, + "animal_weight_post": null, + "weight_unit": "gram", + "data_streams": [ + { + "stream_start_time": "1999-10-04T00:00:00", + "stream_end_time": "1999-10-04T00:00:48", + "daq_names": [], + "camera_names": [], + "light_sources": [ + { + "device_type": "LightEmittingDiode", + "name": "470nm LED", + "excitation_power": "0.02", + "excitation_power_unit": "milliwatt" + }, + { + "device_type": "LightEmittingDiode", + "name": "415nm LED", + "excitation_power": "0.02", + "excitation_power_unit": "milliwatt" + }, + { + "device_type": "LightEmittingDiode", + "name": "565nm LED", + "excitation_power": "0.02", + "excitation_power_unit": "milliwatt" + } + ], + "ephys_modules": [], + "stick_microscopes": [], + "manipulator_modules": [], + "detectors": [ + { + "name": "Hamamatsu Camera", + "exposure_time": "10", + "exposure_time_unit": "millisecond", + "trigger_type": "Internal" + } + ], + "fiber_connections": [ + { + "patch_cord_name": "Patch Cord A", + "patch_cord_output_power": "40", + "output_power_unit": "microwatt", + "fiber_name": "Fiber A" + } + ], + "fiber_modules": [], + "ophys_fovs": [], + "slap_fovs": null, + "stack_parameters": null, + "stimulus_device_names": [], + "mouse_platform_name": "Disc", + "active_mouse_platform": false, + "stream_modalities": [ + { + "name": "Fiber photometry", + "abbreviation": "fib" + } + ], + "notes": null + } + ], + "stimulus_epochs": [ + { + "stimulus": { + "stimulus_type": "OptoStimulation", + "stimulus_name": "OptoStim10Hz", + "pulse_shape": "Square", + "pulse_frequency": 10, + "pulse_frequency_unit": "hertz", + "number_pulse_trains": 2, + "pulse_width": 5000, + "pulse_width_unit": "millisecond", + "pulse_train_duration": "2.0", + "pulse_train_duration_unit": "second", + "fixed_pulse_train_interval": true, + "pulse_train_interval": "18.0", + "pulse_train_interval_unit": "second", + "baseline_duration": "10.0", + "baseline_duration_unit": "second", + "other_parameters": {}, + "notes": null + }, + "stimulus_start_time": "1999-10-04T00:00:00", + "stimulus_end_time": "1999-10-04T00:00:48" + } + ], + "reward_delivery": null, + "reward_consumed_total": null, + "reward_consumed_unit": "microliter", + "notes": "brabrabrabra...." +} \ No newline at end of file diff --git a/tests/resources/fib/example_from_teensy.txt b/tests/resources/fib/example_from_teensy.txt new file mode 100644 index 00000000..3a7e2a0f --- /dev/null +++ b/tests/resources/fib/example_from_teensy.txt @@ -0,0 +1,10 @@ +LabtrackID:000000 DateTime: 2023-03-08-20-05-05 +o +Received command o with pulsewidth value 5000 + +OptoStim10Hz_____________ +OptoTrialN: 2 +PulseW(um): 5000 +OptoDuration(s): 2 +OptoInterval(s): 18 +OptoBase(s): 10 \ No newline at end of file diff --git a/tests/test_fib.py b/tests/test_fib.py new file mode 100644 index 00000000..9ab61e9c --- /dev/null +++ b/tests/test_fib.py @@ -0,0 +1,115 @@ +"""Tests parsing of session information from fib rig.""" + +import json +import os +import unittest +from datetime import datetime +from pathlib import Path + +from aind_data_schema.core.session import Session + +from aind_metadata_mapper.fib.session import FIBEtl + +RESOURCES_DIR = ( + Path(os.path.dirname(os.path.realpath(__file__))) / "resources" / "fib" +) +EXAMPLE_MD_PATH = RESOURCES_DIR / "example_from_teensy.txt" +EXPECTED_SESSION = RESOURCES_DIR / "000000_ophys_session.json" + + +class TestSchemaWriter(unittest.TestCase): + """Test methods in SchemaWriter class.""" + + @classmethod + def setUpClass(cls): + """Load record object and user settings before running tests.""" + + cls.example_experiment_data = { + "labtracks_id": "000000", + "experimenter_name": [ + "john doe", + ], + "notes": "brabrabrabra....", # + "experimental_mode": "c", + "save_dir": "", + "iacuc": "2115", + "rig_id": "ophys_rig", + "COMPort": "COM3", + "mouse_platform_name": "Disc", + "active_mouse_platform": False, + "light_source": [ + { + "name": "470nm LED", + "excitation_power": 0.020, + "excitation_power_unit": "milliwatt", + }, + { + "name": "415nm LED", + "excitation_power": 0.020, + "excitation_power_unit": "milliwatt", + }, + { + "name": "565nm LED", + "excitation_power": 0.020, # Set 0 for unused StimLED + "excitation_power_unit": "milliwatt", + }, + ], # default light source + "detectors": [ + { + "name": "Hamamatsu Camera", + "exposure_time": 10, + "trigger_type": "Internal", + } + ], + "fiber_connections": [ + { + "patch_cord_name": "Patch Cord A", + "patch_cord_output_power": 40, + "output_power_unit": "microwatt", + "fiber_name": "Fiber A", + } + ], + "session_type": "Foraging_Photometry", + } + + with open(EXAMPLE_MD_PATH, "r") as f: + raw_md_contents = f.read() + with open(EXPECTED_SESSION, "r") as f: + expected_session_contents = Session(**json.load(f)) + + cls.expected_session = expected_session_contents + cls.example_metadata = raw_md_contents + + def test_extract(self): + """Tests that the teensy response and experiment + data is extracted correctly""" + + etl_job1 = FIBEtl( + output_directory=RESOURCES_DIR, + teensy_str=self.example_metadata, + experiment_data=self.example_experiment_data, + start_datetime=datetime(1999, 10, 4), + ) + parsed_info = etl_job1._extract() + self.assertEqual(self.example_metadata, parsed_info.teensy_str) + self.assertEqual( + self.example_experiment_data, parsed_info.experiment_data + ) + self.assertEqual(datetime(1999, 10, 4), parsed_info.start_datetime) + + def test_transform(self): + """Tests that the teensy response maps correctly to ophys session.""" + + etl_job1 = FIBEtl( + output_directory=RESOURCES_DIR, + teensy_str=self.example_metadata, + experiment_data=self.example_experiment_data, + start_datetime=datetime(1999, 10, 4), + ) + parsed_info = etl_job1._extract() + actual_session = etl_job1._transform(parsed_info) + self.assertEqual(self.expected_session, actual_session) + + +if __name__ == "__main__": + unittest.main()