|
4 | 4 |
|
5 | 5 | import qt |
6 | 6 | import vtk |
| 7 | +import numpy as np |
7 | 8 |
|
8 | 9 | import slicer |
9 | 10 | from slicer.i18n import tr as _ |
|
14 | 15 | from slicer import vtkMRMLMarkupsFiducialNode, vtkMRMLScalarVolumeNode, vtkMRMLTransformNode |
15 | 16 |
|
16 | 17 | from OpenLIFULib import ( |
| 18 | + openlifu_lz, |
17 | 19 | get_target_candidates, |
18 | 20 | get_openlifu_data_parameter_node, |
19 | 21 | OpenLIFUAlgorithmInputWidget, |
20 | 22 | SlicerOpenLIFUProtocol, |
21 | 23 | SlicerOpenLIFUTransducer, |
22 | 24 | ) |
23 | | -from OpenLIFULib.util import replace_widget |
| 25 | +from OpenLIFULib.util import replace_widget, BusyCursor |
24 | 26 | from OpenLIFULib.virtual_fit_results import ( |
25 | 27 | add_virtual_fit_result, |
26 | 28 | clear_virtual_fit_results, |
|
32 | 34 | get_target_id_from_virtual_fit_result_node, |
33 | 35 | ) |
34 | 36 | from OpenLIFULib.targets import fiducial_to_openlifu_point_id |
| 37 | +from OpenLIFULib.coordinate_system_utils import get_IJK2RAS |
| 38 | +from OpenLIFULib.transform_conversion import transform_node_from_openlifu |
35 | 39 |
|
36 | 40 | if TYPE_CHECKING: |
37 | 41 | from OpenLIFUData.OpenLIFUData import OpenLIFUDataLogic |
| 42 | + import openlifu |
| 43 | + import openlifu.geo |
38 | 44 |
|
39 | 45 | PLACE_INTERACTION_MODE_ENUM_VALUE = slicer.vtkMRMLInteractionNode().Place |
40 | 46 |
|
@@ -458,19 +464,19 @@ def updateApprovalStatusLabel(self): |
458 | 464 |
|
459 | 465 | def onVirtualfitClicked(self): |
460 | 466 | activeData = self.algorithm_input_widget.get_current_data() |
461 | | - virtual_fit_result : Optional[vtkMRMLTransformNode] = self.logic.virtual_fit( |
462 | | - activeData["Protocol"],activeData["Transducer"], activeData["Volume"], activeData["Target"] |
463 | | - ) |
| 467 | + with BusyCursor(): |
| 468 | + virtual_fit_result : Optional[vtkMRMLTransformNode] = self.logic.virtual_fit( |
| 469 | + activeData["Protocol"],activeData["Transducer"], activeData["Volume"], activeData["Target"] |
| 470 | + ) |
464 | 471 |
|
465 | | - if virtual_fit_result is None: # Temporary behavior! |
466 | | - # None indicates for now that the user activated the transform handles to do the placeholder manual virtual fitting. |
467 | | - # It will not be possible to get None once the algorithm is implemented, or at least it wouldn't mean the same thing. |
468 | | - target_id = fiducial_to_openlifu_point_id(activeData["Target"]) |
469 | | - self.algorithm_input_widget.inputs_dict["Target"].disable_with_tooltip(f"VF for {target_id} in progress...") # Disable target selector during manual VF |
| 472 | + if virtual_fit_result is None: |
| 473 | + slicer.util.errorDisplay("Virtual fit failed. No viable transducer positions found.") |
470 | 474 | return |
471 | | - self.algorithm_input_widget.update() # Re-enable target-selector now that manual VF is completed. Again, temporary behavior. |
| 475 | + else: |
| 476 | + # TODO: Make the virtual fit button both update the transducer transform and populate in the virtual fit results |
| 477 | + activeData["Transducer"].set_current_transform_to_match_transform_node(virtual_fit_result) |
| 478 | + self.watchVirtualFit(virtual_fit_result) |
472 | 479 |
|
473 | | - self.watchVirtualFit(virtual_fit_result) |
474 | 480 | self.updateApproveButton() |
475 | 481 | self.updateApprovalStatusLabel() |
476 | 482 |
|
@@ -549,39 +555,52 @@ def virtual_fit( |
549 | 555 | volume: vtkMRMLScalarVolumeNode, |
550 | 556 | target: vtkMRMLMarkupsFiducialNode, |
551 | 557 | ) -> Optional[vtkMRMLTransformNode]: |
552 | | - # Temporary measure of "manual" virtual fitting. See https://github.com/OpenwaterHealth/SlicerOpenLIFU/issues/153 |
553 | | - transducer.transform_node.CreateDefaultDisplayNodes() |
554 | | - if not transducer.transform_node.GetDisplayNode().GetEditorVisibility(): |
555 | | - slicer.util.infoDisplay( |
556 | | - text=( |
557 | | - "The automatic virtual fitting algorithm is not yet implemented." |
558 | | - " Use the interaction handles on the transducer to manually fit it." |
559 | | - " You can click the Virtual fit button again to remove the interaction handles," |
560 | | - " completing the manual virtual fit and recording the virtual fit transform." |
561 | | - ), |
562 | | - windowTitle="Not implemented" |
563 | | - ) |
564 | | - transducer.transform_node.GetDisplayNode().SetEditorVisibility(True) |
565 | | - return None # we would also return this in the event of failure to do virtual fitting |
566 | | - else: |
567 | | - # "Complete" the virtual fit |
568 | | - transducer.transform_node.GetDisplayNode().SetEditorVisibility(False) |
569 | 558 |
|
570 | | - session = get_openlifu_data_parameter_node().loaded_session |
571 | | - session_id : Optional[str] = session.get_session_id() if session is not None else None |
| 559 | + # TODO: Many quantities are hard-coded here will not have to be when these two issues are done: |
| 560 | + # https://github.com/OpenwaterHealth/OpenLIFU-python/issues/166 |
| 561 | + # https://github.com/OpenwaterHealth/OpenLIFU-python/issues/165 |
| 562 | + vf_transforms = openlifu_lz().virtual_fit( |
| 563 | + standoff_transform = openlifu_lz().geo.create_standoff_transform( |
| 564 | + z_offset = 13.55, |
| 565 | + dzdy = 0.15 |
| 566 | + ), |
| 567 | + volume_array = slicer.util.arrayFromVolume(volume), |
| 568 | + volume_affine_RAS = get_IJK2RAS(volume), |
| 569 | + target_RAS = target.GetNthControlPointPosition(0), |
| 570 | + pitch_range = (-10,150), |
| 571 | + pitch_step = 5, |
| 572 | + yaw_range = (-65, 65), |
| 573 | + yaw_step = 5, |
| 574 | + transducer_steering_center_distance = 50, |
| 575 | + steering_limits = ( |
| 576 | + (-50, 50), # lat |
| 577 | + (-50, 50), # ele |
| 578 | + (-50, 50), # ax |
| 579 | + ), |
| 580 | + ) |
| 581 | + # TODO: add log handler for this |
572 | 582 |
|
573 | | - target_id = fiducial_to_openlifu_point_id(target) |
574 | | - clear_virtual_fit_results(target_id=target_id,session_id=session_id) |
| 583 | + session = get_openlifu_data_parameter_node().loaded_session |
| 584 | + session_id : Optional[str] = session.get_session_id() if session is not None else None |
575 | 585 |
|
576 | | - # When actually running the real virtual fit algorithm, there will be more virtual fit results to add |
577 | | - # but we would only return the best one. |
578 | | - return add_virtual_fit_result( |
579 | | - transform_node = transducer.transform_node, |
| 586 | + target_id = fiducial_to_openlifu_point_id(target) |
| 587 | + clear_virtual_fit_results(target_id=target_id,session_id=session_id) |
| 588 | + |
| 589 | + vf_result_nodes = [] |
| 590 | + |
| 591 | + for i,vf_transform in zip(range(10), vf_transforms): # We only add the top 10 virtual fit nodes, to not put so many transforms into the scene. |
| 592 | + node = add_virtual_fit_result( |
| 593 | + transform_node = transform_node_from_openlifu(vf_transform, transducer.transducer.transducer, "mm"), |
580 | 594 | target_id = target_id, |
581 | 595 | session_id = session_id, |
582 | 596 | approval_status = False, |
583 | | - clone_node=True, |
| 597 | + clone_node=False, |
| 598 | + rank = i+1, |
584 | 599 | ) |
| 600 | + vf_result_nodes.append(node) |
| 601 | + if len(vf_result_nodes)==0: |
| 602 | + return None |
| 603 | + return vf_result_nodes[0] |
585 | 604 |
|
586 | 605 | # |
587 | 606 | # OpenLIFUPrePlanningTest |
|
0 commit comments