Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions OpenLIFUData/OpenLIFUData.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
ensure_list,
replace_widget,
)
from OpenLIFULib.volume_thresholding import load_volume_and_threshold_background
from OpenLIFULib.virtual_fit_results import (
add_virtual_fit_results_from_openlifu_session_format,
clear_virtual_fit_results,
Expand Down Expand Up @@ -2301,7 +2302,7 @@ def load_volume_from_openlifu(self, volume_dir: Path, volume_metadata: Dict):
slicer.mrmlScene.RemoveNode(loaded_volumes[idx])

volume_filepath = Path(volume_dir,volume_metadata['data_filename'])
loadedVolumeNode = slicer.util.loadVolume(volume_filepath)
loadedVolumeNode = load_volume_and_threshold_background(volume_filepath)
# Note: OnNodeAdded/updateLoadedObjectsView is called before openLIFU metadata is assigned to the node so need
# call updateLoadedObjectsView again to display openlifu name/id.
assign_openlifu_metadata_to_volume_node(loadedVolumeNode, volume_metadata)
Expand All @@ -2313,7 +2314,7 @@ def load_volume_from_file(self, filepath: str) -> None:
parent_dir = Path(filepath).parent
# Load volume using use slicer default volume name and id based on filepath
if slicer.app.coreIOManager().fileType(filepath) == 'VolumeFile':
slicer.util.loadVolume(filepath)
load_volume_and_threshold_background(filepath)

# If the user selects a json file, infer volume filepath information based on the volume_metadata.
elif Path(filepath).suffix == '.json':
Expand Down
1 change: 1 addition & 0 deletions OpenLIFULib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(MODULE_PYTHON_SCRIPTS
OpenLIFULib/transducer_tracking_wizard_utils.py
OpenLIFULib/events.py
OpenLIFULib/notifications.py
OpenLIFULib/volume_thresholding.py
)

set(MODULE_PYTHON_RESOURCES
Expand Down
3 changes: 2 additions & 1 deletion OpenLIFULib/OpenLIFULib/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from slicer.parameterNodeWrapper import parameterPack
from OpenLIFULib.util import get_openlifu_data_parameter_node
from OpenLIFULib.volume_thresholding import load_volume_and_threshold_background
from OpenLIFULib.lazyimport import openlifu_lz
from OpenLIFULib.parameter_node_utils import SlicerOpenLIFUSessionWrapper, SlicerOpenLIFUPhotoscanWrapper
from OpenLIFULib.targets import (
Expand Down Expand Up @@ -147,7 +148,7 @@ def initialize_from_openlifu_session(
"""

# Load volume
volume_node = slicer.util.loadVolume(volume_info['data_abspath'])
volume_node = load_volume_and_threshold_background(volume_info['data_abspath'])
assign_openlifu_metadata_to_volume_node(volume_node, volume_info)

# Load targets
Expand Down
2 changes: 1 addition & 1 deletion OpenLIFULib/OpenLIFULib/skinseg.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ def get_skin_segmentation(volume : Union[vtkMRMLScalarVolumeNode, str]) -> vtkMR
elif len(skin_mesh_node) > 1:
raise RuntimeError(f"Found multiple skin segmentation models affiliated with volume {volume_id}")
else:
return skin_mesh_node[0]
return skin_mesh_node[0]
48 changes: 48 additions & 0 deletions OpenLIFULib/OpenLIFULib/volume_thresholding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Volume thresholding tools"""

import logging
import vtk
import slicer
from slicer import vtkMRMLScalarVolumeNode
from OpenLIFULib.util import BusyCursor
from OpenLIFULib.lazyimport import openlifu_lz

def cast_volume_to_float(volume_node:vtkMRMLScalarVolumeNode) -> None:
"""Converts a volume node to float, replacing the underlying vtkImageData."""
image_cast = vtk.vtkImageCast()
image_cast.SetInputData(volume_node.GetImageData())
image_cast.SetOutputScalarTypeToDouble()
image_cast.Update()
volume_node.SetAndObserveImageData(image_cast.GetOutput())

# I am not certain that the display node will know to update itself to handle the new image data type,
# so I hope poking `CreateDefaultDisplayNodes` here makes it do the right thing. If it's not needed then it's harmless anyway:
volume_node.CreateDefaultDisplayNodes()

def threshold_volume_by_foreground_mask(volume_node:vtkMRMLScalarVolumeNode) -> None:
"""Compute the foreground mask for a loaded volume and threshold the volume to strip out the background.
This modifies the values of the background region in the volume and sets them to 1 less than the minimum value in the volume.
This way we can simply enable volume thresholding to remove
It can take a moment to actually compute the foreground mask.
"""
volume_array = slicer.util.arrayFromVolume(volume_node)
volume_array_min = volume_array.min()

background_value = volume_array_min - 1
if background_value < volume_node.GetImageData().GetScalarTypeMin(): # e.g. if volume_array_min is 0 and it's an unsigned int type
logging.info("Casting volume to float for the sake of `threshold_volume_by_foreground_mask`.")
cast_volume_to_float(volume_node)
Comment on lines +28 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I addressed the problem you pointed out: if the value I am trying to use for background (e.g. -1) is less than the minimum for the data type (e.g. 0 for unsigned integer types), then we cast to float before proceeding.


foreground_mask = openlifu_lz().seg.skinseg.compute_foreground_mask(volume_array)
slicer.util.arrayFromVolume(volume_node)[~foreground_mask] = background_value
volume_node.GetDisplayNode().SetThreshold(volume_array_min,volume_array.max())
volume_node.GetDisplayNode().SetApplyThreshold(1)
volume_node.GetDisplayNode().SetAutoThreshold(0)
volume_node.Modified()

def load_volume_and_threshold_background(volume_filepath) -> vtkMRMLScalarVolumeNode:
"""Load a volume node from file, and also set the background values to a certain value that can be threshoded out, and threshold it out."""
volume_node = slicer.util.loadVolume(volume_filepath)
with BusyCursor():
threshold_volume_by_foreground_mask(volume_node)
return volume_node