Visit MONAI Docs for more information
') 435 | 436 | buffer_rows = int(slicer.util.settingsValue("MONAIViz/bufferArgs", "15")) 437 | dlg = CustomDialog(self.resourcePath, name, ClassUtils.expression_to_args(exp), doc_section, buffer_rows) 438 | dlg.exec() 439 | os.unlink(doc_section) 440 | 441 | if dlg.updatedArgs is not None: 442 | new_exp = ClassUtils.args_to_expression(dlg.updatedArgs) 443 | print(f"Old:: {exp}") 444 | print(f"New:: {new_exp}") 445 | if exp != new_exp: 446 | if row < self.ctx.next_idx or row == self.ui.transformTable.rowCount - 1: 447 | self.onClearTransform() 448 | self.ui.transformTable.item(row, 3).setText(new_exp) 449 | print("Updated for new args...") 450 | 451 | def onAddTransform(self): 452 | print(f"Adding Transform: {self.ui.modulesComboBox.currentText}.{self.ui.transformsComboBox.currentText}") 453 | if not self.ui.modulesComboBox.currentText or not self.ui.transformsComboBox.currentText: 454 | return 455 | 456 | t = self.ui.transformsComboBox.currentText 457 | m = self.ui.modulesComboBox.currentText 458 | 459 | v = "" 460 | if t[-1] == "d": # this is a dictionary transform 461 | # now exclude some transforms whose name happens to end with d 462 | if t not in ["AffineGrid", "Decollated", "RandAffineGrid", "RandDeformGrid"]: 463 | image_key = slicer.util.settingsValue("SlicerMONAIViz/imageKey", "image") 464 | label_key = slicer.util.settingsValue("SlicerMONAIViz/labelKey", "label") 465 | v = f"keys=['{image_key}', '{label_key}']" 466 | 467 | self.addTransform(-1, None, t, v) 468 | 469 | def addTransform(self, pos, m, t, v, active=True): 470 | table = self.ui.transformTable 471 | pos = pos if pos >= 0 else table.rowCount if table.currentRow() < 0 else table.currentRow() + 1 472 | 473 | table.insertRow(pos) 474 | # table.setCellWidget(pos, 0, EditButtonsWidget()) 475 | 476 | box = qt.QCheckBox() 477 | box.setChecked(active) 478 | box.setProperty("row", pos) 479 | widget = qt.QWidget() 480 | box.connect("clicked(bool)", lambda checked: self.onBoxClicked(checked, box.row)) 481 | layout = qt.QHBoxLayout(widget) 482 | layout.addWidget(box) 483 | layout.setAlignment(qt.Qt.AlignCenter) 484 | layout.setContentsMargins(0, 0, 0, 0) 485 | widget.setLayout(layout) 486 | 487 | table.setCellWidget(pos, 0, widget) 488 | 489 | item = qt.QTableWidgetItem() 490 | item.setIcon(self.icon("icons8-yellow-circle-48.png")) 491 | table.setItem(pos, 1, item) 492 | 493 | table.setItem(pos, 2, qt.QTableWidgetItem(f"{m}.{t}" if m else t)) 494 | table.setItem(pos, 3, qt.QTableWidgetItem(v if v else "")) 495 | 496 | table.selectRow(pos) 497 | self.onSelectTransform(pos, 0) 498 | 499 | def onRemoveTransform(self): 500 | row = self.ui.transformTable.currentRow() 501 | if row < 0: 502 | return 503 | self.ui.transformTable.removeRow(row) 504 | self.onSelectTransform(-1, -1) 505 | 506 | def onMoveUpTransform(self): 507 | row = self.ui.transformTable.currentRow() 508 | if row < 0: 509 | return 510 | 511 | t = str(self.ui.transformTable.item(row, 2).text()) 512 | v = str(self.ui.transformTable.item(row, 3).text()) 513 | active = self.ui.transformTable.cellWidget(row, 0).findChild("QCheckBox").isChecked() 514 | self.onRemoveTransform() 515 | self.addTransform(row - 1, None, t, v, active) 516 | 517 | def onMoveDownTransform(self): 518 | row = self.ui.transformTable.currentRow() 519 | if row < 0: 520 | return 521 | 522 | t = str(self.ui.transformTable.item(row, 2).text()) 523 | v = str(self.ui.transformTable.item(row, 3).text()) 524 | active = self.ui.transformTable.cellWidget(row, 0).findChild("QCheckBox").isChecked() 525 | self.onRemoveTransform() 526 | self.addTransform(row + 1, None, t, v, active) 527 | 528 | def prepare_dict(self): 529 | image = self.ui.imagePathLineEdit.currentPath 530 | label = self.ui.labelPathLineEdit.currentPath 531 | additional = json.loads(self.ui.textEdit.toPlainText()) 532 | 533 | image_key = slicer.util.settingsValue("MONAIViz/imageKey", "image") 534 | label_key = slicer.util.settingsValue("MONAIViz/labelKey", "label") 535 | 536 | d = {image_key: image, **additional} 537 | if label: 538 | d[label_key] = label 539 | return d 540 | 541 | def get_exp(self, row): 542 | name = str(self.ui.transformTable.item(row, 2).text()) 543 | args = str(self.ui.transformTable.item(row, 3).text()) 544 | return f"monai.transforms.{name}({args})" 545 | 546 | def onRunTransform(self): 547 | if not self.ui.imagePathLineEdit.currentPath: 548 | slicer.util.errorDisplay("Image is not selected!") 549 | return 550 | 551 | current_row = self.ui.transformTable.currentRow() 552 | print(f"Current Row: {current_row}; Total: {self.ui.transformTable.rowCount}") 553 | if current_row < 0: 554 | return 555 | 556 | image_key = slicer.util.settingsValue("MONAIViz/imageKey", "image") 557 | label_key = slicer.util.settingsValue("MONAIViz/labelKey", "label") 558 | 559 | try: 560 | qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor) 561 | # Temporary:: clear current scene 562 | slicer.mrmlScene.Clear(0) 563 | 564 | current_exp = self.get_exp(current_row) 565 | d = self.ctx.get_d(current_exp, d=self.prepare_dict()) 566 | 567 | import monai 568 | 569 | print(monai.__version__) 570 | 571 | if self.ctx.last_exp != current_exp: 572 | for row in range(self.ctx.next_idx, current_row + 1): 573 | if self.ui.transformTable.cellWidget(row, 0).findChild("QCheckBox").isChecked(): 574 | exp = self.get_exp(row) 575 | print("") 576 | print("====================================================================") 577 | print(f"Run:: {exp}") 578 | print("====================================================================") 579 | 580 | t = eval(exp) 581 | if isinstance(d, list): 582 | d = [t(dx) for dx in d] # Batched Transforms 583 | else: 584 | d = t(d) 585 | 586 | self.ctx.set_d(d, exp, key=image_key) 587 | self.ui.transformTable.item(row, 1).setIcon(self.icon("icons8-green-circle-48.png")) 588 | else: 589 | self.ui.transformTable.item(row, 1).setIcon(self.icon("icons8-yellow-circle-48.png")) 590 | continue 591 | 592 | next_idx = current_row 593 | next_exp = self.get_exp(next_idx) 594 | if current_row + 1 < self.ui.transformTable.rowCount: 595 | next_idx = current_row + 1 596 | next_exp = self.get_exp(next_idx) 597 | 598 | self.ui.transformTable.selectRow(next_idx) 599 | for row in range(next_idx, self.ui.transformTable.rowCount): 600 | self.ui.transformTable.item(row, 1).setIcon(self.icon("icons8-yellow-circle-48.png")) 601 | 602 | v = self.ctx.get_tensor(key=image_key) 603 | volumeNode = slicer.util.addVolumeFromArray(v) 604 | 605 | origin, spacing, direction = self.ctx.get_tensor_osd(key=image_key) 606 | volumeNode.SetName(os.path.basename(self.ui.imagePathLineEdit.currentPath)) 607 | volumeNode.SetOrigin(origin) 608 | volumeNode.SetSpacing(spacing) 609 | volumeNode.SetIJKToRASDirections(direction) 610 | # logging.info(f"Volume direction: {direction}") 611 | 612 | l = self.ctx.get_tensor(key=label_key) 613 | labelNode = None 614 | if l is not None: 615 | labelNode = slicer.util.addVolumeFromArray(l, nodeClassName="vtkMRMLLabelMapVolumeNode") 616 | origin, spacing, direction = self.ctx.get_tensor_osd(key=label_key) 617 | labelNode.SetName(os.path.basename(self.ui.labelPathLineEdit.currentPath)) 618 | labelNode.SetOrigin(origin) 619 | labelNode.SetSpacing(spacing) 620 | labelNode.SetIJKToRASDirections(direction) 621 | # logging.info(f"Label direction: {direction}") 622 | slicer.util.setSliceViewerLayers(volumeNode, label=labelNode, fit=True) 623 | 624 | self.ctx.set_next(next_idx, next_exp) 625 | self.ui.clearTransformButton.setEnabled(self.ctx.valid()) 626 | finally: 627 | qt.QApplication.restoreOverrideCursor() 628 | 629 | def onClearTransform(self): 630 | self.ctx.reset() 631 | for row in range(0, self.ui.transformTable.rowCount): 632 | self.ui.transformTable.item(row, 1).setIcon(self.icon("icons8-yellow-circle-48.png")) 633 | self.ui.clearTransformButton.setEnabled(self.ctx.valid()) 634 | 635 | def onLoadTransform(self): 636 | fname = qt.QFileDialog().getOpenFileName(None, "Select json file to import", "", "(*.json)") 637 | if fname: 638 | with open(fname) as transformFile: 639 | transforms = json.load(transformFile) 640 | 641 | for idx, transform in transforms.items(): 642 | t = transform["name"] 643 | v = "" 644 | 645 | if t[-1] == "d": # this is a dictionary transform 646 | # now exclude some transforms whose name happens to end with d 647 | if t not in ["AffineGrid", "Decollated", "RandAffineGrid", "RandDeformGrid"]: 648 | v = transform["args"] 649 | 650 | self.addTransform(int(idx), None, t, v) 651 | 652 | def onSaveTransform(self): 653 | fname = qt.QFileDialog().getSaveFileName(None, "Save file", "", "json (*.json)") 654 | if fname: 655 | rows = self.ui.transformTable.rowCount 656 | table = {} 657 | for row in range(rows): 658 | name = str(self.ui.transformTable.item(row, 2).text()) 659 | args = str(self.ui.transformTable.item(row, 3).text()) 660 | table[row] = {"name": name, "args": args} 661 | 662 | with open(fname, "w") as output: 663 | json.dump(table, output) 664 | 665 | def onShowDictionary(self): 666 | dlg = TransformDictDialog(self.ctx.get_d(None, d=self.prepare_dict()), self.resourcePath) 667 | dlg.exec() 668 | 669 | 670 | class EditButtonsWidget(qt.QWidget): 671 | def __init__(self, parent=None): 672 | super().__init__(parent) 673 | 674 | layout = qt.QHBoxLayout() 675 | layout.setContentsMargins(0, 0, 0, 0) 676 | layout.setSpacing(0) 677 | 678 | b1 = qt.QPushButton("") 679 | b1.setIcon(self.icon("icons8-green-circle-16.png")) 680 | b1.setMaximumWidth(20) 681 | layout.addWidget(b1) 682 | 683 | b2 = qt.QPushButton("") 684 | b2.setIcon(self.icon("icons8-preview-16.png")) 685 | b2.setMaximumWidth(20) 686 | layout.addWidget(b2) 687 | 688 | b3 = qt.QPushButton("") 689 | b3.setIcon(self.icon("icons8-delete-document-16.png")) 690 | b3.setMaximumWidth(20) 691 | layout.addWidget(b3) 692 | 693 | self.setLayout(layout) 694 | 695 | def icon(self, name): 696 | # It should not be necessary to modify this method 697 | iconPath = os.path.join(os.path.dirname(__file__), "Resources", "Icons", name) 698 | if os.path.exists(iconPath): 699 | return qt.QIcon(iconPath) 700 | return qt.QIcon() 701 | 702 | 703 | class CustomDialog(qt.QDialog): 704 | def __init__(self, resourcePath, name, args, doc_html, buffer_rows): 705 | super().__init__() 706 | self.name = name 707 | self.args = args 708 | self.updatedArgs = None 709 | self.buffer_rows = buffer_rows 710 | 711 | short_name = name.split(".")[-1] 712 | self.setWindowTitle(f"Edit - {short_name}") 713 | print(f"{name} => {args}") 714 | 715 | layout = qt.QVBoxLayout() 716 | uiWidget = slicer.util.loadUI(resourcePath("UI/MONAITransformDialog.ui")) 717 | layout.addWidget(uiWidget) 718 | 719 | self.ui = slicer.util.childWidgetVariables(uiWidget) 720 | self.setLayout(layout) 721 | 722 | url = f"https://docs.monai.io/en/stable/transforms.html#{short_name.lower()}" 723 | self.ui.nameLabel.setText('' + short_name + "") 724 | 725 | headers = ["Name", "Value"] 726 | table = self.ui.tableWidget 727 | table.setRowCount(len(args) + buffer_rows) 728 | table.setColumnCount(len(headers)) 729 | table.setHorizontalHeaderLabels(headers) 730 | table.setColumnWidth(0, 150) 731 | table.setColumnWidth(1, 200) 732 | 733 | for row, (k, v) in enumerate(args.items()): 734 | table.setItem(row, 0, qt.QTableWidgetItem(k)) 735 | table.setItem(row, 1, qt.QTableWidgetItem(str(v))) 736 | 737 | self.ui.updateButton.connect("clicked(bool)", self.onUpdate) 738 | self.ui.webEngineView.url = qt.QUrl.fromLocalFile(doc_html) 739 | 740 | def onUpdate(self): 741 | args = {} 742 | table = self.ui.tableWidget 743 | for row in range(table.rowCount): 744 | k = table.item(row, 0) 745 | k = str(k.text()) if k else None 746 | v = table.item(row, 1) 747 | v = str(v.text()) if v else None 748 | if k: 749 | print(f"Row: {row} => {k} => {v}") 750 | try: 751 | v = eval(v) if v else v 752 | except: 753 | pass 754 | args[k] = v 755 | 756 | self.updatedArgs = args 757 | self.close() 758 | 759 | 760 | class TransformDictDialog(qt.QDialog): 761 | def __init__(self, data, resourcePath): 762 | super().__init__() 763 | 764 | self.setWindowTitle("Dictionary Data") 765 | print(f"{data.keys()}") 766 | 767 | layout = qt.QVBoxLayout() 768 | uiWidget = slicer.util.loadUI(resourcePath("UI/MONAIDictionaryDialog.ui")) 769 | layout.addWidget(uiWidget) 770 | 771 | self.ui = slicer.util.childWidgetVariables(uiWidget) 772 | self.setLayout(layout) 773 | 774 | s = StringIO() 775 | pprint.pprint(data, s, indent=2) 776 | self.ui.dataTextEdit.setPlainText(s.getvalue()) 777 | 778 | headers = ["Key", "Type", "Shape", "Value"] 779 | tree = self.ui.treeWidget 780 | tree.setColumnCount(len(headers)) 781 | tree.setHeaderLabels(headers) 782 | tree.setColumnWidth(0, 150) 783 | tree.setColumnWidth(1, 75) 784 | tree.setColumnWidth(2, 100) 785 | 786 | def get_val(v): 787 | if type(v) in (int, float, bool, str): 788 | return str(v) 789 | 790 | s = StringIO() 791 | pprint.pprint(v, s, compact=True, indent=1, width=-1) 792 | return s.getvalue().replace("\n", "") 793 | 794 | items = [] 795 | for key, val in data.items(): 796 | if isinstance(val, dict): 797 | item = qt.QTreeWidgetItem([key]) 798 | for k1, v1 in val.items(): 799 | tvals = [k1, type(v1).__name__, v1.shape if hasattr(v1, "shape") else "", get_val(v1)] 800 | child = qt.QTreeWidgetItem(tvals) 801 | item.addChild(child) 802 | else: 803 | tvals = [key, type(val).__name__, val.shape if hasattr(val, "shape") else "", get_val(val)] 804 | item = qt.QTreeWidgetItem(tvals) 805 | items.append(item) 806 | 807 | tree.insertTopLevelItems(0, items) 808 | 809 | 810 | class MONAIVizLogic(ScriptedLoadableModuleLogic): 811 | def __init__(self): 812 | ScriptedLoadableModuleLogic.__init__(self) 813 | self.torchLogic = PyTorchUtils.PyTorchUtilsLogic() 814 | 815 | def setDefaultParameters(self, parameterNode): 816 | # if not parameterNode.GetParameter("Threshold"): 817 | # parameterNode.SetParameter("Threshold", "100.0") 818 | pass 819 | 820 | def process(self): 821 | import time 822 | 823 | startTime = time.time() 824 | logging.info("Processing started") 825 | 826 | stopTime = time.time() 827 | logging.info(f"Processing completed in {stopTime - startTime:.2f} seconds") 828 | 829 | def importMONAI(self): 830 | if not self.torchLogic.torchInstalled(): 831 | logging.info("PyTorch module not found") 832 | torch = self.torchLogic.installTorch(askConfirmation=True) 833 | if torch is None: 834 | slicer.util.errorDisplay( 835 | "PyTorch needs to be installed to use the MONAI extension." 836 | " Please reload this module to install PyTorch." 837 | ) 838 | return None 839 | try: 840 | import monai 841 | except ModuleNotFoundError: 842 | with self.showWaitCursor(), self.peakPythonConsole(): 843 | monai = self.installMONAI() 844 | logging.info(f"MONAI {monai.__version__} imported correctly") 845 | return monai 846 | 847 | @staticmethod 848 | def installMONAI(confirm=True): 849 | if confirm and not slicer.app.commandOptions().testingEnabled: 850 | install = slicer.util.confirmOkCancelDisplay( 851 | "MONAI will be downloaded and installed now. The process might take some minutes." 852 | ) 853 | if not install: 854 | logging.info("Installation of MONAI aborted by user") 855 | return None 856 | slicer.util.pip_install("monai[itk,nibabel,tqdm]") 857 | import monai 858 | 859 | logging.info(f"MONAI {monai.__version__} installed correctly") 860 | return monai 861 | 862 | 863 | class MONAIVizTest(ScriptedLoadableModuleTest): 864 | def setUp(self): 865 | slicer.mrmlScene.Clear() 866 | 867 | def runTest(self): 868 | self.setUp() 869 | self.test_MONAIViz1() 870 | 871 | def test_MONAIViz1(self): 872 | self.delayDisplay("Starting the test") 873 | self.delayDisplay("Test passed") 874 | 875 | 876 | class TransformCtx: 877 | def __init__(self): 878 | self.d = None 879 | self.last_exp = "" 880 | self.next_idx = 0 881 | self.next_exp = "" 882 | self.channel = False 883 | self.bidx = 0 884 | self.original_spatial_shape = None 885 | self.original_affine = None 886 | 887 | def reset(self): 888 | self.__init__() 889 | 890 | def valid(self) -> bool: 891 | return False if self.d is None or self.next_idx == 0 else True 892 | 893 | def valid_for_next(self, exp) -> bool: 894 | return True if exp and self.next_exp and exp == self.next_exp else False 895 | 896 | def get_d(self, exp, d=None): 897 | if exp is None: 898 | if self.valid(): 899 | bidx = self.bidx % len(self.d) if isinstance(self.d, list) else -1 900 | return self.d[bidx] if bidx >= 0 else self.d 901 | return d 902 | 903 | if not self.valid_for_next(exp): 904 | self.reset() 905 | 906 | if not self.valid(): 907 | print(d) 908 | return d 909 | return self.d 910 | 911 | def set_d(self, d, exp, key): 912 | key_tensor = d[self.bidx % len(d)][key] if isinstance(d, list) else d[key] 913 | print(f"{key}: {key_tensor.shape}") 914 | 915 | if self.original_spatial_shape is None: 916 | self.original_spatial_shape = key_tensor.shape 917 | self.original_affine = key_tensor.affine.numpy() 918 | 919 | if "EnsureChannelFirstd" in exp: 920 | self.channel = True 921 | 922 | self.d = d 923 | self.last_exp = exp 924 | 925 | def set_next(self, next_idx, next_exp): 926 | if self.next_idx == next_idx and self.next_exp == next_exp: 927 | self.bidx += 1 928 | else: 929 | self.next_idx = next_idx 930 | self.next_exp = next_exp 931 | 932 | def get_tensor(self, key, transpose=True): 933 | import numpy as np 934 | import torch 935 | 936 | bidx = self.bidx % len(self.d) if isinstance(self.d, list) else -1 937 | d = self.d[bidx] if bidx >= 0 else self.d 938 | if d.get(key) is None: 939 | return None 940 | 941 | key_tensor = d[key] 942 | if isinstance(key_tensor, str) or key_tensor is None: 943 | return None 944 | 945 | v = key_tensor.numpy() if isinstance(key_tensor, torch.Tensor) else key_tensor 946 | v = np.squeeze(v, axis=0) if self.channel else v 947 | v = v.transpose() if transpose else v 948 | 949 | print(f"Display {key}{'[' + str(bidx) + ']' if bidx >= 0 else ''}: {v.shape}") 950 | return v 951 | 952 | def get_tensor_osd(self, key, scale=False): 953 | import numpy as np 954 | from monai.transforms.utils import scale_affine 955 | 956 | bidx = self.bidx % len(self.d) if isinstance(self.d, list) else -1 957 | d = self.d[bidx] if bidx >= 0 else self.d 958 | if d.get(key) is None: 959 | return None 960 | 961 | key_tensor = d[key] 962 | actual_shape = key_tensor.shape[1:] if self.channel else key_tensor.shape 963 | 964 | affine = ( 965 | scale_affine(self.original_affine, self.original_spatial_shape, actual_shape) 966 | if scale 967 | else key_tensor.affine.numpy() 968 | ) 969 | 970 | # convert_aff_mat = np.diag([-1, -1, 1, 1]) # RAS <-> LPS conversion matrix 971 | # affine = convert_aff_mat @ affine # convert from RAS to LPS 972 | 973 | dim = affine.shape[0] - 1 974 | _origin_key = (slice(-1), -1) 975 | _m_key = (slice(-1), slice(-1)) 976 | 977 | origin = affine[_origin_key] 978 | spacing = np.linalg.norm(affine[_m_key] @ np.eye(dim), axis=0) 979 | direction = affine[_m_key] @ np.diag(1 / spacing) 980 | 981 | return origin, spacing, direction 982 | -------------------------------------------------------------------------------- /MONAIViz/MONAIVizLib/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) MONAI Consortium 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | 12 | from .utils import * 13 | -------------------------------------------------------------------------------- /MONAIViz/MONAIVizLib/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) MONAI Consortium 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # Unless required by applicable law or agreed to in writing, software 7 | # distributed under the License is distributed on an "AS IS" BASIS, 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | # See the License for the specific language governing permissions and 10 | # limitations under the License. 11 | import importlib 12 | import inspect 13 | import os 14 | from typing import Any 15 | 16 | 17 | class ClassUtils: 18 | @staticmethod 19 | def is_subclass(n, o, base_c): 20 | if inspect.isclass(o) and n != base_c: 21 | b = [cls.__name__ for cls in o.__bases__] 22 | if base_c in b: 23 | return True 24 | return False 25 | 26 | @staticmethod 27 | def get_class_of_subclass(module, base_classes) -> dict[str, Any]: 28 | print(f"{module} => {base_classes}") 29 | res: dict[str, Any] = {} 30 | for n, o in inspect.getmembers(module): 31 | if not inspect.isclass(o) or inspect.isabstract(o): 32 | continue 33 | 34 | # print(f"{n} => {o}") 35 | for base_c in base_classes: 36 | if ClassUtils.is_subclass(n, o, base_c): 37 | cp = f"{o.__module__}.{o.__name__}" 38 | if res.get(cp): 39 | res[cp]["alias"].append(n) 40 | else: 41 | res[cp] = { 42 | "name": o.__name__, 43 | "alias": [n], 44 | "class": cp, 45 | "module": o.__module__, 46 | "dictionary": o.__module__.endswith("dictionary"), 47 | "base_class": base_c, 48 | "category": ".".join(o.__module__.split(".")[:3]), 49 | } 50 | break 51 | 52 | sorted_d: dict[str, Any] = {} 53 | for k in sorted(res.keys()): 54 | v = res[k] 55 | v["alias"] = sorted(v["alias"]) 56 | sorted_d[k] = v 57 | 58 | return sorted_d 59 | 60 | @staticmethod 61 | def args_to_expression(class_args): 62 | key_val = [] 63 | for key in class_args: 64 | val = class_args[key] 65 | if isinstance(val, str): 66 | val = "'" + val + "'" 67 | elif isinstance(val, tuple) or isinstance(val, list): 68 | vals = [] 69 | for v in val: 70 | if isinstance(v, str): 71 | v = "'" + v + "'" 72 | else: 73 | v = str(v) 74 | vals.append(v) 75 | if isinstance(val, tuple): 76 | val = "(" + ", ".join(vals) + ")" 77 | else: 78 | val = "[" + ", ".join(vals) + "]" 79 | else: 80 | val = str(val) 81 | key_val.append(f"{key}={val}") 82 | return ", ".join(key_val) 83 | 84 | @staticmethod 85 | def expression_to_args(exp, handle_bool=True): 86 | if not exp: 87 | return {} 88 | 89 | if handle_bool: 90 | exp = exp.replace("=true", "=True").replace("=false", "=False") # safe to assume 91 | exp = exp.replace(" true", " True").replace(" false", " False") 92 | 93 | def foo(**kwargs): 94 | return kwargs 95 | 96 | return eval("foo(" + exp + ")") 97 | 98 | 99 | class MonaiUtils: 100 | @staticmethod 101 | def list_transforms(module="monai.transforms"): 102 | mt = importlib.import_module(module) 103 | return ClassUtils.get_class_of_subclass(mt, ["Transform", "MapTransform"]) 104 | 105 | @staticmethod 106 | def list_bundles(auth_token=None): 107 | from monai.bundle import get_all_bundles_list 108 | 109 | return get_all_bundles_list(auth_token=auth_token) 110 | 111 | @staticmethod 112 | def download_bundle(name, bundle_dir, auth_token=None): 113 | from monai.bundle import download, get_bundle_versions 114 | 115 | # Support 1.1.0 monai (bug) for downloading bundles on Windows (pass specific version) 116 | version = get_bundle_versions(name, auth_token=auth_token)["latest_version"] 117 | download(name, version=version, bundle_dir=bundle_dir) 118 | 119 | @staticmethod 120 | def transforms_from_bundle(name, bundle_dir): 121 | from monai.bundle import ConfigParser 122 | 123 | bundle_root = os.path.join(bundle_dir, name) 124 | config_path = os.path.join(bundle_root, "configs", "train.json") 125 | if not os.path.exists(config_path): 126 | config_path = os.path.join(bundle_root, "configs", "train.yaml") 127 | 128 | bundle_config = ConfigParser() 129 | bundle_config.read_config(config_path) 130 | bundle_config.config.update({"bundle_root": bundle_root}) # type: ignore 131 | 132 | for k in ["train#preprocessing#transforms", "train#pre_transforms#transforms"]: 133 | if bundle_config.get(k): 134 | c = bundle_config.get_parsed_content(k, instantiate=False) 135 | c = [x.get_config() for x in c] 136 | return c 137 | 138 | return None 139 | 140 | @staticmethod 141 | def run_transform(name, args, data): 142 | import monai 143 | 144 | print(monai.__version__) 145 | 146 | exp = f"monai.transforms.{name}({args if args else ''})" 147 | print(exp) 148 | t = eval(exp) 149 | 150 | print(data) 151 | d = t(data) 152 | return d 153 | 154 | 155 | """ 156 | def main(): 157 | transforms = MonaiUtils.list_transforms() 158 | 159 | print("ALL Transforms....") 160 | print("----------------------------------------------------------------") 161 | for t in transforms: 162 | print(f"{t} => {transforms[t]['module']}") 163 | 164 | modules = sorted(list({v["module"] for v in transforms.values()})) 165 | 166 | print("") 167 | print("ALL Modules....") 168 | print("----------------------------------------------------------------") 169 | for m in modules: 170 | print(f"{m}") 171 | # print(json.dumps(categories, indent=2)) 172 | 173 | print("") 174 | print("ALL Bundles....") 175 | print("----------------------------------------------------------------") 176 | bundles = MonaiUtils.list_bundles() 177 | for b in sorted({b[0] for b in bundles}): 178 | print(b) 179 | 180 | bundle_dir = "/tmp/Slicer-sachi/slicer-monai-transforms/bundle" 181 | bundle_dir = "C:/Users/salle/AppData/Local/Temp/Slicer/slicer-monai-transforms/bundle" 182 | # MonaiUtils.download_bundle( 183 | # "spleen_ct_segmentation", bundle_dir 184 | # ) 185 | 186 | print("") 187 | print("Bundle Transforms....") 188 | print("----------------------------------------------------------------") 189 | b_transforms = MonaiUtils.transforms_from_bundle("spleen_ct_segmentation", bundle_dir) 190 | for t in b_transforms: 191 | print(f"{type(t)} => {t}") 192 | 193 | 194 | def main2(): 195 | data = { 196 | "image": "/localhome/sachi/Datasets/Radiology/Task09_Spleen/imagesTr/spleen_2.nii.gz", 197 | "label": "/localhome/sachi/Datasets/Radiology/Task09_Spleen/labelsTr/spleen_2.nii.gz", 198 | } 199 | MonaiUtils.run_transform(name="LoadImaged", args="keys=['image', 'label']", data=data) 200 | 201 | 202 | if __name__ == "__main__": 203 | # pip_install("monai") 204 | # pip_install("nibabel") 205 | main() 206 | """ 207 | -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/MONAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/MONAI.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/MONAIViz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/MONAIViz.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/done.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/download.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-delete-document-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-delete-document-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-delete-row-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-delete-row-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-edit-row-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-edit-row-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-green-circle-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-green-circle-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-insert-row-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-insert-row-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-load-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-load-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-preview-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-preview-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-red-circle-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-red-circle-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-save-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-save-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/icons8-yellow-circle-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/icons8-yellow-circle-48.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/refresh-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Project-MONAI/SlicerMONAIViz/37dcd21d16c8524bc689c00ceda741ef620f8352/MONAIViz/Resources/Icons/refresh-icon.png -------------------------------------------------------------------------------- /MONAIViz/Resources/Icons/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MONAIViz/Resources/UI/MONAIDictionaryDialog.ui: -------------------------------------------------------------------------------- 1 | 2 |