From 781aebdfe9b69929c13995f28cbb873101a33fcb Mon Sep 17 00:00:00 2001 From: JPPayonk Date: Mon, 29 Jan 2024 10:17:19 +0100 Subject: [PATCH] restructure and add SEEG electrodes --- ossdbs/electrodes/__init__.py | 35 ++++-- ossdbs/electrodes/defaults.py | 76 +++++++++-- ossdbs/electrodes/dixi_microtechniques.py | 147 ++-------------------- 3 files changed, 100 insertions(+), 158 deletions(-) diff --git a/ossdbs/electrodes/__init__.py b/ossdbs/electrodes/__init__.py index 4b12b3f7..4fec7db4 100644 --- a/ossdbs/electrodes/__init__.py +++ b/ossdbs/electrodes/__init__.py @@ -17,8 +17,12 @@ AbbottStJudeDirected6173, BostonScientificVercise, BostonScientificVerciseDirected, + DixiSEEG5, + DixiSEEG8, DixiSEEG10, + DixiSEEG12, DixiSEEG15, + DixiSEEG18, Medtronic3387, Medtronic3389, Medtronic3391, @@ -35,10 +39,8 @@ default_electrode_parameters, ) from .dixi_microtechniques import ( - DixiSEEG10Model, - DixiSEEG10Parameters, - DixiSEEG15Model, - DixiSEEG15Parameters, + DixiSEEGModel, + DixiSEEGParameters, ) from .electrode_model_template import ElectrodeModel from .medtronic import ( @@ -63,8 +65,12 @@ "AbbottStJudeDirected6173": AbbottStJudeDirected6173, "BostonScientificVercise": BostonScientificVercise, "BostonScientificVerciseDirected": BostonScientificVerciseDirected, + "DixiSEEG5": DixiSEEG5, + "DixiSEEG8": DixiSEEG8, "DixiSEEG10": DixiSEEG10, + "DixiSEEG12": DixiSEEG12, "DixiSEEG15": DixiSEEG15, + "DixiSEEG18": DixiSEEG18, "Medtronic3387": Medtronic3387, "Medtronic3389": Medtronic3389, "Medtronic3391": Medtronic3391, @@ -87,8 +93,12 @@ "AbbottStJudeDirected6173Custom": AbbottStJudeDirectedModel, "BostonScientificVerciseCustom": BostonScientificVerciseModel, "BostonScientificVerciseDirectedCustom": BostonScientificVerciseDirectedModel, - "DixiSEEG10Custom": DixiSEEG10Model, - "DixiSEEG15Custom": DixiSEEG15Model, + "DixiSEEG5Custom": DixiSEEGModel, + "DixiSEEG8Custom": DixiSEEGModel, + "DixiSEEG10Custom": DixiSEEGModel, + "DixiSEEG12Custom": DixiSEEGModel, + "DixiSEEG15Custom": DixiSEEGModel, + "DixiSEEG158ustom": DixiSEEGModel, "Medtronic3387Custom": MedtronicModel, "Medtronic3389Custom": MedtronicModel, "Medtronic3391Custom": MedtronicModel, @@ -110,8 +120,7 @@ "AbbottStJudeDirectedModel": AbbottStJudeParameters, "BostonScientificVerciseModel": BostonScientificVerciseParameters, "BostonScientificVerciseDirectedModel": BostonScientificVerciseDirectedParameters, - "DixiSEEG10Model": DixiSEEG10Parameters, - "DixiSEEG15Model": DixiSEEG15Parameters, + "DixiSEEGModel": DixiSEEGParameters, "MedtronicModel": MedtronicParameters, "MedtronicSenSightModel": MedtronicParameters, "MicroElectrodeModel": MicroElectrodeParameters, @@ -137,12 +146,14 @@ "BostonScientificVerciseDirectedModel", "BostonScientificVerciseParameters", "BostonScientificVerciseDirectedParameters", + "DixiSEEG5", + "DixiSEEG8", "DixiSEEG10", + "DixiSEEG12", "DixiSEEG15", - "DixiSEEG10Model", - "DixiSEEG15Model", - "DixiSEEG10Parameters", - "DixiSEEG15Parameters", + "DixiSEEG18", + "DixiSEEGModel", + "DixiSEEGParameters", "Medtronic3387", "Medtronic3389", "Medtronic3391", diff --git a/ossdbs/electrodes/defaults.py b/ossdbs/electrodes/defaults.py index 62ffe1b4..6e38b46f 100644 --- a/ossdbs/electrodes/defaults.py +++ b/ossdbs/electrodes/defaults.py @@ -11,10 +11,8 @@ BostonScientificVerciseParameters, ) from .dixi_microtechniques import ( - DixiSEEG10Model, - DixiSEEG10Parameters, - DixiSEEG15Model, - DixiSEEG15Parameters, + DixiSEEGModel, + DixiSEEGParameters, ) from .medtronic import ( MedtronicModel, @@ -177,19 +175,53 @@ lead_diameter=1.3, total_length=400.0, ), - "DixiSEEG10": DixiSEEG10Parameters( + "DixiSEEG5": DixiSEEGParameters( tip_length=0.8, contact_length=2.0, contact_spacing=1.5, lead_diameter=0.8, total_length=400.0, + n_contacts=5, ), - "DixiSEEG15": DixiSEEG15Parameters( + "DixiSEEG8": DixiSEEGParameters( tip_length=0.8, contact_length=2.0, contact_spacing=1.5, lead_diameter=0.8, total_length=400.0, + n_contacts=8, + ), + "DixiSEEG10": DixiSEEGParameters( + tip_length=0.8, + contact_length=2.0, + contact_spacing=1.5, + lead_diameter=0.8, + total_length=400.0, + n_contacts=10, + ), + "DixiSEEG12": DixiSEEGParameters( + tip_length=0.8, + contact_length=2.0, + contact_spacing=1.5, + lead_diameter=0.8, + total_length=400.0, + n_contacts=12, + ), + "DixiSEEG15": DixiSEEGParameters( + tip_length=0.8, + contact_length=2.0, + contact_spacing=1.5, + lead_diameter=0.8, + total_length=400.0, + n_contacts=15, + ), + "DixiSEEG18": DixiSEEGParameters( + tip_length=0.8, + contact_length=2.0, + contact_spacing=1.5, + lead_diameter=0.8, + total_length=400.0, + n_contacts=18, ), } @@ -329,15 +361,43 @@ def PINSMedicalL303( return PINSMedicalModel(parameters, rotation, direction, position) +def DixiSEEG5( + rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) +): + parameters = default_electrode_parameters["DixiSEEG5"] + return DixiSEEGModel(parameters, rotation, direction, position) + + +def DixiSEEG8( + rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) +): + parameters = default_electrode_parameters["DixiSEEG8"] + return DixiSEEGModel(parameters, rotation, direction, position) + + def DixiSEEG10( rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) ): parameters = default_electrode_parameters["DixiSEEG10"] - return DixiSEEG10Model(parameters, rotation, direction, position) + return DixiSEEGModel(parameters, rotation, direction, position) + + +def DixiSEEG12( + rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) +): + parameters = default_electrode_parameters["DixiSEEG12"] + return DixiSEEGModel(parameters, rotation, direction, position) def DixiSEEG15( rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) ): parameters = default_electrode_parameters["DixiSEEG15"] - return DixiSEEG15Model(parameters, rotation, direction, position) + return DixiSEEGModel(parameters, rotation, direction, position) + + +def DixiSEEG18( + rotation: float = 0, direction: tuple = (0, 0, 1), position: tuple = (0, 0, 0) +): + parameters = default_electrode_parameters["DixiSEEG18"] + return DixiSEEGModel(parameters, rotation, direction, position) diff --git a/ossdbs/electrodes/dixi_microtechniques.py b/ossdbs/electrodes/dixi_microtechniques.py index 4b933617..3794585a 100644 --- a/ossdbs/electrodes/dixi_microtechniques.py +++ b/ossdbs/electrodes/dixi_microtechniques.py @@ -9,13 +9,14 @@ @dataclass -class DixiSEEG10Parameters: +class DixiSEEGParameters: # dimensions [mm] tip_length: float contact_length: float contact_spacing: float lead_diameter: float total_length: float + n_contacts: int def get_center_first_contact(self) -> float: """Returns distance between electrode tip and center of first contact.""" @@ -23,11 +24,11 @@ def get_center_first_contact(self) -> float: def get_distance_l1_l4(self) -> float: """Returns distance between first level contact and fourth level contact.""" - return 9 * (self.contact_length + self.contact_spacing) + return 3 * (self.contact_length + self.contact_spacing) -class DixiSEEG10Model(ElectrodeModel): - """Dixi Microtechniques SEEG10 electrode. +class DixiSEEGModel(ElectrodeModel): + """Dixi Microtechniques SEEG electrode. Attributes ---------- @@ -44,7 +45,9 @@ class DixiSEEG10Model(ElectrodeModel): Position vector (x,y,z) of electrode tip. """ - _n_contacts = 10 + _n_contacts = 18 # TODO set to actual value from each dataclass + # def __init__(self, parameters: DixiSEEGParameters, rotation: float, direction: tuple, position: tuple): + # self._n_contacts = parameters.n_contacts def parameter_check(self): # Check to ensure that all parameters are at least 0 @@ -108,139 +111,7 @@ def __contacts(self) -> netgen.libngpy._NgOCC.TopoDS_Shape: contact_cyl = occ.Cylinder(p=(0, 0, 0), d=direction, r=radius, h=height) contacts = [] - for count in range(self._n_contacts): - name = self._boundaries[f"Contact_{count + 1}"] - contact.bc(name) - min_edge_z_val = float("inf") - max_edge_z_val = float("-inf") - for edge in contact.edges: - if edge.center.z < min_edge_z_val: - min_edge_z_val = edge.center.z - min_edge = edge - if edge.center.z > max_edge_z_val: - max_edge_z_val = edge.center.z - max_edge = edge - # Only name edge with the max z value for contact_1 - max_edge.name = name - # first contact is different from others - if count == 0: - distance = ( - self._parameters.contact_length + self._parameters.contact_spacing - ) - contacts.append(contact) - contact = contact_cyl - else: - min_edge.name = name - vector = tuple(np.array(self._direction) * distance) - contacts.append(contact.Move(vector)) - distance += ( - self._parameters.contact_length + self._parameters.contact_spacing - ) - - return occ.Glue(contacts) - - -@dataclass -class DixiSEEG15Parameters: - # dimensions [mm] - tip_length: float - contact_length: float - contact_spacing: float - lead_diameter: float - total_length: float - - def get_center_first_contact(self) -> float: - """Returns distance between electrode tip and center of first contact.""" - return 0.5 * self.contact_length - - def get_distance_l1_l4(self) -> float: - """Returns distance between first level contact and fourth level contact.""" - return 14 * (self.contact_length + self.contact_spacing) - - -class DixiSEEG15Model(ElectrodeModel): - """Dixi Microtechniques SEEG10 electrode. - - Attributes - ---------- - parameters : DixiSEEGParameters - Parameters for Dixi Microtechniques SEEG electrode geometry. - - rotation : float - Rotation angle in degree of electrode. - - direction : tuple - Direction vector (x,y,z) of electrode. - - position : tuple - Position vector (x,y,z) of electrode tip. - """ - - _n_contacts = 15 - - def parameter_check(self): - # Check to ensure that all parameters are at least 0 - for param in asdict(self._parameters).values(): - if param < 0: - raise ValueError("Parameter values cannot be less than zero") - - def _construct_encapsulation_geometry( - self, thickness: float - ) -> netgen.libngpy._NgOCC.TopoDS_Shape: - """Generate geometry of encapsulation layer around electrode. - - Parameters - ---------- - thickness : float - Thickness of encapsulation layer. - - Returns - ------- - netgen.libngpy._NgOCC.TopoDS_Shape - """ - center = tuple(np.array(self._direction) * self._parameters.lead_diameter * 0.5) - radius = self._parameters.lead_diameter * 0.5 + thickness - height = self._parameters.total_length - self._parameters.tip_length - tip = occ.Sphere(c=center, r=radius) - lead = occ.Cylinder(p=center, d=self._direction, r=radius, h=height) - encapsulation = tip + lead - encapsulation.bc("EncapsulationLayerSurface") - encapsulation.mat("EncapsulationLayer") - return encapsulation.Move(v=self._position) - self.geometry - - def _construct_geometry(self) -> netgen.libngpy._NgOCC.TopoDS_Shape: - contacts = self.__contacts() - # TODO check - electrode = netgen.occ.Glue([self.__body() - contacts, contacts]) - return electrode.Move(v=self._position) - - def __body(self) -> netgen.libngpy._NgOCC.TopoDS_Shape: - radius = self._parameters.lead_diameter * 0.5 - center = tuple(np.array(self._direction) * radius) - tip = occ.Sphere(c=center, r=radius) - height = self._parameters.total_length - self._parameters.tip_length - lead = occ.Cylinder(p=center, d=self._direction, r=radius, h=height) - body = tip + lead - body.bc(self._boundaries["Body"]) - return body - - def __contacts(self) -> netgen.libngpy._NgOCC.TopoDS_Shape: - radius = self._parameters.lead_diameter * 0.5 - direction = self._direction - - center = tuple(np.array(direction) * radius) - # define half space at tip_center to use to construct a hemsiphere as part of the contact tip - half_space = netgen.occ.HalfSpace(p=center, n=direction) - contact_tip = occ.Sphere(c=center, r=radius) * half_space - h_pt2 = self._parameters.contact_length - radius - contact_pt2 = occ.Cylinder(p=center, d=direction, r=radius, h=h_pt2) - # defining first contact - contact = contact_tip + contact_pt2 - height = self._parameters.contact_length - contact_cyl = occ.Cylinder(p=(0, 0, 0), d=direction, r=radius, h=height) - - contacts = [] - for count in range(self._n_contacts): + for count in range(self._parameters.n_contacts): name = self._boundaries[f"Contact_{count + 1}"] contact.bc(name) min_edge_z_val = float("inf")