Skip to content
Draft
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
14 changes: 8 additions & 6 deletions OpenLIFUData/OpenLIFUData.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
if TYPE_CHECKING:
import openlifu
import openlifu.nav.photoscan
import openlifu.plan
import openlifu.xdc
from OpenLIFUHome.OpenLIFUHome import OpenLIFUHomeLogic
from OpenLIFUPrePlanning.OpenLIFUPrePlanning import OpenLIFUPrePlanningWidget

Expand Down Expand Up @@ -1286,7 +1288,7 @@ def updateLoadedObjectsView(self):
self.loadedObjectsItemModel.appendRow(row)
for transducer_slicer in parameter_node.loaded_transducers.values():
transducer_slicer : SlicerOpenLIFUTransducer
transducer_openlifu : "openlifu.Transducer" = transducer_slicer.transducer.transducer
transducer_openlifu : "openlifu.xdc.Transducer" = transducer_slicer.transducer.transducer
row = list(map(
create_noneditable_QStandardItem,
[transducer_openlifu.name, "Transducer", transducer_openlifu.id]
Expand Down Expand Up @@ -1415,7 +1417,7 @@ def updateSessionStatus(self):
loaded_session = self._parameterNode.loaded_session
session_openlifu : "openlifu.db.Session" = loaded_session.session.session
subject_openlifu = self.logic.get_subject(session_openlifu.subject_id)
protocol_openlifu : "openlifu.Protocol" = loaded_session.get_protocol().protocol
protocol_openlifu : "openlifu.plan.Protocol" = loaded_session.get_protocol().protocol

self.ui.sessionStatusSubjectNameIdValueLabel.setText(
f"{subject_openlifu.name} (ID: {session_openlifu.subject_id})"
Expand All @@ -1431,7 +1433,7 @@ def updateSessionStatus(self):
# Add a validity check here since this function call is triggered after a transducer is removed but
# before a session is invalidated.
if loaded_session.transducer_is_valid():
transducer_openlifu : "openlifu.Transducer" = loaded_session.get_transducer().transducer.transducer
transducer_openlifu : "openlifu.xdc.Transducer" = loaded_session.get_transducer().transducer.transducer
self.ui.sessionStatusTransducerValueLabel.setText(
f"{transducer_openlifu.name} (ID: {session_openlifu.transducer_id})"
)
Expand Down Expand Up @@ -2121,10 +2123,10 @@ def _on_transducer_transform_modified(self, transducer: SlicerOpenLIFUTransducer


def load_protocol_from_file(self, filepath:str) -> None:
protocol = openlifu_lz().Protocol.from_file(filepath)
protocol = openlifu_lz().plan.Protocol.from_file(filepath)
self.load_protocol_from_openlifu(protocol)

def load_protocol_from_openlifu(self, protocol:"openlifu.Protocol", replace_confirmed: bool = False) -> None:
def load_protocol_from_openlifu(self, protocol:"openlifu.plan.Protocol", replace_confirmed: bool = False) -> None:
"""Load an openlifu protocol object into the scene as a SlicerOpenLIFUProtocol,
adding it to the list of loaded openlifu objects. If there are
changes in the protocol config, also confirms user wants to discard
Expand Down Expand Up @@ -2188,7 +2190,7 @@ def load_transducer_from_file(self, filepath:str) -> None:

def load_transducer_from_openlifu(
self,
transducer: "openlifu.Transducer",
transducer: "openlifu.xdc.Transducer",
transducer_abspaths_info: dict = {},
transducer_matrix: Optional[np.ndarray]=None,
transducer_matrix_units: Optional[str]=None,
Expand Down
2 changes: 1 addition & 1 deletion OpenLIFUDatabase/OpenLIFUDatabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def load_database(self, path: Path) -> None:
Args:
path: Path to the openlifu database folder on disk.
"""
self.db = openlifu_lz().Database(path)
self.db = openlifu_lz().db.Database(path)
add_slicer_log_handler_for_openlifu_object(self.db)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion OpenLIFULib/OpenLIFULib/Resources/python-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
openlifu==v0.20.0
openlifu[app] @ git+https://github.com/OpenwaterHealth/openlifu-python.git@e479cbd3464c54cba2ea071144701fe8aa4e6eaf
bcrypt
threadpoolctl
requests
Expand Down
2 changes: 2 additions & 0 deletions OpenLIFULib/OpenLIFULib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from OpenLIFULib.lazyimport import (
openlifu_lz,
openlifu_sdk_lz,
xarray_lz,
bcrypt_lz,
threadpoolctl_lz,
Expand Down Expand Up @@ -43,6 +44,7 @@

__all__ = [
"openlifu_lz",
"openlifu_sdk_lz",
"xarray_lz",
"bcrypt_lz",
"threadpoolctl_lz",
Expand Down
25 changes: 22 additions & 3 deletions OpenLIFULib/OpenLIFULib/lazyimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ def check_and_install_kwave_binaries() -> bool:
Returns whether they were successfully installed (or just already present).
This assumes that openlifu can be imported already, so do not call this function until after that is assured.
"""
import openlifu
kwave_paths = openlifu.util.assets.get_kwave_paths()
from openlifu.util.assets import get_kwave_paths
kwave_paths = get_kwave_paths()
if all(p.exists() for p,_ in kwave_paths):
return True

Expand Down Expand Up @@ -144,6 +144,18 @@ def openlifu_lz() -> "openlifu":

with BusyCursor():
import openlifu
import openlifu_sdk
import openlifu.bf
import openlifu.db
import openlifu.geo
import openlifu.nav.photoscan
import openlifu.plan
import openlifu.seg.seg_methods
import openlifu.seg.skinseg
import openlifu.sim
import openlifu.util.assets
import openlifu.xdc
import openlifu.xdc.util

if slicer.app.testingEnabled():
# Ensure kwave assets are present (no-op if already installed)
Expand Down Expand Up @@ -181,4 +193,11 @@ def segno_lz() -> "segno":
if "segno" not in sys.modules:
check_and_install_python_requirements(prompt_if_found=False)
import segno
return sys.modules["segno"]
return sys.modules["segno"]

def openlifu_sdk_lz() -> "openlifu_sdk":
"""Import openlifu_sdk and return the module. openlifu_sdk is installed as a dependency
of openlifu, so openlifu_lz() must be called first to ensure it is available."""
if "openlifu_sdk" not in sys.modules:
openlifu_lz()
return sys.modules["openlifu_sdk"]
42 changes: 22 additions & 20 deletions OpenLIFULib/OpenLIFULib/parameter_node_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,53 @@
if TYPE_CHECKING:
import openlifu # This import is deferred at runtime, but it is done here for IDE and static analysis purposes
import openlifu.db
import openlifu.geo
import openlifu.plan
import openlifu.nav.photoscan
import openlifu.xdc
import xarray


# This very thin wrapper around openlifu.Protocol is needed to do our lazy importing of openlifu
# This very thin wrapper around openlifu.plan.Protocol is needed to do our lazy importing of openlifu
# while still providing type annotations that the parameter node wrapper can use.
# If we tried to make openlifu.Protocol directly supported as a type by parameter nodes, we would
# If we tried to make openlifu.plan.Protocol directly supported as a type by parameter nodes, we would
# get errors from parameterNodeWrapper as it tries to use typing.get_type_hints. This fails because
# get_type_hints tries to *evaluate* the type annotations like "openlifu.Protocol" possibly before
# get_type_hints tries to *evaluate* the type annotations like "openlifu.plan.Protocol" possibly before
# the user has installed openlifu, and possibly before the main window widgets exist that would allow
# an install prompt to even show up.
class SlicerOpenLIFUProtocol:
"""Ultrathin wrapper of openlifu.Protocol. This exists so that protocols can have parameter node
"""Ultrathin wrapper of openlifu.plan.Protocol. This exists so that protocols can have parameter node
support while we still do lazy-loading of openlifu."""
def __init__(self, protocol: "Optional[openlifu.Protocol]" = None):
def __init__(self, protocol: "Optional[openlifu.plan.Protocol]" = None):
self.protocol = protocol

# For the same reason we have a thin wrapper around openlifu.Transducer. But the name SlicerOpenLIFUTransducer
# For the same reason we have a thin wrapper around openlifu.xdc.Transducer. But the name SlicerOpenLIFUTransducer
# is reserved for the upcoming parameter pack.
class SlicerOpenLIFUTransducerWrapper:
"""Ultrathin wrapper of openlifu.Transducer. This exists so that transducers can have parameter node
"""Ultrathin wrapper of openlifu.xdc.Transducer. This exists so that transducers can have parameter node
support while we still do lazy-loading of openlifu."""
def __init__(self, transducer: "Optional[openlifu.Transducer]" = None):
def __init__(self, transducer: "Optional[openlifu.xdc.Transducer]" = None):
self.transducer = transducer

# For the same reason we have a thin wrapper around openlifu.Point
# For the same reason we have a thin wrapper around openlifu.geo.Point
class SlicerOpenLIFUPoint:
"""Ultrathin wrapper of openlifu.Point. This exists so that points can have parameter node
"""Ultrathin wrapper of openlifu.geo.Point. This exists so that points can have parameter node
support while we still do lazy-loading of openlifu."""
def __init__(self, point: "Optional[openlifu.Point]" = None):
def __init__(self, point: "Optional[openlifu.geo.Point]" = None):
self.point = point

# For the same reason we have a thin wrapper around openlifu.Session
# For the same reason we have a thin wrapper around openlifu.db.Session
class SlicerOpenLIFUSessionWrapper:
"""Ultrathin wrapper of openlifu.Session. This exists so that sessions can have parameter node
"""Ultrathin wrapper of openlifu.db.Session. This exists so that sessions can have parameter node
support while we still do lazy-loading of openlifu."""
def __init__(self, session: "Optional[openlifu.db.Session]" = None):
self.session = session

# For the same reason we have a thin wrapper around openlifu.Solution
# For the same reason we have a thin wrapper around openlifu.plan.Solution
class SlicerOpenLIFUSolutionWrapper:
"""Ultrathin wrapper of openlifu.Solution. This exists so that solutions can have parameter node
"""Ultrathin wrapper of openlifu.plan.Solution. This exists so that solutions can have parameter node
support while we still do lazy-loading of openlifu."""
def __init__(self, solution: "Optional[openlifu.Solution]" = None):
def __init__(self, solution: "Optional[openlifu.plan.Solution]" = None):
self.solution = solution

# For the same reason we have a thin wrapper around xarray.Dataset
Expand Down Expand Up @@ -173,7 +175,7 @@ def read(self, parameterNode: slicer.vtkMRMLScriptedModuleNode, name: str) -> Sl
Reads and returns the value with the given name from the parameterNode.
"""
json_string = parameterNode.GetParameter(name)
return SlicerOpenLIFUProtocol(openlifu_lz().Protocol.from_json(json_string))
return SlicerOpenLIFUProtocol(openlifu_lz().plan.Protocol.from_json(json_string))

@parameterNodeSerializer
class OpenLIFUTransducerSerializer(SlicerOpenLIFUSerializerBaseMaker(SlicerOpenLIFUTransducerWrapper)):
Expand All @@ -191,7 +193,7 @@ def read(self, parameterNode: slicer.vtkMRMLScriptedModuleNode, name: str) -> Sl
Reads and returns the value with the given name from the parameterNode.
"""
json_string = parameterNode.GetParameter(name)
return SlicerOpenLIFUTransducerWrapper(openlifu_lz().Transducer.from_json(json_string))
return SlicerOpenLIFUTransducerWrapper(openlifu_lz().xdc.Transducer.from_json(json_string))

@parameterNodeSerializer
class OpenLIFUSessionSerializer(SlicerOpenLIFUSerializerBaseMaker(SlicerOpenLIFUSessionWrapper)):
Expand All @@ -215,7 +217,7 @@ def write(self, parameterNode: slicer.vtkMRMLScriptedModuleNode, name: str, valu

def read(self, parameterNode: slicer.vtkMRMLScriptedModuleNode, name: str) -> SlicerOpenLIFUSolutionWrapper:
json_string = parameterNode.GetParameter(name)
return SlicerOpenLIFUSolutionWrapper(openlifu_lz().Solution.from_json(json_string))
return SlicerOpenLIFUSolutionWrapper(openlifu_lz().plan.Solution.from_json(json_string))

@parameterNodeSerializer
class OpenLIFUPointSerializer(SlicerOpenLIFUSerializerBaseMaker(SlicerOpenLIFUPoint)):
Expand All @@ -233,7 +235,7 @@ def read(self, parameterNode: slicer.vtkMRMLScriptedModuleNode, name: str) -> Sl
Reads and returns the value with the given name from the parameterNode.
"""
json_string = parameterNode.GetParameter(name)
return SlicerOpenLIFUPoint(openlifu_lz().Point.from_json(json_string))
return SlicerOpenLIFUPoint(openlifu_lz().geo.Point.from_json(json_string))

@parameterNodeSerializer
class OpenLIFURunSerializer(SlicerOpenLIFUSerializerBaseMaker(SlicerOpenLIFURun)):
Expand Down
3 changes: 2 additions & 1 deletion OpenLIFULib/OpenLIFULib/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
if TYPE_CHECKING:
import openlifu
import openlifu.db
import openlifu.plan
import xarray
from OpenLIFULib import SlicerOpenLIFUTransducer

Expand Down Expand Up @@ -43,7 +44,7 @@ def make_volume_from_xarray_in_transducer_coords(data_array: "xarray.DataArray",

return volumeNode

def make_xarray_in_transducer_coords_from_volume(volume_node:vtkMRMLScalarVolumeNode, transducer:"SlicerOpenLIFUTransducer", protocol:"openlifu.Protocol") -> "xarray.DataArray":
def make_xarray_in_transducer_coords_from_volume(volume_node:vtkMRMLScalarVolumeNode, transducer:"SlicerOpenLIFUTransducer", protocol:"openlifu.plan.Protocol") -> "xarray.DataArray":
"""Convert a volume node into a DataArray in the coordinates of a given transducer.
See also `make_volume_from_xarray_in_transducer_coords`.
"""
Expand Down
3 changes: 2 additions & 1 deletion OpenLIFULib/OpenLIFULib/solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

if TYPE_CHECKING:
import openlifu
import openlifu.plan
import xarray

@parameterPack
Expand All @@ -32,7 +33,7 @@ class SlicerOpenLIFUSolution:

@staticmethod
def initialize_from_openlifu_data(
solution : "openlifu.Solution",
solution : "openlifu.plan.Solution",
pnp_datarray : "xarray.DataArray",
intensity_dataarray : "xarray.DataArray",
transducer : SlicerOpenLIFUTransducer,
Expand Down
11 changes: 6 additions & 5 deletions OpenLIFULib/OpenLIFULib/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

if TYPE_CHECKING:
import openlifu
import openlifu.geo
from OpenLIFULib.transducer import SlicerOpenLIFUTransducer

def get_target_candidates() -> List[vtkMRMLMarkupsFiducialNode]:
Expand All @@ -24,7 +25,7 @@ def get_target_candidates() -> List[vtkMRMLMarkupsFiducialNode]:
if fiducial_node.GetNumberOfControlPoints() == 1
]

def openlifu_point_to_fiducial(point : "openlifu.Point") -> vtkMRMLMarkupsFiducialNode:
def openlifu_point_to_fiducial(point : "openlifu.geo.Point") -> vtkMRMLMarkupsFiducialNode:
"""Create a fiducial node out of an openlifu Point, removing any existing nodes that would have the same name.
The name of the node will be the openlifu point ID, so we do not allow this to be duplicated.
"""
Expand Down Expand Up @@ -60,29 +61,29 @@ def fiducial_to_openlifu_point_id(fiducial_node:vtkMRMLMarkupsFiducialNode) -> s
"""Get the openlifu point ID that we would use if we were to convert the given fiducial node to an openlifu Point"""
return fiducial_node.GetName()

def fiducial_to_openlifu_point_in_transducer_coords(fiducial_node:vtkMRMLMarkupsFiducialNode, transducer:"SlicerOpenLIFUTransducer", name:Optional[str] = None) -> "openlifu.Point":
def fiducial_to_openlifu_point_in_transducer_coords(fiducial_node:vtkMRMLMarkupsFiducialNode, transducer:"SlicerOpenLIFUTransducer", name:Optional[str] = None) -> "openlifu.geo.Point":
"""Given a fiducial node with at least one point, return an openlifu Point in the local coordinates of the given transducer.
If name is provided then it will be used as the name of the openlifu Point. Otherwise we use the label on the control point.
"""
if fiducial_node.GetNumberOfControlPoints() < 1:
raise ValueError(f"Fiducial node {fiducial_node.GetID()} does not have any points.")
position = (np.linalg.inv(slicer.util.arrayFromTransformMatrix(transducer.transform_node)) @ np.array([*fiducial_node.GetNthControlPointPosition(0),1]))[:3] # TODO handle 4th coord here actually, would need to unprojectivize
return openlifu_lz().Point(
return openlifu_lz().geo.Point(
position=position,
name = name if name is not None else fiducial_node.GetNthControlPointLabel(0),
id = f"{fiducial_to_openlifu_point_id(fiducial_node)}-in-transducer-coords",
dims=('x','y','z'), # Here x,y,z means transducer coordinates.
units = transducer.transducer.transducer.units,
)

def fiducial_to_openlifu_point(fiducial_node:vtkMRMLMarkupsFiducialNode) -> "openlifu.Point":
def fiducial_to_openlifu_point(fiducial_node:vtkMRMLMarkupsFiducialNode) -> "openlifu.geo.Point":
"""Given a fiducial node with at least one point, return an openlifu Point in RAS coordinates.
This tries to be roughly an inverse operation of `openlifu_point_to_fiducial`, but isn't an inverse when it comes to
for example the coordinates, and units. The opnenlifu point ID is however preserved between this function and
`openlifu_point_to_fiducial`, because it is used as the node name."""
if fiducial_node.GetNumberOfControlPoints() < 1:
raise ValueError(f"Fiducial node {fiducial_node.GetID()} does not have any points.")
return openlifu_lz().Point(
return openlifu_lz().geo.Point(
position = np.array(fiducial_node.GetNthControlPointPosition(0)),
name = fiducial_node.GetNthControlPointLabel(0),
id = fiducial_to_openlifu_point_id(fiducial_node),
Expand Down
3 changes: 2 additions & 1 deletion OpenLIFULib/OpenLIFULib/transducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

if TYPE_CHECKING:
import openlifu # This import is deferred at runtime, but it is done here for IDE and static analysis purposes
import openlifu.xdc


# Define transducer color dictionary
Expand All @@ -40,7 +41,7 @@ class SlicerOpenLIFUTransducer:

@staticmethod
def initialize_from_openlifu_transducer(
transducer : "openlifu.Transducer",
transducer : "openlifu.xdc.Transducer",
transducer_abspaths_info: dict = {},
transducer_matrix: Optional[np.ndarray]=None,
transducer_matrix_units: Optional[str]=None,
Expand Down
12 changes: 7 additions & 5 deletions OpenLIFUPrePlanning/OpenLIFUPrePlanning.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@
if TYPE_CHECKING:
import openlifu
import openlifu.geo
import openlifu.virtual_fit
import openlifu.plan
import openlifu.seg.virtual_fit
import openlifu.xdc
from OpenLIFUData.OpenLIFUData import OpenLIFUDataLogic

PLACE_INTERACTION_MODE_ENUM_VALUE = slicer.vtkMRMLInteractionNode().Place
Expand Down Expand Up @@ -1100,8 +1102,8 @@ def virtual_fit(

add_slicer_log_handler("VirtualFit", "Virtual fitting")

transducer_openlifu : "openlifu.Transducer" = transducer.transducer.transducer
protocol_openlifu : "openlifu.Protocol" = protocol.protocol
transducer_openlifu : "openlifu.xdc.Transducer" = transducer.transducer.transducer
protocol_openlifu : "openlifu.plan.Protocol" = protocol.protocol

units = "mm" # These are the units of the output space of the transform returned by get_IJK2RAS

Expand All @@ -1116,7 +1118,7 @@ def virtual_fit(
# tiny svd calls end up having more overhead than is worth it.
# For some unknown reason, the improvement is only noticable when we do not use the embree
# option in virtual fitting, which makes things very fast.
vf_transforms = openlifu_lz().run_virtual_fit(
vf_transforms = openlifu_lz().seg.run_virtual_fit(
units = units,
target_RAS = target.GetNthControlPointPosition(0),
standoff_transform = transducer_openlifu.get_standoff_transform_in_units(units),
Expand Down Expand Up @@ -1159,7 +1161,7 @@ def virtual_fit(
return None
return vf_result_nodes[0]

def load_vf_debugging_info(self, debug_info : "openlifu.virtual_fit.VirtualFitDebugInfo") -> None:
def load_vf_debugging_info(self, debug_info : "openlifu.seg.virtual_fit.VirtualFitDebugInfo") -> None:
"""Load virtual fit debugging info into the Slicer scene."""
skin_node = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode")
skin_node.SetAndObservePolyData(debug_info.skin_mesh)
Expand Down
Loading
Loading