From 8f2897d0aba623b354ed3aa86a0823325b02f098 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 02:00:36 +0000 Subject: [PATCH] Bump [skip actions] --- pyproject.toml | 2 +- src/npc_shields/injections.py | 1 + src/npc_shields/shields.py | 47 +++++++++++++++++++++-------------- src/npc_shields/types.py | 8 +++--- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 812497d..0654684 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "npc_shields" -version = "0.1.9" +version = "0.1.10" description = "Tools for Neuropixels probe insertion: providing suggested targets, recording actual insertions, storing notes." authors = [ { name = "bjhardcastle", email = "ben.hardcastle@alleninstitute.org" }, diff --git a/src/npc_shields/injections.py b/src/npc_shields/injections.py index e6ad4b6..5cd6465 100644 --- a/src/npc_shields/injections.py +++ b/src/npc_shields/injections.py @@ -114,6 +114,7 @@ def serialize_start_time_field(self, start_time: datetime.datetime) -> str: def to_json(self) -> dict[str, Any]: return self.model_dump() + @dataclasses.dataclass class InjectionRecord: """A record of a set of injections. diff --git a/src/npc_shields/shields.py b/src/npc_shields/shields.py index f6129ab..94723a0 100644 --- a/src/npc_shields/shields.py +++ b/src/npc_shields/shields.py @@ -12,8 +12,8 @@ DRAWINGS_DIR = pathlib.Path(__file__).parent / "drawings" COORDINATES_DIR = pathlib.Path(__file__).parent / "hole_coordinates" + class Hole(pydantic.BaseModel): - model_config = pydantic.ConfigDict( arbitrary_types_allowed=True, extra="allow", @@ -34,24 +34,25 @@ class Hole(pydantic.BaseModel): location_ml: float | None = None """Medial-lateral distance of the hole, in millimeters, from midline (positive is right hemisphere).""" - + location_z: float | None = None """Depth of the hole, in millimeters, origin uncertain.""" + @functools.cache def get_svg_data( shield: npc_shields.types.Shield, ) -> str: return shield.drawing_svg.read_text() - + class Shield(pydantic.BaseModel): model_config = pydantic.ConfigDict( arbitrary_types_allowed=True, extra="allow", frozen=True, ) - + name: str drawing_id: int | str drawing_svg: pathlib.Path @@ -64,51 +65,61 @@ def to_json(self) -> dict[str, str | int]: def holes(self) -> dict[str, Hole]: return get_holes_from_csv(self.hole_coordinates_csv) - @pydantic.model_validator(mode='after') + @pydantic.model_validator(mode="after") def validate_holes_in_svg(self): svg_data = get_svg_data(self) for label in self.holes: if f">{label}" not in svg_data: - raise ValueError(f"Mismatch between coordinates csv and drawing svg: {label} not found in {self.drawing_svg.name}") - + raise ValueError( + f"Mismatch between coordinates csv and drawing svg: {label} not found in {self.drawing_svg.name}" + ) + def get_holes_from_csv(csv_path: pathlib.Path) -> dict[str, Hole]: """ >>> holes = get_holes_from_csv(COORDINATES_DIR / "2011.csv") >>> holes['B4'] Hole(label='B4', target_structure='CP coverage', location_ap=0.9, location_ml=-2.2, location_z=None) - + # target structure is empty string if not specified in the csv: >>> holes['E2'] Hole(label='E2', target_structure='', location_ap=-1.3, location_ml=-3.2, location_z=None) """ - with open(csv_path, newline='') as csvfile: - creader = csv.reader(csvfile, delimiter=',') + with open(csv_path, newline="") as csvfile: + creader = csv.reader(csvfile, delimiter=",") columns = creader.__next__() # column_dtypes = {'AP': float, 'ML': float, 'Target': str} hole_data: dict[str, Hole] = {} for row in creader: label = row[0] + def get_column_idx(name) -> int: - idx = next((i for i, col in enumerate(columns) if name.lower() in col.lower()), None) + idx = next( + (i for i, col in enumerate(columns) if name.lower() in col.lower()), + None, + ) if idx is None: - raise ValueError(f"Column {name!r} not found in csv columns: {columns}") + raise ValueError( + f"Column {name!r} not found in csv columns: {columns}" + ) return idx + try: - target = row[get_column_idx('Target')] + target = row[get_column_idx("Target")] except ValueError: target = None hole_data[label] = Hole( label=label, target_structure=target or "", # pydantic will convert these named params to the correct types: - location_ap=row[get_column_idx('AP')], # type: ignore[arg-type] - location_ml=row[get_column_idx('ML')], # type: ignore[arg-type] + location_ap=row[get_column_idx("AP")], # type: ignore[arg-type] + location_ml=row[get_column_idx("ML")], # type: ignore[arg-type] # extra columns are will be added as strings: - **{c: row[get_column_idx(c)] for c in columns[1:] if c not in ('AP', 'ML', 'Target')} # type: ignore[arg-type] + **{c: row[get_column_idx(c)] for c in columns[1:] if c not in ("AP", "ML", "Target")}, # type: ignore[arg-type] ) return hole_data - + + def get_labels_from_mapping(mapping: Mapping[str, Iterable[int]]) -> tuple[str, ...]: """Convert a mapping of probe letter to insertion holes to a tuple of labels. @@ -140,7 +151,7 @@ def get_labels_from_mapping(mapping: Mapping[str, Iterable[int]]) -> tuple[str, name="2006", drawing_id="0283-200-006", drawing_svg=DRAWINGS_DIR / "2006.svg", - hole_coordinates_csv=COORDINATES_DIR / "2006.csv" + hole_coordinates_csv=COORDINATES_DIR / "2006.csv", ) """DR2 rev1/2006 - MPE drawing 0283-200-006""" diff --git a/src/npc_shields/types.py b/src/npc_shields/types.py index edc4c68..3f399cc 100644 --- a/src/npc_shields/types.py +++ b/src/npc_shields/types.py @@ -18,6 +18,7 @@ e.g `{"A": "A1", "B": "B2", "C": None, "D": "E2", "E": "E1", "F": "F1"}` """ + @typing.runtime_checkable class Hole(Protocol): """Info about a hole in a shield, with label and AP/ML coordinates, and other optional params.""" @@ -25,21 +26,22 @@ class Hole(Protocol): label: str """Label of the hole, as specified in the coords csv & job svg, e.g. 'A1'.""" - location_ap: float | None + location_ap: float | None """Anterior-posterior distance of the hole, in millimeters, from Bregma (positive is anterior).""" - location_ml: float | None + location_ml: float | None """Medial-lateral distance of the hole, in millimeters, from midline (positive is right hemisphere).""" target_structure: str | None """Intended target structure of the hole, e.g. 'VISp', when the corresponding probe is inserted, e.g. probe B in B1.""" - + location_z: float | None """Depth of the hole, in millimeters, origin uncertain.""" + @typing.runtime_checkable class Shield(Protocol): """A specific implant with a diagram and labelled holes"""