Skip to content

Commit

Permalink
Merge pull request #869 from rmatsuda/sub-list-ctrl-for-brain-targets
Browse files Browse the repository at this point in the history
Create a sub list ctrl for brain targets for mTMS
  • Loading branch information
rmatsuda authored Dec 16, 2024
2 parents 78b5f41 + edc6bec commit cb0f548
Show file tree
Hide file tree
Showing 10 changed files with 565 additions and 62 deletions.
28 changes: 21 additions & 7 deletions invesalius/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,9 +802,23 @@
Z_OFFSET_COLUMN = 5
POINT_OF_INTEREST_TARGET_COLUMN = 6
MEP_COLUMN = 7
X_COLUMN = 8
Y_COLUMN = 9
Z_COLUMN = 10
UUID = 8
X_COLUMN = 9
Y_COLUMN = 10
Z_COLUMN = 11

# The column order in the brain marker panel
#
BRAIN_ID_COLUMN = 0
BRAIN_SESSION_COLUMN = 1
BRAIN_MARKER_TYPE_COLUMN = 2
BRAIN_LABEL_COLUMN = 3
BRAIN_MEP_COLUMN = 4
BRAIN_X_MTMS = 5
BRAIN_Y_MTMS = 6
BRAIN_R_MTMS = 7
BRAIN_INTENSITY_MTMS = 8
BRAIN_UUID = 9

# ------------ Navigation defaults -------------------

Expand Down Expand Up @@ -1049,10 +1063,10 @@
"max": (1.0, 0.0, 0.0),
},
"Viridis": { # Viridis (perceptually uniform)
"min": (0.267004, 0.004874, 0.329415),
"low": (0.226337, 0.31071, 0.577055),
"mid": (0.993248, 0.906157, 0.143936),
"max": (0.968627, 0.813008, 0.0),
"min": (0.267, 0.004, 0.329),
"low": (0.192, 0.408, 0.556),
"mid": (0.137, 0.718, 0.475),
"max": (0.993, 0.906, 0.144),
},
"Grayscale": { # Grayscale (often used for CT/MRI)
"min": (0.0, 0.0, 0.0), # Black
Expand Down
31 changes: 28 additions & 3 deletions invesalius/data/markers/marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ class Marker:
is_target: bool = False
is_point_of_interest: bool = False
session_id: int = 1
x_cortex: float = 0
y_cortex: float = 0
z_cortex: float = 0
x_cortex: float = dataclasses.field(default=None)
y_cortex: float = dataclasses.field(default=None)
z_cortex: float = dataclasses.field(default=None)
alpha_cortex: float = dataclasses.field(default=None)
beta_cortex: float = dataclasses.field(default=None)
gamma_cortex: float = dataclasses.field(default=None)
Expand All @@ -78,6 +78,7 @@ class Marker:
# #TODO: add a reference to original coil marker to relate it to MEP
# in micro Volts (but scale in milli Volts for display)
mep_value: float = dataclasses.field(default=None)
brain_target_list: list = dataclasses.field(default_factory=list)

# x, y, z can be jointly accessed as position
@property
Expand Down Expand Up @@ -209,6 +210,24 @@ def to_csv_row(self):
)
return res

def to_brain_targets_dict(self):
return {
"position": self.position,
"orientation": self.orientation,
"colour": self.colour,
"size": self.size,
"label": self.label,
"is_target": self.is_target,
"marker_type": self.marker_type.value,
"session_id": self.session_id,
"mep_value": self.mep_value,
"marker_uuid": self.marker_uuid,
"x_mtms": self.x_mtms,
"y_mtms": self.y_mtms,
"r_mtms": self.r_mtms,
"intensity_mtms": self.intensity_mtms,
}

def to_dict(self):
return {
"position": self.position,
Expand All @@ -225,6 +244,8 @@ def to_dict(self):
"z_rotation": self.z_rotation,
"z_offset": self.z_offset,
"mep_value": self.mep_value,
"brain_target_list": self.brain_target_list,
"marker_uuid": self.marker_uuid,
}

def from_dict(self, d):
Expand Down Expand Up @@ -270,6 +291,8 @@ def from_dict(self, d):
z_rotation = d.get("z_rotation", 0.0)
is_point_of_interest = d.get("is_point_of_interest", False)
mep_value = d.get("mep_value", None)
brain_target_list = d.get("brain_target_list", [])
marker_uuid = d.get("marker_uuid", "")

self.size = d["size"]
self.label = d["label"]
Expand All @@ -286,6 +309,8 @@ def from_dict(self, d):
self.z_offset = z_offset
self.z_rotation = z_rotation
self.mep_value = mep_value
self.brain_target_list = brain_target_list
self.marker_uuid = marker_uuid

return self

Expand Down
7 changes: 4 additions & 3 deletions invesalius/data/visualization/coil_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def ShowCoil(self, state, coil_name=None):

if self.target_coil_actor is not None:
self.target_coil_actor.SetVisibility(state)
# self.vector_field_assembly.SetVisibility(state) # LUKATODO: Keep this hidden for now
self.vector_field_assembly.SetVisibility(state) # LUKATODO: Keep this hidden for now

if not self.is_navigating:
Publisher.sendMessage("Render volume viewer")
Expand Down Expand Up @@ -262,7 +262,7 @@ def AddCoil(self, coil_name, coil_path):
self.coils[coil_name]["path"] = coil_path

# LUKATODO: Vector field assembly follows a different pattern for addition, should unify.
# self.vector_field_assembly.SetVisibility(1)
self.vector_field_assembly.SetVisibility(1)

def RemoveCoil(self, coil_name=None):
if coil_name is not None:
Expand Down Expand Up @@ -301,4 +301,5 @@ def UpdateCoilPoses(self, m_imgs, coords):
self.coils[name]["center_actor"].SetUserMatrix(m_img_vtk)

# LUKATODO
# self.vector_field_assembly.SetUserMatrix(m_img_vtk)

self.vector_field_assembly.SetUserMatrix(m_img_vtk)
32 changes: 21 additions & 11 deletions invesalius/data/visualization/marker_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,33 @@ def __bind_events(self):
Publisher.subscribe(self.UnsetTarget, "Unset target")
Publisher.subscribe(self.SetTargetTransparency, "Set target transparency")
Publisher.subscribe(self.SetCoilAtTarget, "Coil at target")
Publisher.subscribe(self.UpdateVectorField, "Update vector field")
Publisher.subscribe(self.UpdateBrainTargets, "Update brain targets")
Publisher.subscribe(self.UpdateNavigationStatus, "Navigation status")
Publisher.subscribe(self.UpdateTargetMode, "Set target mode")
Publisher.subscribe(
self.UpdateVectorFieldAssemblyVisibility, "Set vector field assembly visibility"
)

def UpdateNavigationStatus(self, nav_status, vis_status):
self.is_navigating = nav_status

def UpdateTargetMode(self, enabled=False):
self.is_target_mode = enabled

def UpdateVectorField(self):
def UpdateVectorFieldAssemblyVisibility(self, enabled=False):
self.vector_field_assembly.SetVisibility(enabled)
# If not navigating, render the scene.
if not self.is_navigating:
self.interactor.Render()

def UpdateBrainTargets(self, brain_targets):
"""
Update the vector field assembly to reflect the current vector field.
"""
# Create a new vector field assembly.
new_vector_field_assembly = self.vector_field_visualizer.CreateVectorFieldAssembly()

new_vector_field_assembly = self.vector_field_visualizer.CreateVectorFieldAssembly(
brain_targets
)
# Replace the old vector field assembly with the new one.
self.actor_factory.ReplaceActor(
self.renderer, self.vector_field_assembly, new_vector_field_assembly
Expand Down Expand Up @@ -446,13 +456,13 @@ def HighlightMarker(self, marker, render=True):

# If the marker is a coil target, show the vector field assembly and update its position and orientation,
# otherwise, hide the vector field assembly.
if marker_type == MarkerType.COIL_TARGET:
self.vector_field_assembly.SetVisibility(1)

self.vector_field_assembly.SetPosition(position_flipped)
self.vector_field_assembly.SetOrientation(orientation)
else:
self.vector_field_assembly.SetVisibility(0)
# if marker_type == MarkerType.COIL_TARGET:
# self.vector_field_assembly.SetVisibility(1)
#
# self.vector_field_assembly.SetPosition(position_flipped)
# self.vector_field_assembly.SetOrientation(orientation)
# else:
# self.vector_field_assembly.SetVisibility(0)

# Return early if the marker is a target and the coil is at the target.
#
Expand Down
48 changes: 48 additions & 0 deletions invesalius/data/visualization/mep_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def __bind_events(self):
# Publisher.subscribe(self.UpdateMEPPoints, "Update marker list")
# Publisher.subscribe(self.SetBrainSurface, "Set MEP brain surface")
Publisher.subscribe(self.UpdateMEPPoints, "Redraw MEP mapping")
Publisher.subscribe(
self.UpdateMEPPointsFromBrainTargets, "Redraw MEP mapping from brain targets"
)
Publisher.subscribe(self.UpdateNavigationStatus, "Navigation status")
Publisher.subscribe(self.SetBrainSurface, "Load brain surface actor")
Publisher.subscribe(self.OnCloseProject, "Close project data")
Expand Down Expand Up @@ -131,6 +134,7 @@ def DisplayMotorMap(self, show: bool):
self.UpdateVisualization()
progress_dialog.Update(value=50, msg="Preparing brain surface...")
self.UpdateMEPPoints()
self.UpdateMEPPointsFromBrainTargets()
progress_dialog.Close()
else:
self._config_params["mep_enabled"] = False
Expand Down Expand Up @@ -353,6 +357,50 @@ def UpdateMEPPoints(self):
if self._config_params["mep_enabled"]:
self.UpdateVisualization()

def UpdateMEPPointsFromBrainTargets(self):
"""
Updates or creates the point data with MEP values from a list of markers.
Args:
markers (List[Marker]): The list of marker objects to add/update points for.
clear_old (bool, default=False): If True, clears all existing points before updating.
"""
if not self._config_params["mep_enabled"]:
return
if not self.surface:
return
brain_markers = []
for marker in MarkersControl().list:
if marker.brain_target_list:
for m in marker.brain_target_list:
brain_markers.append(m)

if not brain_markers:
self.points = vtkPolyData()
self.UpdateVisualization()
return

points = vtkPoints()

point_data = self.points.GetPointData()
mep_array = vtkDoubleArray()
mep_array.SetName("MEP")
point_data.AddArray(mep_array)

for marker in brain_markers:
points.InsertNextPoint(
marker["position"][0], -marker["position"][1], marker["position"][2]
)
mep_value = marker["mep_value"] or 0
mep_array.InsertNextValue(mep_value)
MarkersControl().SaveState()

self.points.SetPoints(points)
self.points.GetPointData().SetActiveScalars("MEP")
self.points.Modified()
if self._config_params["mep_enabled"]:
self.UpdateVisualization()

def UpdateVisualization(self):
if not self._config_params["mep_enabled"] or not self.surface:
return
Expand Down
6 changes: 4 additions & 2 deletions invesalius/data/visualization/vector_field_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ def SetVectorField(self, vector_field):
self.vector_field = vector_field
Publisher.sendMessage("Update vector field")

def CreateVectorFieldAssembly(self):
def CreateVectorFieldAssembly(self, vector_field=None):
"""
Create an assembly for the current vector field.
"""
if not vector_field:
vector_field = self.vector_field
assembly = vtk.vtkAssembly()

actors = [
Expand All @@ -40,7 +42,7 @@ def CreateVectorFieldAssembly(self):
colour=vector["color"],
length_multiplier=vector["length"],
)
for vector in self.vector_field
for vector in vector_field
]

for actor in actors:
Expand Down
4 changes: 2 additions & 2 deletions invesalius/gui/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1207,9 +1207,9 @@ def ShowEnterMarkerID(default: str) -> str:
def ShowEnterMEPValue(default):
msg = _("Enter the MEP value (uV)")
if sys.platform == "darwin":
dlg = wx.TextEntryDialog(None, "", msg, defaultValue=default)
dlg = wx.TextEntryDialog(None, "", msg, defaultValue=str(default))
else:
dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", value=default)
dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", value=str(default))
dlg.ShowModal()
result = dlg.GetValue()
# check if the value is a number
Expand Down
Loading

0 comments on commit cb0f548

Please sign in to comment.