Skip to content

Commit 189fa04

Browse files
authored
Merge pull request #304 from AllenNeuralDynamics/release-v0.27.0
Release v0.27.0
2 parents 147bee7 + a1a4623 commit 189fa04

13 files changed

Lines changed: 619 additions & 373 deletions

File tree

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@ mesoscope = [
7676
openephys = [
7777
"aind-metadata-mapper[schema]",
7878
"h5py >= 3.11.0",
79+
"hdmf < 4.0.0",
7980
"npc_ephys >= 0.1.18",
8081
"scipy >= 1.11.0",
8182
"pandas >= 2.2.2",
8283
"numpy >= 1.26.4",
8384
"npc_mvr >= 0.1.6",
84-
"wavpack-numcodecs==0.2.2"
85+
"wavpack-numcodecs<0.2.2",
86+
"numcodecs<0.16.0",
8587
]
8688

8789
dynamicrouting = [
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Init package"""
22

3-
__version__ = "0.26.0"
3+
__version__ = "0.27.0"

src/aind_metadata_mapper/gather_metadata.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,17 @@
3838
JobSettings as FipSessionJobSettings,
3939
)
4040
from aind_metadata_mapper.fip.session import FIBEtl
41+
from aind_metadata_mapper.mesoscope.models import (
42+
JobSettings as MesoscopeSessionJobSettings,
43+
)
4144
from aind_metadata_mapper.mesoscope.session import MesoscopeEtl
4245
from aind_metadata_mapper.models import JobSettings
46+
from aind_metadata_mapper.open_ephys.camstim_ephys_session import (
47+
CamstimEphysSessionEtl,
48+
)
49+
from aind_metadata_mapper.open_ephys.models import (
50+
JobSettings as OpenEphysJobSettings,
51+
)
4352
from aind_metadata_mapper.smartspim.acquisition import SmartspimETL
4453

4554

@@ -284,8 +293,14 @@ def get_session_metadata(self) -> Optional[dict]:
284293
session_job = MRIEtl(job_settings=session_settings)
285294
elif isinstance(session_settings, FipSessionJobSettings):
286295
session_job = FIBEtl(job_settings=session_settings)
287-
else:
296+
elif isinstance(session_settings, MesoscopeSessionJobSettings):
288297
session_job = MesoscopeEtl(job_settings=session_settings)
298+
elif isinstance(session_settings, OpenEphysJobSettings):
299+
session_job = CamstimEphysSessionEtl(
300+
job_settings=session_settings
301+
)
302+
else:
303+
raise ValueError("Unknown session job settings class!")
289304
job_response = session_job.run_job()
290305
if job_response.status_code != 500:
291306
return json.loads(job_response.data)

src/aind_metadata_mapper/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
from aind_metadata_mapper.mesoscope.models import (
2222
JobSettings as MesoscopeSessionJobSettings,
2323
)
24+
from aind_metadata_mapper.open_ephys.models import (
25+
JobSettings as OpenEphysJobSettings,
26+
)
2427
from aind_metadata_mapper.smartspim.models import (
2528
JobSettings as SmartSpimAcquisitionJobSettings,
2629
)
@@ -35,6 +38,7 @@ class SessionSettings(BaseSettings, extra="allow"):
3538
BrukerSessionJobSettings,
3639
FipSessionJobSettings,
3740
MesoscopeSessionJobSettings,
41+
OpenEphysJobSettings,
3842
],
3943
Field(discriminator="job_settings_name"),
4044
]

src/aind_metadata_mapper/open_ephys/camstim_ephys_session.py

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from pathlib import Path
1111
from typing import Union
1212

13-
import npc_ephys
1413
import npc_mvr
1514
import numpy as np
1615
import pandas as pd
@@ -25,7 +24,13 @@
2524
VisualStimulation,
2625
)
2726
from aind_data_schema_models.modalities import Modality
27+
from npc_ephys import (
28+
get_ephys_timing_on_sync,
29+
get_newscale_coordinates,
30+
get_single_oebin_path,
31+
)
2832

33+
import aind_metadata_mapper.open_ephys.utils.pkl_utils as pkl
2934
import aind_metadata_mapper.open_ephys.utils.sync_utils as sync
3035
import aind_metadata_mapper.stimulus.camstim
3136
from aind_metadata_mapper.core import GenericEtl
@@ -46,9 +51,7 @@ class CamstimEphysSessionEtl(
4651
session_path: Path
4752
recording_dir: Path
4853

49-
def __init__(
50-
self, session_id: str, job_settings: Union[JobSettings, str, dict]
51-
) -> None:
54+
def __init__(self, job_settings: Union[JobSettings, str, dict]) -> None:
5255
"""
5356
Determine needed input filepaths from np-exp and lims, get session
5457
start and end times from sync file, write stim tables and extract
@@ -67,48 +70,69 @@ def __init__(
6770
job_settings_model = job_settings
6871
GenericEtl.__init__(self, job_settings=job_settings_model)
6972

70-
sessions_root = Path(self.job_settings.sessions_root)
71-
self.folder = self.get_folder(session_id, sessions_root)
72-
self.session_path = self.get_session_path(session_id, sessions_root)
73-
self.recording_dir = npc_ephys.get_single_oebin_path(
74-
self.session_path
75-
).parent
73+
# sessions_root = Path(self.job_settings.sessions_root)
74+
# self.folder_name = self.get_folder(session_id, sessions_root)
75+
# self.session_path = self.get_session_path(session_id, sessions_root)
76+
self.session_path = job_settings.input_source
77+
self.folder_name = self.session_path.name
78+
self.output_dir = job_settings.output_directory
79+
# sometimes data files are deleted on npexp so try files on lims
80+
# try:
81+
# self.recording_dir = get_single_oebin_path(
82+
# session_inst.lims_path
83+
# ).parent
84+
# except:
85+
self.recording_dir = get_single_oebin_path(self.session_path).parent
7686

7787
self.motor_locs_path = (
78-
self.session_path / f"{self.folder}.motor-locs.csv"
88+
self.session_path / f"{self.folder_name}.motor-locs.csv"
7989
)
80-
self.pkl_path = self.session_path / f"{self.folder}.stim.pkl"
81-
self.opto_pkl_path = self.session_path / f"{self.folder}.opto.pkl"
90+
self.pkl_path = self.session_path / f"{self.folder_name}.stim.pkl"
91+
if not self.pkl_path.exists():
92+
self.pkl_path = (
93+
self.session_path / f"{self.folder_name}.behavior.pkl"
94+
)
95+
logger.debug("Using pickle:", self.pkl_path)
96+
self.pkl_data = pkl.load_pkl(self.pkl_path)
97+
self.fps = pkl.get_fps(self.pkl_data)
98+
99+
self.opto_pkl_path = self.session_path / f"{self.folder_name}.opto.pkl"
82100
self.opto_table_path = (
83-
self.session_path / f"{self.folder}_opto_epochs.csv"
101+
self.session_path / f"{self.folder_name}_opto_epochs.csv"
84102
)
103+
self.opto_conditions_map = job_settings.opto_conditions_map
85104
self.stim_table_path = (
86-
self.session_path / f"{self.folder}_stim_epochs.csv"
105+
self.session_path / f"{self.folder_name}_stim_epochs.csv"
87106
)
88-
self.sync_path = self.session_path / f"{self.folder}.sync"
107+
self.sync_path = self.session_path / f"{self.folder_name}.sync"
89108

90109
platform_path = next(
91-
self.session_path.glob(f"{self.folder}_platform*.json")
110+
self.session_path.glob(f"{self.folder_name}_platform*.json")
92111
)
93112
self.platform_json = json.loads(platform_path.read_text())
94113
self.project_name = self.platform_json["project"]
95114

96-
sync_data = sync.load_sync(self.sync_path)
97-
self.session_start = sync.get_start_time(sync_data)
98-
self.session_end = sync.get_stop_time(sync_data)
115+
self.sync_data = sync.load_sync(self.sync_path)
116+
self.session_start = sync.get_start_time(self.sync_data)
117+
self.session_end = sync.get_stop_time(self.sync_data)
99118
logger.debug(
100119
f"session start: {self.session_start} \n"
101120
f" session end: {self.session_end}"
102121
)
103122

104123
self.session_uuid = self.get_session_uuid()
105-
self.mtrain_regimen = self.get_mtrain()
124+
self.mtrain_server = job_settings.mtrain_server
125+
self.stage_name = pkl.get_stage(self.pkl_data)
126+
self.behavior = self._is_behavior()
106127

107128
if not self.stim_table_path.exists() or (
108129
self.job_settings.overwrite_tables
109130
):
110131
logger.debug("building stim table")
111-
self.build_stimulus_table()
132+
if self.behavior:
133+
self.build_behavior_table()
134+
else:
135+
self.build_stimulus_table()
112136
if self.opto_pkl_path.exists() and (
113137
not self.opto_table_path.exists()
114138
or self.job_settings.overwrite_tables
@@ -127,7 +151,7 @@ def run_job(self):
127151
"""Transforms all metadata for the session into relevant files"""
128152
self._extract()
129153
self._transform()
130-
return self._load(self.session_json, self.session_path)
154+
return self._load(self.session_json, self.output_dir)
131155

132156
def _extract(self):
133157
"""TODO: refactor a lot of the __init__ code here"""
@@ -146,7 +170,7 @@ def _transform(self) -> Session:
146170
session_type=self.job_settings.session_type,
147171
iacuc_protocol=self.job_settings.iacuc_protocol,
148172
rig_id=self.platform_json["rig_id"],
149-
subject_id=self.folder.split("_")[1],
173+
subject_id=self.folder_name.split("_")[1],
150174
data_streams=self.data_streams(),
151175
stimulus_epochs=self.stim_epochs,
152176
mouse_platform_name=self.job_settings.mouse_platform_name,
@@ -235,9 +259,7 @@ def ephys_modules(self) -> list:
235259
"""
236260
Return list of schema ephys modules for each available probe.
237261
"""
238-
newscale_coords = npc_ephys.get_newscale_coordinates(
239-
self.motor_locs_path
240-
)
262+
newscale_coords = get_newscale_coordinates(self.motor_locs_path)
241263

242264
ephys_modules = []
243265
for probe_letter in self.available_probes:
@@ -251,7 +273,7 @@ def ephys_modules(self) -> list:
251273
arc_angle=0.0,
252274
module_angle=0.0,
253275
rotation_angle=0.0,
254-
primary_targeted_structure="none",
276+
primary_targeted_structure="root",
255277
manipulator_coordinates=manipulator_coordinates,
256278
notes=notes,
257279
)
@@ -265,7 +287,7 @@ def ephys_stream(self) -> Stream:
265287
"""
266288
probe_exp = r"(?<=[pP{1}]robe)[-_\s]*(?P<letter>[A-F]{1})(?![a-zA-Z])"
267289

268-
times = npc_ephys.get_ephys_timing_on_sync(
290+
times = get_ephys_timing_on_sync(
269291
sync=self.sync_path, recording_dirs=[self.recording_dir]
270292
)
271293

@@ -336,10 +358,62 @@ def data_streams(self) -> tuple[Stream, ...]:
336358
"""
337359
data_streams = []
338360
data_streams.append(self.ephys_stream())
339-
data_streams.append(self.sync_stream())
340-
data_streams.append(self.video_stream())
361+
# data_streams.append(self.sync_stream())
362+
# data_streams.append(self.video_stream())
341363
return tuple(data_streams)
342364

365+
def build_optogenetics_table(self):
366+
"""
367+
Builds an optogenetics table from the opto pickle file and sync file.
368+
Writes the table to a csv file.
369+
Parameters
370+
----------
371+
output_opto_table_path : str
372+
Path to write the optogenetics table to.
373+
returns
374+
-------
375+
dict
376+
Dictionary containing the path to the output opto table
377+
"""
378+
opto_file = pkl.load_pkl(self.opto_pkl_path)
379+
sync_file = sync.load_sync(self.sync_path)
380+
start_times = sync.extract_led_times(
381+
sync_file, self.opto_conditions_map
382+
)
383+
condition_nums = [str(item) for item in opto_file["opto_conditions"]]
384+
levels = opto_file["opto_levels"]
385+
assert len(condition_nums) == len(levels)
386+
if len(start_times) > len(condition_nums):
387+
raise ValueError(
388+
f"there are {len(start_times) - len(condition_nums)} extra "
389+
f"optotagging sync times!"
390+
)
391+
optotagging_table = pd.DataFrame(
392+
{
393+
"start_time": start_times,
394+
"condition_num": condition_nums,
395+
"level": levels,
396+
}
397+
)
398+
optotagging_table = optotagging_table.sort_values(
399+
by="start_time", axis=0
400+
)
401+
stop_times = []
402+
conditions = []
403+
names = []
404+
for _, row in optotagging_table.iterrows():
405+
condition = self.opto_conditions_map[row["condition_num"]]
406+
stop_times.append(row["start_time"] + condition["duration"])
407+
conditions.append(condition["condition"])
408+
names.append(condition["name"])
409+
optotagging_table["stop_time"] = stop_times
410+
optotagging_table["condition"] = conditions
411+
optotagging_table["name"] = names
412+
optotagging_table["duration"] = (
413+
optotagging_table["stop_time"] - optotagging_table["start_time"]
414+
)
415+
optotagging_table.to_csv(self.opto_table_path, index=False)
416+
343417
def epoch_from_opto_table(self) -> StimulusEpoch:
344418
"""
345419
From the optogenetic stimulation table, returns a single schema
@@ -348,11 +422,9 @@ def epoch_from_opto_table(self) -> StimulusEpoch:
348422
parameters, and include the set of all of that column's values as the
349423
parameter values.
350424
"""
351-
352425
script_obj = Software(
353-
name=self.mtrain_regimen["name"],
426+
name=self.stage_name,
354427
version="1.0",
355-
url=self.mtrain_regimen,
356428
)
357429

358430
opto_table = pd.read_csv(self.opto_table_path)

0 commit comments

Comments
 (0)