-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ambient data #777
base: iblrigv8dev
Are you sure you want to change the base?
Ambient data #777
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
from typing import Annotated, Literal | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import serial | ||
import sounddevice as sd | ||
from annotated_types import Ge, Le | ||
|
@@ -30,6 +31,10 @@ | |
from pybpodapi.state_machine import StateMachine | ||
|
||
SOFTCODE = IntEnum('SOFTCODE', ['STOP_SOUND', 'PLAY_TONE', 'PLAY_NOISE', 'TRIGGER_CAMERA']) | ||
DTYPE_AMBIENT_SENSOR_RAW = np.dtype( | ||
[('Temperature_C', np.float16), ('AirPressure_mb', np.float16), ('RelativeHumidity', np.float16)] | ||
) | ||
DTYPE_AMBIENT_SENSOR_BIN = np.dtype([('Trial', np.uint16)] + DTYPE_AMBIENT_SENSOR_RAW.descr) | ||
|
||
# some annotated types | ||
Uint8 = Annotated[int, Ge(0), Le(255)] | ||
|
@@ -206,23 +211,69 @@ def define_rotary_encoder_actions(self, module: BpodModule | None = None) -> Non | |
} | ||
) | ||
|
||
def get_ambient_sensor_reading(self): | ||
def get_ambient_sensor_reading(self) -> np.ndarray: | ||
""" | ||
Retrieve ambient sensor readings. | ||
|
||
If the ambient sensor module is not available, returns an array filled with NaN values. | ||
Otherwise, retrieves the temperature, air pressure, and relative humidity readings. | ||
|
||
Returns | ||
------- | ||
np.ndarray | ||
A NumPy array containing the sensor readings in the following order: | ||
|
||
- [0] : Temperature in degrees Celsius | ||
- [1] : Air pressure in millibars | ||
- [2] : Relative humidity in percentage | ||
""" | ||
if self.ambient_module is None: | ||
return { | ||
'Temperature_C': np.nan, | ||
'AirPressure_mb': np.nan, | ||
'RelativeHumidity': np.nan, | ||
} | ||
self.ambient_module.start_module_relay() | ||
self.bpod_modules.module_write(self.ambient_module, 'R') | ||
reply = self.bpod_modules.module_read(self.ambient_module, 12) | ||
self.ambient_module.stop_module_relay() | ||
|
||
return { | ||
'Temperature_C': np.frombuffer(bytes(reply[:4]), np.float32)[0], | ||
'AirPressure_mb': np.frombuffer(bytes(reply[4:8]), np.float32)[0] / 100, | ||
'RelativeHumidity': np.frombuffer(bytes(reply[8:]), np.float32)[0], | ||
} | ||
data = np.full(3, np.nan, np.float16) | ||
else: | ||
self.ambient_module.start_module_relay() | ||
self.bpod_modules.module_write(self.ambient_module, 'R') | ||
reply = self.bpod_modules.module_read(self.ambient_module, 12) | ||
self.ambient_module.stop_module_relay() | ||
data = np.frombuffer(bytes(reply), dtype=np.float16).copy() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the original data in float32 ? Why do we use float16 here ? |
||
data[1] /= 100 | ||
return data | ||
|
||
@staticmethod | ||
def write_ambient_data(filepath: Path | str, trial_number: int, sensor_reading: np.ndarray): | ||
""" | ||
Write ambient sensor data to a binary file. | ||
|
||
Parameters | ||
---------- | ||
filepath : Path or str | ||
The path to the file where the data will be written. | ||
trial_number : int | ||
The trial number associated with the sensor readings. | ||
sensor_reading : np.ndarray | ||
A 1D array containing three sensor readings: temperature, air pressure, and relative humidity. | ||
""" | ||
with Path(filepath).open('ab') as f: | ||
ambient_data = np.array([(trial_number, *sensor_reading)], dtype=DTYPE_AMBIENT_SENSOR_BIN) | ||
ambient_data.tofile(f) | ||
|
||
@staticmethod | ||
def read_ambient_data(filepath: Path | str) -> pd.DataFrame: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would put this with the jsonable in iblutils ! |
||
""" | ||
Read ambient sensor data from a binary file into a DataFrame. | ||
|
||
Parameters | ||
---------- | ||
filepath : Path or str | ||
The path to the file from which the data will be read. | ||
|
||
Returns | ||
------- | ||
pd.DataFrame | ||
A DataFrame containing the ambient sensor data, with columns corresponding to the fields | ||
defined in `DTYPE_AMBIENT_SENSOR_BIN`. | ||
""" | ||
data = np.fromfile(filepath, dtype=DTYPE_AMBIENT_SENSOR_BIN) | ||
return pd.DataFrame(data) | ||
|
||
def flush(self): | ||
"""Flushes valve 1.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes so here it is worthwhile noting that we get to open/close the file at each trial.
It is not an issue in our case, but for a much faster stream, we would rather open the file at the start of the session, and feed the file pointer to this method.
The idea being that the method can parse the type being fed, if it is a file pointer it writes directly, if it is a file path it opens / closes the file as usual.