diff --git a/src/cohen_lab_to_nwb/abbyleung_conversion.py b/src/cohen_lab_to_nwb/abbyleung_conversion.py deleted file mode 100644 index 4678299..0000000 --- a/src/cohen_lab_to_nwb/abbyleung_conversion.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import uuid -from datetime import datetime - -import numpy as np -from neuroconv.tools.nwb_helpers import configure_and_write_nwbfile -from pynwb import NWBFile, TimeSeries -from pynwb.image import ImageSeries -from pynwb.ogen import OptogeneticSeries -from scipy.io import loadmat - - -mat_file = "/Users/bendichter/data/CohenU01_data/Cohen Lab/SS40851_1A_Activation.mat" -video_dir = "/Users/bendichter/data/CohenU01_data/Cohen Lab/videos" - -def convert_session(mat_file, video_dir, metadata): - nwbfile = NWBFile( - identifier=str(uuid.uuid4()), - **metadata["NWBFile"] - ) - - # add videos - for video_file in os.listdir(video_dir): - - image_series = ImageSeries( - name=os.path.splitext(video_file)[0], - external_file=[video_file], - starting_frame=[0], - format="external", - starting_time=np.nan, - rate=8000, - ) - - nwbfile.add_acquisition(image_series) - - # add behavior - var_name = os.path.splitext(os.path.split(mat_file)[1])[0] - mat_out = loadmat(mat_file, simplify_cells=True) - for i, x in enumerate(mat_out[var_name]): - for ts_name, info in metadata["Behavior"].items(): - nwbfile.add_acquisition( - TimeSeries( - name=f"{ts_name}{i:03d}", - description=info["description"], - data=x[ts_name], - unit=info["unit"], - rate=8000, - starting_time=np.nan, - ) - ) - - #optogenetic stimulation - device = nwbfile.create_device(**metadata["Ogen"]["Device"]) - - ogen_site = nwbfile.create_ogen_site( - device=device, - **metadata["Ogen"]["OgenSite"], - ) - - ogen_series = OptogeneticSeries( - name="OptogeneticSeries", - data=, # watts - site=ogen_site, - rate=30.0, # Hz - ) - - configure_and_write_nwbfile(nwbfile=nwbfile, backend="hdf5", output_filepath="test2.nwb") - - -if __name__ == "__main__": - convert_session(mat_file, video_dir, session_start_time=datetime.now()) - diff --git a/src/cohen_lab_to_nwb/assets/experiment_diagram.excalidraw b/src/cohen_lab_to_nwb/assets/experiment_diagram.excalidraw new file mode 100644 index 0000000..b486efd --- /dev/null +++ b/src/cohen_lab_to_nwb/assets/experiment_diagram.excalidraw @@ -0,0 +1,595 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "pJf_E41_iA7lJXQ9X2aOL", + "type": "line", + "x": 287.06663065592454, + "y": 120.62781575520779, + "width": 285.6550340951326, + "height": 6.173223107862668, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 164133741, + "version": 143, + "versionNonce": 783635853, + "isDeleted": false, + "boundElements": null, + "updated": 1739823416373, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 285.6550340951326, + -6.173223107862668 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "5LVvvo1-I7yjxezlMpfdo", + "type": "line", + "x": 203.70470906235334, + "y": 238.13239541519982, + "width": 653.1894378960734, + "height": 13.005678728883254, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1551189859, + "version": 310, + "versionNonce": 531409507, + "isDeleted": false, + "boundElements": null, + "updated": 1739825309603, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 653.1894378960734, + -13.005678728883254 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "OzUGQP_5XU9I7NRRWo-9X", + "type": "text", + "x": 111.76021219068274, + "y": 271.0722890486094, + "width": 196.7198028564453, + "height": 25, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1616525507, + "version": 125, + "versionNonce": 458079245, + "isDeleted": false, + "boundElements": null, + "updated": 1739824969181, + "link": null, + "locked": false, + "text": "Movies at 8000 Hz", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17, + "containerId": null, + "originalText": "Movies at 8000 Hz", + "lineHeight": 1.25 + }, + { + "id": "-WcmtxR-8uYoabmVFpuu8", + "type": "text", + "x": -29.297453322399022, + "y": 88.35399716311747, + "width": 317.439697265625, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 583314509, + "version": 183, + "versionNonce": 990325571, + "isDeleted": false, + "boundElements": null, + "updated": 1739823427466, + "link": null, + "locked": false, + "text": "Matlab Struct (also 8000 Hz?)", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17, + "containerId": null, + "originalText": "Matlab Struct (also 8000 Hz?)", + "lineHeight": 1.25 + }, + { + "id": "NUxbUhwf3ByuZktyXGe20", + "type": "rectangle", + "x": 378.40936603874417, + "y": -13.280949083732253, + "width": 174.387859553506, + "height": 305.76290448978773, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1564847203, + "version": 265, + "versionNonce": 2002083277, + "isDeleted": false, + "boundElements": [ + { + "id": "leeLFuYs1IjNZmZZhVnPj", + "type": "arrow" + } + ], + "updated": 1739828309508, + "link": null, + "locked": false + }, + { + "id": "MAHdWbwp7qoyOXuVzTAW9", + "type": "text", + "x": 379.72750173214865, + "y": -47.21368829517647, + "width": 118.25985717773438, + "height": 25, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1253504013, + "version": 98, + "versionNonce": 1389199235, + "isDeleted": false, + "boundElements": null, + "updated": 1739823407216, + "link": null, + "locked": false, + "text": "Opto Stimuli", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17, + "containerId": null, + "originalText": "Opto Stimuli", + "lineHeight": 1.25 + }, + { + "id": "4ji0UAaVRL9CvShxypg0n", + "type": "text", + "x": 648.8386262542613, + "y": 281.82725857788785, + "width": 140.37985229492188, + "height": 50, + "angle": 0, + "strokeColor": "#2f9e44", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 839505069, + "version": 164, + "versionNonce": 1203944931, + "isDeleted": false, + "boundElements": null, + "updated": 1739824971750, + "link": null, + "locked": false, + "text": "~ 80 seconds\n2301 frames", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 42, + "containerId": null, + "originalText": "~ 80 seconds\n2301 frames", + "lineHeight": 1.25 + }, + { + "id": "RPl7qClWw40DWu-L-Vw93", + "type": "text", + "x": 575.2589590788192, + "y": 74.38771326668245, + "width": 150.13983154296875, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1574411341, + "version": 50, + "versionNonce": 1273760589, + "isDeleted": false, + "boundElements": null, + "updated": 1739823421227, + "link": null, + "locked": false, + "text": "401 timestamps", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17, + "containerId": null, + "originalText": "401 timestamps", + "lineHeight": 1.25 + }, + { + "id": "vNsAra3gPrAt7J5cA-1NZ", + "type": "text", + "x": 267.21249941449855, + "y": 138.13825730943165, + "width": 60.91993713378906, + "height": 25, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1630827501, + "version": 11, + "versionNonce": 1810496931, + "isDeleted": false, + "boundElements": null, + "updated": 1739823343472, + "link": null, + "locked": false, + "text": "-10 ms", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 17, + "containerId": null, + "originalText": "-10 ms", + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 77, + "versionNonce": 1557902819, + "isDeleted": false, + "id": "9qncERfCUYlnFO7SAlqh2", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 574.8942988005241, + "y": 128.21933332430837, + "strokeColor": "#e03131", + "backgroundColor": "#a5d8ff", + "width": 59.63993835449219, + "height": 25, + "seed": 90637005, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1739823419392, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "50 ms", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "50 ms", + "lineHeight": 1.25, + "baseline": 17 + }, + { + "id": "oYV8H-m-Yrl-w8IzkqRUS", + "type": "text", + "x": 286.98198163178154, + "y": -186.84902372559088, + "width": 488.8994445800781, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2088754861, + "version": 258, + "versionNonce": 20913517, + "isDeleted": false, + "boundElements": [ + { + "id": "leeLFuYs1IjNZmZZhVnPj", + "type": "arrow" + } + ], + "updated": 1739828292112, + "link": null, + "locked": false, + "text": "Where is the zero?\nFly triggers two lasers interesect on the middle\nphotodiodes. Interesection of two beams used to\ntrigger camera recording", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 92, + "containerId": null, + "originalText": "Where is the zero?\nFly triggers two lasers interesect on the middle\nphotodiodes. Interesection of two beams used to\ntrigger camera recording", + "lineHeight": 1.25 + }, + { + "id": "leeLFuYs1IjNZmZZhVnPj", + "type": "arrow", + "x": 354.66577652934114, + "y": -76.26538548770264, + "width": 7.369497010850807, + "height": 353.93065083063135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1844714723, + "version": 676, + "versionNonce": 1635275629, + "isDeleted": false, + "boundElements": null, + "updated": 1739828309508, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7.369497010850807, + 353.93065083063135 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "oYV8H-m-Yrl-w8IzkqRUS", + "focus": 0.7251896222310626, + "gap": 10.583638237888238 + }, + "endBinding": { + "elementId": "NUxbUhwf3ByuZktyXGe20", + "focus": -1.1777614275315698, + "gap": 16.374092498552244 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "xC8-V-ULStsilsAT2bexv", + "type": "text", + "x": 110.64636676425727, + "y": 358.8683905182487, + "width": 780.8992919921875, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2058581645, + "version": 240, + "versionNonce": 1738001037, + "isDeleted": false, + "boundElements": null, + "updated": 1739828359948, + "link": null, + "locked": false, + "text": "The movie is always -500 frames to 1800 frames\nThey will add the exact frame that is 0 (where the fly is full on the camera)\nto the matlab structure ", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 67, + "containerId": null, + "originalText": "The movie is always -500 frames to 1800 frames\nThey will add the exact frame that is 0 (where the fly is full on the camera)\nto the matlab structure ", + "lineHeight": 1.25 + }, + { + "id": "ylU4RdyTQY_bEZevhUP4R", + "type": "line", + "x": 287.84695302562096, + "y": 171.01873212571593, + "width": 0, + "height": 88.81332007104993, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1770270637, + "version": 59, + "versionNonce": 1677905005, + "isDeleted": false, + "boundElements": null, + "updated": 1739825434519, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 88.81332007104993 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "LmZRAeD2nbCoLzB6gw9j1", + "type": "text", + "x": 121.72139528099518, + "y": 158.23991056509124, + "width": 113.39985656738281, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2106287395, + "version": 107, + "versionNonce": 1364053869, + "isDeleted": false, + "boundElements": null, + "updated": 1739828253132, + "link": null, + "locked": false, + "text": "Trim if\nthe fly \nis not sight", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 67, + "containerId": null, + "originalText": "Trim if\nthe fly \nis not sight", + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/src/cohen_lab_to_nwb/conversion_notes.md b/src/cohen_lab_to_nwb/conversion_notes.md index 89c7c41..1e6dafd 100644 --- a/src/cohen_lab_to_nwb/conversion_notes.md +++ b/src/cohen_lab_to_nwb/conversion_notes.md @@ -9,13 +9,35 @@ The current structure of the shared data is the following. Each bullet point is * Free Flight Optogenetics Data (This contains the data we are working with) * Optogenetics and Mechanical Perturbation Data (Empty) +A similar experiment can be found in: + +> Whitehead, Samuel C., et al. "Neuromuscular embodiment of feedback control elements in Drosophila flight." Science Advances 8.50 (2022): eabo7461. ## Questions * How to align the mp4 movies with the data in the matlab files? My understanding is that they take the data from the phantom camera which records only when the flies are in view. The phantom camera is at 8000 fps and the videos are 30 fps but there are around 2301 frames in the videos and 401 timestamps in the matlab files. The timestamps cover a range from -10 milliseconds to around 50 milliseconds (so 50 milliseconds total) whereas the videos last like 1 minutes and and seventeen seconds. If they really store all the frames of the phantom in the videos, that means that the duration between frames is 1 / 8000 ~ 0.125 milliseconds. So, the 2301 frames in the video should be around 287 milliseconds. The range on the matlab files is 60 milliseconds so the videos are longer. +Answer after the meeting on the 17 of February: +They have a detection mechanism that uses two lasers that intersect at the middle and photodiodes. +This allows them to trigger camera recording when the fly is in the middle of the intersection of the lasers. + +The camera frames goes from -500 to 1800 frames. The data on the matlab structure sometimes does not correspond to all the data on the video, so sometimes the start is trimmed. That means that the video starts before the data. They will add the frame of the video that corresponds to the first timestamps (the `t` on the data structure) to each of the rows. + +* What is the location of the stimulation? +They will send it +* Why is the unit milliwatt per square millimeters? +This is more useful. See discussion here: +https://github.com/NeurodataWithoutBorders/nwb-schema/issues/609 +* Can we get a table instead of a plot? +They will send it over +* Is the stimuli constant in the other experiments? +Yes. Within an experiment it is constant. +* Are you gona have imaging series data on other experiments? +No, only the confocal data. +* What do you use the confocal image for? +For immunohistochemistry. See the paper reference at the top. Basically, they use it to confirm that the optogenetic protocol worked. This should be a single image in a single nwbfile. ## Free Flight Optogenetics Data @@ -87,6 +109,7 @@ The LED intensities are controlled by changing the amount of current that is dri ![LED Calibration Curve](assets/LED_calibration_curve.png) +#### Questions ### mp4 movies Some metadata for two of the videos: @@ -105,7 +128,7 @@ Command: ffprobe -v error -select_streams v:0 -count_frames -show_entries stre 2301 (number of frames) ``` -Now, the question is to which interval of the experimen they belong to? +Now, the question is to which interval of the experiment they belong to? The movies have 2301 frames diff --git a/src/cohen_lab_to_nwb/convert_free_flight_optogenetics_data.py b/src/cohen_lab_to_nwb/convert_free_flight_optogenetics_data.py index bb232c6..5e213d6 100644 --- a/src/cohen_lab_to_nwb/convert_free_flight_optogenetics_data.py +++ b/src/cohen_lab_to_nwb/convert_free_flight_optogenetics_data.py @@ -1,5 +1,7 @@ import time from pathlib import Path + +import numpy as np from pymatreader import read_mat from neuroconv.datainterfaces import VideoInterface from tqdm import tqdm @@ -13,6 +15,7 @@ import zoneinfo from neuroconv.tools.nwb_helpers import configure_and_write_nwbfile + def convert_stimuli_experiment_to_nwb( matlab_struct_file_path: str | Path, video_folder_path: str | Path, @@ -22,13 +25,12 @@ def convert_stimuli_experiment_to_nwb( matlab_struct_file_path = Path(matlab_struct_file_path) video_folder_path = Path(video_folder_path) - + assert matlab_struct_file_path.is_file(), f"Matlab struct file not found at {matlab_struct_file_path}" assert video_folder_path.exists(), f"Video folder not found at {video_folder_path}" output_dir = Path(output_dir) output_dir.mkdir(exist_ok=True, parents=True) - data_matlab_struct = read_mat(matlab_struct_file_path)["datastruct"] @@ -39,13 +41,13 @@ def convert_stimuli_experiment_to_nwb( movie_number = data_matlab_struct["MovNum"] opto_stimuli_range_start_stop_ms = data_matlab_struct["OptoStimRange"] opto_intensity_in_ampers = data_matlab_struct["optoIntensity"] # TODO confirm units - + # Fields with vectors timestamps = data_matlab_struct["t"] body_pitch_angle = data_matlab_struct["bodyPitch"] body_roll_angle = data_matlab_struct["bodyRoll"] body_yaw_angle = data_matlab_struct["bodyYaw"] - + # Fields with matrices left_wing_angles = data_matlab_struct["wingL"] right_ring_angles = data_matlab_struct["wingR"] @@ -54,13 +56,15 @@ def convert_stimuli_experiment_to_nwb( if verbose: print(f"\nStarting conversion of {matlab_struct_file_path.name}") print(f"Found {len(timestamps)} experiments to convert") - + number_of_experiments = len(timestamps) nwbfile_path_list = [] - + # Create iterator with optional progress bar - experiment_iterator = tqdm(range(number_of_experiments), desc="Converting experiments") if verbose else range(number_of_experiments) - + experiment_iterator = ( + tqdm(range(number_of_experiments), desc="Converting experiments") if verbose else range(number_of_experiments) + ) + for experiment_index in experiment_iterator: experiment_timestamp = timestamps[experiment_index] experiment_body_pitch_angle = body_pitch_angle[experiment_index] @@ -79,15 +83,14 @@ def convert_stimuli_experiment_to_nwb( experiment_opto_stim_range = opto_stimuli_range_start_stop_ms[experiment_index] stim_start, stim_end = experiment_opto_stim_range stim_duration = stim_end - stim_start - experiment_video = movie_number[experiment_index] video_number_xxx_format = f"{experiment_video:03}" experiment_opto_intensity_in_ampers = opto_intensity_in_ampers[experiment_index] - + genotype = driver[experiment_index] experiment_effector = effector[experiment_index] - + session_id = f"{session_date}_{genotype}_{experiment_effector}_{experiment_opto_intensity_in_ampers}_{video_number_xxx_format}" session_description = ( @@ -98,14 +101,14 @@ def convert_stimuli_experiment_to_nwb( f"during optogenetic manipulation." ) nwbfile = NWBFile( - session_start_time = session_start_time, + session_start_time=session_start_time, identifier=str(uuid.uuid4()), session_id=session_id, session_description=session_description, experimenter=["Leung, Abby", "Cohen, Itai"], - keywords = ["Free flight", "Optogenetics", "Wing kinematics", "Body kinematics"], + keywords=["Free flight", "Optogenetics", "Wing kinematics", "Body kinematics"], ) - + # Create detailed subject information full_genotype = f"{driver[experiment_index]}/{experiment_effector}" nwbfile.subject = Subject( @@ -115,28 +118,28 @@ def convert_stimuli_experiment_to_nwb( description="Female fly, 3-5 days old post-eclosion", genotype=full_genotype, strain=f"{driver[experiment_index]}>UAS-{experiment_effector}", # Standard notation for fly crosses - species="Drosophila melanogaster" # Common fruit fly + species="Drosophila melanogaster", # Common fruit fly ) # Add the video data video_file_path = video_folder_path / f"Expr_43_movie_{video_number_xxx_format}.mp4" assert video_file_path.is_file(), f"Video file not found at {video_file_path}" video_interface = VideoInterface(file_paths=[video_file_path]) - + # Add the video interface to the NWB file video_interface.add_to_nwbfile(nwbfile=nwbfile) - + # Create a CompassDirection container for body orientation angles body_orientation = CompassDirection(name="BodyOrientation") nwbfile.add_acquisition(body_orientation) - + # Store body angles as SpatialSeries body_angles = { - 'Pitch': experiment_body_pitch_angle, - 'Roll': experiment_body_roll_angle, - 'Yaw': experiment_body_yaw_angle + "Pitch": experiment_body_pitch_angle, + "Roll": experiment_body_roll_angle, + "Yaw": experiment_body_yaw_angle, } - + for angle_name, angle_data in body_angles.items(): body_angle_series = SpatialSeries( name=f"SpatialSeriesBody{angle_name}", @@ -144,17 +147,16 @@ def convert_stimuli_experiment_to_nwb( data=angle_data, timestamps=experiment_timestamp, reference_frame="lab-centered spherical coordinates", - unit="degrees" + unit="degrees", ) body_orientation.add_spatial_series(body_angle_series) - - + # Create a CompassDirection container for wing angles wing_angles = CompassDirection(name="WingAngles") nwbfile.add_acquisition(wing_angles) - + # Extract and store left wing angles - left_wing_components = ['Stroke', 'Deviation', 'Pitch'] + left_wing_components = ["Stroke", "Deviation", "Pitch"] for i, component in enumerate(left_wing_components): left_wing_series = SpatialSeries( name=f"SpatialSeriesLeftWing{component}", @@ -162,12 +164,12 @@ def convert_stimuli_experiment_to_nwb( data=experiment_left_wing_angles[i], timestamps=experiment_timestamp, reference_frame="body-centered spherical coordinates", - unit="degrees" + unit="degrees", ) wing_angles.add_spatial_series(left_wing_series) - + # Extract and store right wing angles - right_wing_components = ['Stroke', 'Deviation', 'Pitch'] + right_wing_components = ["Stroke", "Deviation", "Pitch"] for i, component in enumerate(right_wing_components): right_wing_series = SpatialSeries( name=f"SpatialSeriesRightWing{component}", @@ -175,46 +177,78 @@ def convert_stimuli_experiment_to_nwb( data=experiment_right_wing_angles[i], timestamps=experiment_timestamp, reference_frame="body-centered spherical coordinates", - unit="degrees" + unit="degrees", ) wing_angles.add_spatial_series(right_wing_series) - + nwbfile_path = output_dir / f"{session_id}.nwb" - configure_and_write_nwbfile(nwbfile=nwbfile, output_filepath=nwbfile_path) - + + # Add optogenetic stimulation information + from pynwb.ogen import OptogeneticStimulusSite, OptogeneticSeries + + device_laser = nwbfile.create_device( + name="DeviceLaser", + description="The laser used for optogenetic stimulation", + ) + + ogen_stimuli_site = OptogeneticStimulusSite( + name="OptogeneticStimulusSite", + description="Optogenetic stimulation site", + device=device_laser, + excitation_lambda=625.0, + location="TBD", + ) + + nwbfile.add_ogen_site(ogen_stimuli_site) + + data = [experiment_opto_intensity_in_ampers] * len(experiment_timestamp) + starting_time = experiment_opto_stim_range[0] + optogenetic_series = OptogeneticSeries( + name="OptogeneticSeries", + description="Optogenetic stimulation series during the experiment", + data=data, + site=ogen_stimuli_site, + rate=np.nan, + starting_time=starting_time, + ) + + nwbfile.add_acquisition(optogenetic_series) + + configure_and_write_nwbfile(nwbfile=nwbfile, output_filepath=nwbfile_path) + if verbose: print(f"\nCreated NWB file: {nwbfile_path.name}") print(f"Session ID: {session_id}") print(f"Subject: {nwbfile.subject.species}, {nwbfile.subject.genotype}") print(f"Experiment date: {session_date}") print(f"Stimulation: {stim_duration}ms at {experiment_opto_intensity_in_ampers}A") - + nwbfile_path_list.append(nwbfile_path) - + end_time = time.time() if verbose: conversion_time = end_time - start_time print(f"\nConversion completed in {conversion_time:.2f} seconds") print(f"Created {len(nwbfile_path_list)} NWB files in {output_dir}") - + return nwbfile_path_list + if __name__ == "__main__": - + driver = "SS40851" effector = "UAS-CsChrimson" intensity = "0.67A" experiment_type = "Opto Activation" - folder_path = Path("/home/heberto/cohen_project/Sample data/Cohen Lab/Free Flight Optogenetics Data") - experiment_folder = folder_path / f"{driver} {effector}"/ f"{intensity} {experiment_type}" + experiment_folder = folder_path / f"{driver} {effector}" / f"{intensity} {experiment_type}" assert experiment_folder.is_dir() matlab_struct_file_path = experiment_folder / "SS40851_0_67A_activation.mat" video_folder_path = experiment_folder / "mp4" output_dir = folder_path.parent / "nwb_files" / f"{driver} {effector}" / f"{intensity} {experiment_type}" - + nwbfile_path_list = convert_stimuli_experiment_to_nwb( matlab_struct_file_path=matlab_struct_file_path, video_folder_path=video_folder_path,