Skip to content

Commit

Permalink
Merge pull request #825 from mo-gaafar/mep_visualization
Browse files Browse the repository at this point in the history
Neuronavigation: TMS Motor Mapping Visualization
  • Loading branch information
rmatsuda authored Sep 30, 2024
2 parents 8a97f49 + 6bddbd0 commit b0dbaf1
Show file tree
Hide file tree
Showing 13 changed files with 1,495 additions and 26 deletions.
Binary file added icons/brain_eye.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 85 additions & 5 deletions invesalius/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,9 +801,10 @@
TARGET_COLUMN = 4
Z_OFFSET_COLUMN = 5
POINT_OF_INTEREST_TARGET_COLUMN = 6
X_COLUMN = 7
Y_COLUMN = 8
Z_COLUMN = 9
MEP_COLUMN = 7
X_COLUMN = 8
Y_COLUMN = 9
Z_COLUMN = 10

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

Expand Down Expand Up @@ -1004,10 +1005,89 @@
}

MARKER_FILE_MAGICK_STRING = "##INVESALIUS3_MARKER_FILE_"
CURRENT_MARKER_FILE_VERSION = 3
SUPPORTED_MARKER_FILE_VERSIONS = [0, 1, 2, 3]
CURRENT_MARKER_FILE_VERSION = 4
SUPPORTED_MARKER_FILE_VERSIONS = [0, 1, 2, 3, 4]
WILDCARD_MARKER_FILES = _("Marker scanner coord files (*.mkss)|*.mkss")

# Motor mapping visualization

DEFAULT_MEP_CONFIG_PARAMS = {
"mep_enabled": False,
"enabled_once": False,
"threshold_down": 0,
"range_up": 1,
"brain_surface_index": None,
"mep_colormap": "Viridis",
"gaussian_sharpness": 0.4,
"gaussian_radius": 20,
"bounds": None,
"colormap_range_uv": {"min": 50, "low": 200, "mid": 600, "max": 1000},
}


MEP_COLORMAP_DEFINITIONS = {
"BlueCyanYellowRed": { # Blue, Cyan, Yellow, Red
"min": (0.0, 0.0, 1.0),
"low": (0.0, 1.0, 1.0),
"mid": (1.0, 1.0, 0.0),
"max": (1.0, 0.0, 0.0),
},
"GreenYellowOrangeRed": { # Green, Yellow, Orange, Red
"min": (0.0, 1.0, 0.0),
"low": (1.0, 1.0, 0.0),
"mid": (1.0, 0.647, 0.0),
"max": (1.0, 0.0, 0.0),
},
"PurpleBlueGreenYellow": { # Purple, Blue, Green, Yellow
"min": (0.5, 0.0, 0.5),
"low": (0.0, 0.0, 1.0),
"mid": (0.0, 1.0, 0.0),
"max": (1.0, 1.0, 0.0),
},
"BlackGrayWhiteRed": { # Black, Gray, White, Red (grayscale with highlight)
"min": (0.0, 0.0, 0.0),
"low": (0.5, 0.5, 0.5),
"mid": (1.0, 1.0, 1.0),
"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),
},
"Grayscale": { # Grayscale (often used for CT/MRI)
"min": (0.0, 0.0, 0.0), # Black
"low": (0.25, 0.25, 0.25), # Dark Gray
"mid": (0.75, 0.75, 0.75), # Light Gray
"max": (1.0, 1.0, 1.0), # White
},
"HotMetal": { # Hot Metal (useful for highlighting hot spots)
"min": (0.0, 0.0, 0.0), # Black
"low": (0.5, 0.0, 0.0), # Dark Red
"mid": (1.0, 0.5, 0.0), # Orange
"max": (1.0, 1.0, 1.0), # White
},
"Rainbow": { # Rainbow (although not perceptually uniform, still common)
"min": (0.0, 0.0, 1.0), # Blue
"low": (0.0, 1.0, 0.0), # Green
"mid": (1.0, 1.0, 0.0), # Yellow
"max": (1.0, 0.0, 0.0), # Red
},
"Bone": { # Bone (specifically designed for CT bone visualization)
"min": (0.0, 0.0, 0.0), # Black
"low": (0.388, 0.224, 0.0), # Brown
"mid": (0.902, 0.827, 0.631), # Beige
"max": (1.0, 1.0, 1.0), # White
},
"InvertedGrayscale": { # Inverted Grayscale (sometimes used for PET)
"min": (1.0, 1.0, 1.0), # White
"low": (0.75, 0.75, 0.75), # Light Gray
"mid": (0.25, 0.25, 0.25), # Dark Gray
"max": (0.0, 0.0, 0.0), # Black
},
}

# Keycodes for moving markers using the keyboard
MOVE_MARKER_LEFT_KEYCODE = 65 # A
MOVE_MARKER_RIGHT_KEYCODE = 68 # D
Expand Down
33 changes: 16 additions & 17 deletions invesalius/data/markers/marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class Marker:
z_offset: float = 0.0
visualization: dict = dataclasses.field(default_factory=dict)
marker_uuid: str = ""
# #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)

# x, y, z can be jointly accessed as position
@property
Expand Down Expand Up @@ -160,7 +163,11 @@ def to_csv_header(cls):
res = [
field.name
for field in dataclasses.fields(cls)
if (field.name != "version" and field.name != "marker_uuid")
if (
field.name != "version"
and field.name != "marker_uuid"
and field.name != "visualization"
)
]
res.extend(["x_world", "y_world", "z_world", "alpha_world", "beta_world", "gamma_world"])
return "\t".join(map(lambda x: f'"{x}"', res))
Expand Down Expand Up @@ -217,6 +224,7 @@ def to_dict(self):
"cortex_position_orientation": self.cortex_position_orientation,
"z_rotation": self.z_rotation,
"z_offset": self.z_offset,
"mep_value": self.mep_value,
}

def from_dict(self, d):
Expand Down Expand Up @@ -245,33 +253,23 @@ def from_dict(self, d):
else:
marker_type = MarkerType.COIL_TARGET.value

if all(
[
k in d
for k in [
"x_cortex",
"y_cortex",
"z_cortex",
"alpha_cortex",
"beta_cortex",
"gamma_cortex",
]
]
):
cortex_position_orientation = [
cortex_position_orientation = (
d["cortex_position_orientation"]
if "cortex_position_orientation" in d
else [
d["x_cortex"],
d["y_cortex"],
d["z_cortex"],
d["alpha_cortex"],
d["beta_cortex"],
d["gamma_cortex"],
]
else:
cortex_position_orientation = [None, None, None, None, None, None]
)

z_offset = d.get("z_offset", 0.0)
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)

self.size = d["size"]
self.label = d["label"]
Expand All @@ -287,6 +285,7 @@ def from_dict(self, d):
self.cortex_position_orientation = cortex_position_orientation
self.z_offset = z_offset
self.z_rotation = z_rotation
self.mep_value = mep_value

return self

Expand Down
15 changes: 15 additions & 0 deletions invesalius/data/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def __bind_events(self):
Publisher.subscribe(self.OnSplitSurface, "Split surface")
Publisher.subscribe(self.OnLargestSurface, "Create surface from largest region")
Publisher.subscribe(self.OnSeedSurface, "Create surface from seeds")
Publisher.subscribe(self.GetVisibleSurfaceActor, "Get visible surface actor")

Publisher.subscribe(self.OnDuplicate, "Duplicate surfaces")
Publisher.subscribe(self.OnRemove, "Remove surfaces")
Expand Down Expand Up @@ -1129,6 +1130,20 @@ def OnChangeSurfaceName(self, index, name):
def OnShowSurface(self, index, visibility):
self.ShowActor(index, visibility)

def GetVisibleSurfaceActor(self):
"""
Gets the first visible surface actor.
"""
index = 0
for key in self.actors_dict:
if self.actors_dict[key].GetVisibility():
index = key
break

Publisher.sendMessage(
"Load visible surface actor", actor=self.actors_dict[index], index=index
)

def ShowActor(self, index, value):
"""
Show or hide actor, according to given actor index and value.
Expand Down
Loading

0 comments on commit b0dbaf1

Please sign in to comment.