From 2eb911408dcb8ef4ad27c93dbefda97b20bc5cad Mon Sep 17 00:00:00 2001 From: bjhardcastle Date: Thu, 9 May 2024 14:54:14 -0700 Subject: [PATCH] Update injection parameters [skip CI] --- src/npc_shields/injections.py | 66 ++++++++++++++++++---------- src/npc_shields/types.py | 83 +++++++++++++++++++++++------------ 2 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/npc_shields/injections.py b/src/npc_shields/injections.py index 6678235..d72fcee 100644 --- a/src/npc_shields/injections.py +++ b/src/npc_shields/injections.py @@ -21,34 +21,33 @@ class Injection: >>> i = Injection( ... shield=npc_shields.shields.DR2002, - ... location="A1", + ... location="D1", + ... target_structure="VISp", ... hemisphere="left", - ... depth_um=3000, + ... depth_um=200, ... substance="Fluorogold", ... manufacturer="Sigma", ... identifier="12345", - ... total_volume_ul=1.0, + ... total_volume_nl=1.0, ... concentration_mg_ml=10.0, - ... flow_rate_ul_s=0.1, + ... flow_rate_nl_s=0.1, ... start_time=datetime.datetime(2023, 1, 1, 12, 0), ... fluorescence_nm=500, ... number_of_injections=3, ... notes="This was a test injection", ... is_control=False, + ... is_anaesthetized=True, ... ) """ shield: npc_shields.types.Shield | None """The shield through which the injection was made.""" - location: str - """The hole in the shield through which the injection was made (e.g. 'C3'). - - - alternatively, a string indicating location of a burr hole or other non-shield location. - """ + target_structure: str + """The intended brain structure for the injection ('VISp' etc.).""" hemisphere: Literal['left', 'right'] - """The hemisphere of the brain where the injection was made (e.g. 'left', 'right').""" + """The hemisphere of the brain where the injection was made ('left' or 'right').""" depth_um: float """Depth of the injection, in microns from brain surface.""" @@ -62,22 +61,39 @@ class Injection: identifier: str | None """Identifier of the injected substance (e.g. manufacture serial number).""" - total_volume_ul: float - """Total volume injected, in microliters.""" + total_volume_nl: float + """Total volume injected, in nanoliters.""" concentration_mg_ml: float | None """Concentration of the injected substance in milligrams per milliliter.""" - flow_rate_ul_s: float - """Flow rate of the injection in microliters per second.""" + flow_rate_nl_s: float + """Flow rate of the injection in nanoliters per second.""" start_time: datetime.datetime """Time of the first injection, as a datetime object.""" + is_anaesthetized: bool + """Whether the subject was anaesthetized during the injection.""" + # args with defaults ----------------------------------------------- # + location: str | None = None + """The hole in the shield through which the injection was made (e.g. 'C3'). + + - alternatively, a string indicating location of a burr hole or other non-shield location. + """ + + location_ap: float | None = None + """Distance in millimeters from bregma to injection site along + anterior-posterior axis (+ve is anterior).""" + + location_ml: float | None = None + """Distance in millimeters from brain midline to injection site along + medial-lateral axis.""" + fluorescence_nm: float | None = None - """Wavelength of fluorescence for the injection.""" + """Emission wavelength of the substance injected, if it fluoresces.""" number_of_injections: int = 1 """Number of individual injections made at this site + depth.""" @@ -88,27 +104,33 @@ class Injection: notes: str | None = None """Text notes for the injection.""" - + def to_json(self) -> dict[str, Any]: + data = dataclasses.asdict(self) + if self.shield is not None: + data['shield'] = self.shield.to_json() + return data + @dataclasses.dataclass class InjectionRecord: """A record of a set of injections. >>> i = Injection( ... shield=npc_shields.shields.DR2002, - ... location="A1", + ... target_structure="VISp", ... hemisphere="left", ... depth_um=3000, ... substance="Fluorogold", ... manufacturer="Sigma", ... identifier="12345", - ... total_volume_ul=1.0, + ... total_volume_nl=1.0, ... concentration_mg_ml=10.0, - ... flow_rate_ul_s=0.1, + ... flow_rate_nl_s=0.1, ... start_time=datetime.datetime(2023, 1, 1, 12, 0), ... fluorescence_nm=500, ... number_of_injections=3, ... notes="This was a test injection", ... is_control=False, + ... is_anaesthetized=False, ... ) >>> r = InjectionRecord( ... injections=[i], @@ -116,9 +138,9 @@ class InjectionRecord: ... experiment_day=1, ... ) >>> r.to_json() - {'injections': [{'shield': {'name': '2002', 'drawing_id': '0283-200-002', 'labels': ('A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'B4', 'C1', 'C2', 'C3', 'C4', 'D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'E4', 'F1', 'F2', 'F3'), 'svg': WindowsPath('C:/Users/ben.hardcastle/github/np_probe_targets/src/npc_shields/drawings/2002.svg')}, 'location': 'A1', 'hemisphere': 'left', 'depth_um': 3000, 'substance': 'Fluorogold', 'manufacturer': 'Sigma', 'identifier': '12345', 'total_volume_ul': 1.0, 'concentration_mg_ml': 10.0, 'flow_rate_ul_s': 0.1, 'start_time': datetime.datetime(2023, 1, 1, 12, 0), 'fluorescence_nm': 500, 'number_of_injections': 3, 'is_control': False, 'notes': 'This was a test injection'}], 'session': '366122_20240101', 'experiment_day': 1} + {'injections': [{'shield': {'name': '2002', 'drawing_id': '0283-200-002'}, 'target_structure': 'VISp', 'hemisphere': 'left', 'depth_um': 3000, 'substance': 'Fluorogold', 'manufacturer': 'Sigma', 'identifier': '12345', 'total_volume_nl': 1.0, 'concentration_mg_ml': 10.0, 'flow_rate_nl_s': 0.1, 'start_time': datetime.datetime(2023, 1, 1, 12, 0), 'is_anaesthetized': False, 'location': None, 'location_ap': None, 'location_ml': None, 'fluorescence_nm': 500, 'number_of_injections': 3, 'is_control': False, 'notes': 'This was a test injection'}], 'session': '366122_20240101', 'experiment_day': 1} """ - + injections: Sequence[Injection] """A record of each injection made.""" @@ -132,7 +154,7 @@ class InjectionRecord: def to_json(self)-> dict[str, Any]: """Get a JSON-serializable representation of the injections.""" return { - 'injections': [dataclasses.asdict(injection) for injection in self.injections], + 'injections': [injection.to_json() for injection in self.injections], 'session': self.session, 'experiment_day': self.experiment_day, } diff --git a/src/npc_shields/types.py b/src/npc_shields/types.py index 218632c..7131497 100644 --- a/src/npc_shields/types.py +++ b/src/npc_shields/types.py @@ -4,12 +4,11 @@ from __future__ import annotations -import dataclasses import datetime import pathlib import typing -from collections.abc import Iterable, MutableMapping -from typing import Any, Literal, Protocol, Sequence, Union +from collections.abc import Iterable, MutableMapping, Sequence +from typing import Any, Literal, Protocol, Union import npc_session from typing_extensions import TypeAlias @@ -47,6 +46,9 @@ def svg(self) -> pathlib.Path: def __hash__(self) -> int: ... + def to_json(self) -> dict[str, Any]: + """Get a JSON-serializable representation of the shield.""" + ... class Insertion(Protocol): """A set of probes inserted (or planned to be inserted) into a shield.""" @@ -90,7 +92,7 @@ def experiment_day(self) -> int: class Injection(Protocol): """An injection through a hole in a shield at a particular brain location (site + depth). - + - should allow for no shield (e.g. burr hole) - should record hemisphere - may consist of multiple individual injections @@ -104,75 +106,101 @@ def shield(self) -> Shield | None: @property def location(self) -> str: """The hole in the shield through which the injection was made (e.g. 'C3'). - + - alternatively, a string indicating location of a burr hole or other non-shield location. """ ... - - @property + + @property + def location_ap(self) -> float | None: + """Distance in millimeters from bregma to injection site along + anterior-posterior axis (+ve is anterior).""" + ... + + @property + def location_ml(self) -> float | None: + """Distance in millimeters from brain midline to injection site along + medial-lateral axis.""" + ... + + @property + def target_structure(self) -> str: + """The intended brain structure for the injection ('VISp' etc.).""" + ... + + @property def hemisphere(self) -> Literal['left', 'right']: """The hemisphere of the brain where the injection was made (e.g. 'left', 'right').""" ... - + @property def depth_um(self) -> float: """Depth of the injection, in microns from brain surface.""" ... - + @property def fluorescence_nm(self) -> float | None: """Wavelength of fluorescence for the injection.""" ... - + @property def manufacturer(self) -> str | None: """Manufacturer of the injected substance.""" ... - - @property + + @property def substance(self) -> str: """Name of the injected substance.""" ... - - @property + + @property def identifier(self) -> str | None: """Identifier of the injected substance (e.g. manufacture serial number).""" ... - + @property def concentration_mg_ml(self) -> float | None: """Concentration of the injected substance in milligrams per milliliter.""" ... - + @property - def flow_rate_ul_s(self) -> float: - """Flow rate of the injection in microliters per second.""" + def flow_rate_nl_s(self) -> float: + """Flow rate of the injection in nanoliters per second.""" ... - + @property def number_of_injections(self) -> int: """Number of individual injections made at this site + depth.""" ... - + @property - def total_volume_ul(self) -> float: - """Total volume injected, in microliters.""" + def total_volume_nl(self) -> float: + """Total volume injected, in nanoliters.""" ... - + @property def start_time(self) -> datetime.datetime: """Time of the first injection, as a datetime object.""" ... - + + @property + def is_anaesthetized(self) -> bool: + """Whether the subject was anaesthetized during the injection.""" + ... + @property def is_control(self) -> bool: """Whether the purpose of the injection was a control.""" ... - + @property - def notes(self) -> dict[str | npc_session.ProbeRecord, str | None]: + def notes(self) -> str | None: """Text notes for the injection.""" ... + + def to_json(self) -> dict[str, Any]: + """Get a JSON-serializable representation of the injection.""" + ... class InjectionRecord(Protocol): @@ -194,6 +222,5 @@ def experiment_day(self) -> int: ... def to_json(self) -> dict[str, Any]: - """Get a JSON-serializable representation of the injections.""" + """Get a JSON-serializable representation of the injections in a session.""" ... - \ No newline at end of file