├── .gitignore ├── CMakeLists.txt ├── DeepInfer ├── CMakeLists.txt ├── DeepInfer.py ├── Resources │ ├── .DS_Store │ ├── Icons │ │ ├── .DS_Store │ │ ├── DeepInfer.png │ │ └── Fiducials.png │ └── json │ │ └── .DS_Store ├── Testing │ ├── CMakeLists.txt │ └── Python │ │ └── CMakeLists.txt └── __init__.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled python 2 | *.pyc 3 | 4 | # IPython notebook checkpoints 5 | .ipynb_checkpoints 6 | 7 | # Editor temporaries 8 | *.swp 9 | *~ 10 | *.DS_Store 11 | 12 | # pycharm 13 | .idea -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.9) 2 | 3 | project(DeepInfer) 4 | 5 | #----------------------------------------------------------------------------- 6 | set(EXTENSION_HOMEPAGE "http://www.slicer.org/slicerWiki/index.php?title=Documentation/Nightly/Extensions/DeepInfer") 7 | set(EXTENSION_CATEGORY "Machine Learning") 8 | set(EXTENSION_CONTRIBUTORS "Alireza Mehrtash(UBC, BWH), Mehran Pesteie (UBC)") 9 | set(EXTENSION_DESCRIPTION "Deep learning deployment toolkit for medical imaging.") 10 | set(EXTENSION_ICONURL "https://www.slicer.org/w/images/a/a9/Deepinfer-256.png") 11 | set(EXTENSION_SCREENSHOTURLS "://www.slicer.org/w/images/d/d3/Deepinfer-screenshot1.png") 12 | #----------------------------------------------------------------------------- 13 | find_package(Slicer REQUIRED) 14 | include(${Slicer_USE_FILE}) 15 | 16 | #----------------------------------------------------------------------------- 17 | add_subdirectory(DeepInfer) 18 | 19 | #----------------------------------------------------------------------------- 20 | include(${Slicer_EXTENSION_CPACK}) 21 | -------------------------------------------------------------------------------- /DeepInfer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | set(MODULE_NAME DeepInfer) 3 | 4 | #----------------------------------------------------------------------------- 5 | set(MODULE_PYTHON_SCRIPTS 6 | ${MODULE_NAME}.py 7 | ) 8 | 9 | set(MODULE_PYTHON_RESOURCES 10 | Resources/Icons/${MODULE_NAME}.png 11 | ) 12 | 13 | #----------------------------------------------------------------------------- 14 | slicerMacroBuildScriptedModule( 15 | NAME ${MODULE_NAME} 16 | SCRIPTS ${MODULE_PYTHON_SCRIPTS} 17 | RESOURCES ${MODULE_PYTHON_RESOURCES} 18 | WITH_GENERIC_TESTS 19 | ) 20 | 21 | #----------------------------------------------------------------------------- 22 | if(BUILD_TESTING) 23 | 24 | # Register the unittest subclass in the main script as a ctest. 25 | # Note that the test will also be available at runtime. 26 | slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py) 27 | 28 | # Additional build-time testing 29 | add_subdirectory(Testing) 30 | endif() 31 | -------------------------------------------------------------------------------- /DeepInfer/DeepInfer.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import json 3 | import platform 4 | import os 5 | import re 6 | import subprocess 7 | import shutil 8 | import threading 9 | from collections import OrderedDict 10 | from glob import glob 11 | from time import sleep 12 | 13 | from __main__ import qt, ctk, slicer 14 | 15 | # To avoid the overhead of importing SimpleITK during application 16 | # startup, the import of SimpleITK is delayed until it is needed. 17 | import SimpleITK as sitk 18 | import sitkUtils 19 | 20 | from slicer.util import getNode, saveNode 21 | 22 | 23 | ICON_DIR = os.path.dirname(os.path.realpath(__file__)) + '/Resources/Icons/' 24 | 25 | from os.path import expanduser 26 | home = expanduser("~") 27 | 28 | DEEPINFER_DIR = os.path.join(home, '.deepinfer') 29 | if not os.path.isdir(DEEPINFER_DIR): 30 | os.mkdir(DEEPINFER_DIR) 31 | 32 | JSON_CLOUD_DIR = os.path.join(DEEPINFER_DIR, 'json', 'cloud') 33 | if os.path.isdir(JSON_CLOUD_DIR): 34 | shutil.rmtree(JSON_CLOUD_DIR) 35 | os.makedirs(JSON_CLOUD_DIR) 36 | 37 | JSON_LOCAL_DIR = os.path.join(DEEPINFER_DIR, 'json', 'local') 38 | if not os.path.isdir(JSON_LOCAL_DIR): 39 | os.makedirs(JSON_LOCAL_DIR) 40 | 41 | TMP_PATH = os.path.join(DEEPINFER_DIR, '.tmp') 42 | if os.path.isdir(TMP_PATH): 43 | shutil.rmtree(TMP_PATH) 44 | os.mkdir(TMP_PATH) 45 | 46 | # 47 | # DeepInfer 48 | # 49 | 50 | class DeepInfer: 51 | # Use class-level scoped variable for module consants 52 | if not __file__.endswith("DeepInfer.py"): 53 | import inspect 54 | __file__ = inspect.getframeinfo(inspect.currentframe())[0] 55 | 56 | 57 | 58 | def __init__(self, parent): 59 | parent.title = "DeepInfer" 60 | parent.categories = ["Machine Learning"] 61 | parent.dependencies = [] 62 | parent.contributors = ["Alireza Mehrtash (UBC/BWH/SPL), Mehran Pesteie (UBC)"] 63 | parent.helpText = \ 64 | """ 65 | This modules provides a basic interface to deploy machine learning and deep learning models in Slicer using Docker. 66 | For general information about the module see the online documentation. 67 |

68 | For detailed information about a specific model please consult the DeepInfer website. 69 | """.format(parent.slicerWikiUrl, slicer.app.majorVersion, slicer.app.minorVersion) 70 | 71 | parent.acknowledgementText = """ 72 | The developers would like to thank the support of the Surgical Planning Lab, the University of British Columbia, Slicer Community and the Insight Toolkit. 73 | """ 74 | self.parent = parent 75 | 76 | parent.icon = qt.QIcon("%s/ITK.png" % ICON_DIR) 77 | 78 | 79 | # 80 | # qDeepInferWidget 81 | # 82 | 83 | class DeepInferWidget: 84 | def __init__(self, parent=None): 85 | 86 | # To avoid the overhead of importing SimpleITK during application 87 | # startup, the import of SimpleITK is delayed until it is needed. 88 | 89 | if not parent: 90 | self.parent = slicer.qMRMLWidget() 91 | self.parent.setLayout(qt.QVBoxLayout()) 92 | self.parent.setMRMLScene(slicer.mrmlScene) 93 | else: 94 | self.parent = parent 95 | self.layout = self.parent.layout() 96 | if not parent: 97 | self.parent.show() 98 | 99 | self.modelParameters = None 100 | self.logic = None 101 | 102 | def onReload(self, moduleName="DeepInfer"): 103 | """Generic reload method for any scripted module. 104 | ModuleWizard will subsitute correct default moduleName. 105 | """ 106 | globals()[moduleName] = slicer.util.reloadScriptedModule(moduleName) 107 | 108 | def setup(self): 109 | 110 | # Instantiate and connect widgets ... 111 | # 112 | # Reload and Test area 113 | # 114 | reloadCollapsibleButton = ctk.ctkCollapsibleButton() 115 | reloadCollapsibleButton.collapsed = True 116 | reloadCollapsibleButton.text = "Reload && Test" 117 | reloadFormLayout = qt.QFormLayout(reloadCollapsibleButton) 118 | 119 | # reload button 120 | # (use this during development, but remove it when delivering 121 | # your module to users) 122 | self.reloadButton = qt.QPushButton("Reload") 123 | self.reloadButton.toolTip = "Reload this module." 124 | self.reloadButton.name = "Freehand3DUltrasound Reload" 125 | reloadFormLayout.addWidget(self.reloadButton) 126 | self.reloadButton.connect('clicked()', self.onReload) 127 | # uncomment the following line for debug/development. 128 | self.layout.addWidget(reloadCollapsibleButton) 129 | 130 | # Docker Settings Area 131 | self.dockerGroupBox = ctk.ctkCollapsibleGroupBox() 132 | self.dockerGroupBox.setTitle('Docker Settings') 133 | self.layout.addWidget(self.dockerGroupBox) 134 | dockerForm = qt.QFormLayout(self.dockerGroupBox) 135 | self.dockerPath = ctk.ctkPathLineEdit() 136 | # self.dockerPath.setMaximumWidth(300) 137 | dockerForm.addRow("Docker Executable Path:", self.dockerPath) 138 | self.testDockerButton = qt.QPushButton('Test!') 139 | dockerForm.addRow("Test Docker Configuration:", self.testDockerButton) 140 | if platform.system() == 'Darwin': 141 | self.dockerPath.setCurrentPath('/usr/local/bin/docker') 142 | if platform.system() == 'Linux': 143 | self.dockerPath.setCurrentPath('/usr/bin/docker') 144 | if platform.system() == 'Windows': 145 | self.dockerPath.setCurrentPath("C:/Program Files/Docker/Docker/resources/docker.exe") 146 | 147 | ### use nvidia-docker if it is installed 148 | nvidiaDockerPath = self.dockerPath.currentPath.replace('bin/docker', 'bin/nvidia-docker') 149 | if os.path.isfile(nvidiaDockerPath): 150 | self.dockerPath.setCurrentPath(nvidiaDockerPath) 151 | 152 | # modelRepositoryVerticalLayout = qt.QVBoxLayout(modelRepositoryExpdableArea) 153 | 154 | # Model Repository Area 155 | self.modelRepoGroupBox = ctk.ctkCollapsibleGroupBox() 156 | # self.modelRepoGroupBox.collapsed = True 157 | self.modelRepoGroupBox.setTitle('Cloud Model Repository') 158 | self.layout.addWidget(self.modelRepoGroupBox) 159 | modelRepoVBLayout1 = qt.QVBoxLayout(self.modelRepoGroupBox) 160 | modelRepositoryExpdableArea = ctk.ctkExpandableWidget() 161 | modelRepoVBLayout1.addWidget(modelRepositoryExpdableArea) 162 | modelRepoVBLayout2 = qt.QVBoxLayout(modelRepositoryExpdableArea) 163 | self.modelRegistryTable = qt.QTableWidget() 164 | self.modelRegistryTable.visible = False 165 | self.modelRepositoryModel = qt.QStandardItemModel() 166 | self.modelRepositoryTableHeaderLabels = ['Model', 'Organ', 'Task', 'Status'] 167 | self.modelRegistryTable.setColumnCount(4) 168 | self.modelRegistryTable.setSelectionMode(qt.QAbstractItemView.SingleSelection) 169 | self.modelRegistryTable.sortingEnabled = True 170 | self.modelRegistryTable.setHorizontalHeaderLabels(self.modelRepositoryTableHeaderLabels) 171 | self.modelRepositoryTableWidgetHeader = self.modelRegistryTable.horizontalHeader() 172 | self.modelRepositoryTableWidgetHeader.setStretchLastSection(True) 173 | # self.modelRepositoryTableWidgetHeader.setResizeMode(qt.QHeaderView.Stretch) 174 | modelRepoVBLayout2.addWidget(self.modelRegistryTable) 175 | # 176 | self.progressDownload = qt.QProgressBar() 177 | self.progressDownload.setRange(0, 100) 178 | self.progressDownload.setValue(0) 179 | modelRepoVBLayout2.addWidget(self.progressDownload) 180 | self.progressDownload.hide() 181 | # 182 | self.modelRepositoryTreeSelectionModel = self.modelRegistryTable.selectionModel() 183 | abstractItemView = qt.QAbstractItemView() 184 | self.modelRegistryTable.setSelectionBehavior(abstractItemView.SelectRows) 185 | verticalheader = self.modelRegistryTable.verticalHeader() 186 | verticalheader.setDefaultSectionSize(20) 187 | modelRepoVBLayout1.setSpacing(0) 188 | modelRepoVBLayout2.setSpacing(0) 189 | modelRepoVBLayout1.setMargin(0) 190 | modelRepoVBLayout2.setContentsMargins(7, 3, 7, 7) 191 | refreshWidget = qt.QWidget() 192 | modelRepoVBLayout2.addWidget(refreshWidget) 193 | hBoXLayout = qt.QHBoxLayout(refreshWidget) 194 | # hBoXLayout.setSpacing(0) 195 | # hBoXLayout.setMargin(0) 196 | self.connectButton = qt.QPushButton('Connect') 197 | self.downloadButton = qt.QPushButton('Download') 198 | self.downloadButton.enabled = False 199 | self.downloadButton.visible = False 200 | hBoXLayout.addStretch(1) 201 | hBoXLayout.addWidget(self.connectButton) 202 | hBoXLayout.addWidget(self.downloadButton) 203 | self.populateModelRegistryTable() 204 | 205 | 206 | 207 | # 208 | # Cancel/Apply Row 209 | # 210 | self.restoreDefaultsButton = qt.QPushButton("Restore Defaults") 211 | self.restoreDefaultsButton.toolTip = "Restore the default parameters." 212 | self.restoreDefaultsButton.enabled = True 213 | 214 | self.cancelButton = qt.QPushButton("Cancel") 215 | self.cancelButton.toolTip = "Abort the algorithm." 216 | self.cancelButton.enabled = False 217 | 218 | self.applyButton = qt.QPushButton("Apply") 219 | self.applyButton.toolTip = "Run the algorithm." 220 | self.applyButton.enabled = True 221 | 222 | hlayout = qt.QHBoxLayout() 223 | 224 | # 225 | # Local Models Area 226 | # 227 | modelsCollapsibleButton = ctk.ctkCollapsibleGroupBox() 228 | modelsCollapsibleButton.setTitle("Local Models") 229 | self.layout.addWidget(modelsCollapsibleButton) 230 | # Layout within the dummy collapsible button 231 | modelsFormLayout = qt.QFormLayout(modelsCollapsibleButton) 232 | 233 | # model search 234 | self.searchBox = ctk.ctkSearchBox() 235 | modelsFormLayout.addRow("Search:", self.searchBox) 236 | self.searchBox.connect("textChanged(QString)", self.onSearch) 237 | 238 | # model selector 239 | self.modelSelector = qt.QComboBox() 240 | modelsFormLayout.addRow("Model:", self.modelSelector) 241 | self.populateLocalModels() 242 | 243 | # connections 244 | self.modelSelector.connect('currentIndexChanged(int)', self.onModelSelect) 245 | 246 | # 247 | # Parameters Area 248 | # 249 | parametersCollapsibleButton = ctk.ctkCollapsibleGroupBox() 250 | parametersCollapsibleButton.setTitle("Model Parameters") 251 | self.layout.addWidget(parametersCollapsibleButton) 252 | 253 | # Layout within the dummy collapsible button 254 | parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton) 255 | self.modelParameters = ModelParameters(parametersCollapsibleButton) 256 | 257 | # Add vertical spacer 258 | self.layout.addStretch(1) 259 | 260 | # 261 | # Status and Progress 262 | # 263 | statusLabel = qt.QLabel("Status: ") 264 | self.currentStatusLabel = qt.QLabel("Idle") 265 | hlayout = qt.QHBoxLayout() 266 | hlayout.addStretch(1) 267 | hlayout.addWidget(statusLabel) 268 | hlayout.addWidget(self.currentStatusLabel) 269 | self.layout.addLayout(hlayout) 270 | 271 | self.progress = qt.QProgressBar() 272 | self.progress.setRange(0, 1000) 273 | self.progress.setValue(0) 274 | self.layout.addWidget(self.progress) 275 | self.progress.hide() 276 | 277 | # 278 | # Cancel/Apply Row 279 | # 280 | self.restoreDefaultsButton = qt.QPushButton("Restore Defaults") 281 | self.restoreDefaultsButton.toolTip = "Restore the default parameters." 282 | self.restoreDefaultsButton.enabled = True 283 | 284 | self.cancelButton = qt.QPushButton("Cancel") 285 | self.cancelButton.toolTip = "Abort the algorithm." 286 | self.cancelButton.enabled = False 287 | 288 | self.applyButton = qt.QPushButton("Apply") 289 | self.applyButton.toolTip = "Run the algorithm." 290 | self.applyButton.enabled = True 291 | 292 | hlayout = qt.QHBoxLayout() 293 | 294 | hlayout.addWidget(self.restoreDefaultsButton) 295 | hlayout.addStretch(1) 296 | hlayout.addWidget(self.cancelButton) 297 | hlayout.addWidget(self.applyButton) 298 | self.layout.addLayout(hlayout) 299 | 300 | # connections 301 | self.connectButton.connect('clicked(bool)', self.onConnectButton) 302 | self.downloadButton.connect('clicked(bool)', self.onDownloadButton) 303 | self.testDockerButton.connect('clicked(bool)', self.onTestDockerButton) 304 | self.restoreDefaultsButton.connect('clicked(bool)', self.onRestoreDefaultsButton) 305 | self.applyButton.connect('clicked(bool)', self.onApplyButton) 306 | self.cancelButton.connect('clicked(bool)', self.onCancelButton) 307 | self.modelRegistryTable.connect('itemSelectionChanged()', self.onCloudModelSelect) 308 | 309 | # Initlial Selection 310 | self.modelSelector.currentIndexChanged(self.modelSelector.currentIndex) 311 | 312 | def cleanup(self): 313 | pass 314 | 315 | def getAllDigests(self): 316 | cmd = [] 317 | cmd.append(self.dockerPath.currentPath) 318 | cmd.append('images') 319 | cmd.append('--digests') 320 | # print(cmd) 321 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 322 | digest_index = 2 323 | digests = [] 324 | try: 325 | while True: 326 | slicer.app.processEvents() 327 | line = p.stdout.readline() 328 | if not line: 329 | break 330 | line = line.split() 331 | if 'DIGEST' in line: 332 | digest_index = line.index('DIGEST') 333 | else: 334 | digests.append(line[digest_index]) 335 | except Exception as e: 336 | print("Exception: {}".format(e)) 337 | return digests 338 | 339 | def populateLocalModels(self): 340 | digests = self.getAllDigests() 341 | jsonFiles = glob(JSON_LOCAL_DIR + "/*.json") 342 | jsonFiles.sort(cmp=lambda x, y: cmp(os.path.basename(x), os.path.basename(y))) 343 | self.jsonModels = [] 344 | for fname in jsonFiles: 345 | with open(fname, "r") as fp: 346 | j = json.load(fp, object_pairs_hook=OrderedDict) 347 | self.jsonModels.append(j) 348 | # if j['docker']['digest'] in digests: 349 | # self.jsonModels.append(j) 350 | # else: 351 | # os.remove(fname) 352 | # add all the models listed in the json files 353 | 354 | for idx, j in enumerate(self.jsonModels): 355 | name = j["name"] 356 | self.modelSelector.addItem(name, idx) 357 | 358 | def onCloudModelSelect(self): 359 | self.downloadButton.enabled = False 360 | # print("on cloud model select!") 361 | for item in self.modelTableItems.keys(): 362 | if item.isSelected(): 363 | self.downloadButton.enabled = True 364 | self.selectedModelPath = self.modelTableItems[item] 365 | 366 | def onLogicRunStop(self): 367 | self.applyButton.setEnabled(True) 368 | self.restoreDefaultsButton.setEnabled(True) 369 | self.cancelButton.setEnabled(False) 370 | self.logic = None 371 | self.progress.hide() 372 | 373 | def onLogicRunStart(self): 374 | self.applyButton.setEnabled(False) 375 | self.restoreDefaultsButton.setEnabled(False) 376 | 377 | def onSearch(self, searchText): 378 | # add all the models listed in the json files 379 | self.modelSelector.clear() 380 | # split text on whitespace of and string search 381 | searchTextList = searchText.split() 382 | for idx, j in enumerate(self.jsonModels): 383 | lname = j["name"].lower() 384 | # require all elements in list, to add to select. case insensitive 385 | if reduce(lambda x, y: x and (lname.find(y.lower()) != -1), [True] + searchTextList): 386 | self.modelSelector.addItem(j["name"], idx) 387 | 388 | def onModelSelect(self, selectorIndex): 389 | # print("on model select") 390 | self.modelParameters.destroy() 391 | if selectorIndex < 0: 392 | return 393 | jsonIndex = self.modelSelector.itemData(selectorIndex) 394 | json_model = self.jsonModels[jsonIndex] 395 | self.modelParameters.create(json_model) 396 | 397 | if "briefdescription" in self.jsonModels[jsonIndex]: 398 | tip = self.jsonModels[jsonIndex]["briefdescription"] 399 | tip = tip.rstrip() 400 | self.modelSelector.setToolTip(tip) 401 | else: 402 | self.modelSelector.setToolTip("") 403 | 404 | def onConnectButton(self): 405 | try: 406 | self.modelRegistryTable.visible = True 407 | self.downloadButton.visible = True 408 | self.connectButton.visible = False 409 | self.connectButton.enabled = False 410 | import urllib2 411 | url = 'https://api.github.com/repos/DeepInfer/Model-Registry/contents/' 412 | response = urllib2.urlopen(url) 413 | data = json.load(response) 414 | for item in data: 415 | if 'json' in item['name']: 416 | # print(item['name']) 417 | url = item['url'] 418 | response = urllib2.urlopen(url) 419 | data = json.load(response) 420 | dl_url = data['download_url'] 421 | print("downloading: {}...".format(dl_url)) 422 | response = urllib2.urlopen(dl_url) 423 | content = response.read() 424 | outputPath = os.path.join(JSON_CLOUD_DIR, dl_url.split('/')[-1]) 425 | with open(outputPath, 'w') as f: 426 | f.write(content) 427 | self.populateModelRegistryTable() 428 | except Exception as e: 429 | print("Exception occured: {}".format(e)) 430 | self.connectButton.enabled = True 431 | self.modelRegistryTable.visible = False 432 | self.downloadButton.visible = False 433 | self.connectButton.visible = True 434 | self.connectButton.enabled = True 435 | 436 | def onTestDockerButton(self): 437 | cmd = [] 438 | cmd.append(self.dockerPath.currentPath) 439 | cmd.append('--version') 440 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 441 | message = p.stdout.readline().replace('\n', '') 442 | if message.startswith('Docker version'): 443 | qt.QMessageBox.information(None, 'Docker Status', 'Docker is configured correctly' 444 | ' ({}).'.format(message)) 445 | else: 446 | qt.QMessageBox.critical(None, 'Docker Status', 'Docker is not configured correctly. Check your docker ' 447 | 'installation and make sure that it is configured to ' 448 | 'be run by non-root user.') 449 | 450 | def onDownloadButton(self): 451 | with open(self.selectedModelPath) as json_data: 452 | model = json.load(json_data) 453 | size = model['docker']['size'] 454 | model_name = model['name'] 455 | dockerhub_address = model['docker']['dockerhub_repository'] + '@' + model['docker']['digest'] 456 | if platform.system() == 'Windows': 457 | terminal = 'Command Prompt or PowerShell, but not PowerShell ISE' 458 | else: 459 | terminal = 'terminal' 460 | message_title = '1. Copy the following command from the box below.\n' \ 461 | '2. Open the {}, paste and run the copied command.\n' \ 462 | '3. Wait until the docker image download is finished.\n' \ 463 | '4. Restart 3D Slicer.\n' \ 464 | '5. You should see the downloaded model in the \"Local Models\" section.\n' \ 465 | '(Please note that the size of the \"{}\" docker image is {}.\n' \ 466 | 'Make sure that you have enough storage space on your machine.)'.format(terminal, model_name, size) 467 | pull_command = 'docker pull {}'.format(dockerhub_address) 468 | mainWindow = slicer.util.mainWindow() 469 | downloadWidget = qt.QWidget() 470 | layout = qt.QVBoxLayout() 471 | downloadWidget.setLayout(layout) 472 | popupGeometry = qt.QRect() 473 | if mainWindow: 474 | width = 400 475 | height = 200 476 | popupGeometry.setWidth(width) 477 | popupGeometry.setHeight(height) 478 | downloadWidget.setGeometry(popupGeometry) 479 | 480 | pos = mainWindow.pos 481 | downloadWidget.move(pos.x() + (mainWindow.width - downloadWidget.width) / 2, 482 | pos.y() + (mainWindow.height - downloadWidget.height) / 2) 483 | 484 | titleLabel = qt.QLabel(message_title) 485 | layout.addWidget(titleLabel) 486 | te = qt.QTextEdit(pull_command) 487 | te.readOnly = True 488 | layout.addWidget(te) 489 | closeButton = qt.QPushButton('Close') 490 | layout.addWidget(closeButton) 491 | downloadWidget.show() 492 | def hide_download(): 493 | downloadWidget.hide() 494 | closeButton.connect('clicked(bool)', hide_download) 495 | shutil.copy(self.selectedModelPath, os.path.join(JSON_LOCAL_DIR, os.path.basename(self.selectedModelPath))) 496 | 497 | 498 | ''' 499 | def onDownloadButton(self): 500 | with open(self.selectedModelPath) as json_data: 501 | model = json.load(json_data) 502 | size = model['docker']['size'] 503 | resoponse = self.Question("The size of the selected image to download is {}. Are you sure you want to proceed?".format(size), 504 | title="Download", parent=None) 505 | if resoponse: 506 | cmd = [] 507 | cmd.append(self.dockerPath.currentPath) 508 | cmd.append('pull') 509 | cmd.append(model['docker']['dockerhub_repository'] + '@' + model['docker']['digest']) 510 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 511 | print(cmd) 512 | parts = dict() 513 | try: 514 | while True: 515 | self.progressDownload.show() 516 | slicer.app.processEvents() 517 | line = p.stdout.readline() 518 | if not line: 519 | break 520 | line = line.rstrip() 521 | print(line) 522 | part = line.split(':')[0] 523 | if len(part) == 12: 524 | parts[part] = line.split(':')[1] 525 | if parts.keys(): 526 | print('-'*100) 527 | print(parts) 528 | n_parts = len(parts.keys()) 529 | n_completed = len([status for status in parts.values() if status == ' Pull complete']) 530 | self.progressDownload.setValue(int((100*n_completed)/n_parts)) 531 | 532 | except Exception as e: 533 | print("Exception: {}".format(e)) 534 | print(parts) 535 | self.progressDownload.setValue(0) 536 | self.progressDownload.hide() 537 | shutil.copy(self.selectedModelPath, os.path.join(JSON_LOCAL_DIR, os.path.basename(self.selectedModelPath))) 538 | self.populateLocalModels() 539 | else: 540 | print("Download was canceled!") 541 | 542 | ''' 543 | def Question(self, text, title="", parent=None): 544 | return qt.QMessageBox.question(parent, title, text, 545 | qt.QMessageBox.Yes, qt.QMessageBox.No) == qt.QMessageBox.Yes 546 | 547 | def populateModelRegistryTable(self): 548 | self.modelTableItems = dict() 549 | # print("populate Model Registry Table") 550 | model_files = glob(JSON_CLOUD_DIR+'/*.json') 551 | self.modelRegistryTable.setRowCount(len(model_files)) 552 | n = 0 553 | model_files = [os.path.join(JSON_CLOUD_DIR, model_file) for model_file in model_files] 554 | for model_file in model_files: 555 | with open(model_file) as json_data: 556 | model = json.load(json_data) 557 | keys = model.keys() 558 | for key in keys: 559 | if key == 'name': 560 | nameTableItem = qt.QTableWidgetItem(str(model['name'])) 561 | self.modelTableItems[nameTableItem] = model_file 562 | self.modelRegistryTable.setItem(n, 0, nameTableItem) 563 | if key == 'organ': 564 | organ = qt.QTableWidgetItem(str(model['organ'])) 565 | self.modelRegistryTable.setItem(n, 1, organ) 566 | if key == 'task': 567 | task = qt.QTableWidgetItem(str(model['task'])) 568 | self.modelRegistryTable.setItem(n, 2, task) 569 | n += 1 570 | 571 | def onRestoreDefaultsButton(self): 572 | self.onModelSelect(self.modelSelector.currentIndex) 573 | 574 | def onApplyButton(self): 575 | print('onApply') 576 | self.logic = DeepInferLogic() 577 | # try: 578 | self.currentStatusLabel.text = "Starting" 579 | self.modelParameters.prerun() 580 | self.logic.run(self.modelParameters) 581 | 582 | ''' 583 | except: 584 | self.currentStatusLabel.text = "Exception" 585 | slicer.modules.DeepInferWidget.applyButton.enabled = True 586 | import sys 587 | msg = sys.exc_info()[0] 588 | # if there was an exception during start-up make sure to finish 589 | self.onLogicRunStop() 590 | qt.QMessageBox.critical(slicer.util.mainWindow(), 591 | "Exception before execution: {0} ".format(self.modelParameters.dockerImageName), 592 | msg) 593 | ''' 594 | 595 | def onCancelButton(self): 596 | self.currentStatusLabel.text = "Aborting" 597 | if self.logic: 598 | self.logic.abort = True; 599 | 600 | def onLogicEventStart(self): 601 | self.currentStatusLabel.text = "Running" 602 | self.cancelButton.setDisabled(False) 603 | self.progress.setValue(0) 604 | self.progress.show() 605 | 606 | def onLogicEventEnd(self): 607 | self.currentStatusLabel.text = "Completed" 608 | self.progress.setValue(1000) 609 | 610 | def onLogicEventAbort(self): 611 | self.currentStatusLabel.text = "Aborted" 612 | 613 | def onLogicEventProgress(self, progress): 614 | self.currentStatusLabel.text = "Running ({0:6.5f})".format(progress) 615 | self.progress.setValue(progress * 1000) 616 | 617 | def onLogicEventIteration(self, nIter): 618 | print("Iteration ", nIter) 619 | 620 | # 621 | # DeepInferLogic 622 | # 623 | 624 | class DeepInferLogic: 625 | """This class should implement all the actual 626 | computation done by your module. The interface 627 | should be such that other python code can import 628 | this class and make use of the functionality without 629 | requiring an instance of the Widget 630 | """ 631 | 632 | def __init__(self): 633 | self.main_queue = queue.Queue() 634 | self.main_queue_running = False 635 | self.thread = threading.Thread() 636 | self.abort = False 637 | modules = slicer.modules 638 | if hasattr(modules, 'DeepInferWidget'): 639 | self.dockerPath = slicer.modules.DeepInferWidget.dockerPath.currentPath 640 | else: 641 | if platform.system() == 'Darwin': 642 | defualt_path = '/usr/local/bin/docker' 643 | self.setDockerPath(defualt_path) 644 | elif platform.system() == 'Linux': 645 | defualt_path = '/usr/bin/docker' 646 | self.setDockerPath(defualt_path) 647 | elif platform.system() == 'Windows': 648 | defualt_path = "C:/Program Files/Docker/Docker/resources/docker.exe" 649 | self.setDockerPath(defualt_path) 650 | else: 651 | print('could not determine system type') 652 | 653 | 654 | def __del__(self): 655 | if self.main_queue_running: 656 | self.main_queue_stop() 657 | if self.thread.is_alive(): 658 | self.thread.join() 659 | 660 | def setDockerPath(self, path): 661 | self.dockerPath = path 662 | 663 | def yieldPythonGIL(self, seconds=0): 664 | sleep(seconds) 665 | 666 | def cmdCheckAbort(self, p): 667 | if self.abort: 668 | p.kill() 669 | self.cmdAbortEvent() 670 | 671 | def cmdStartEvent(self): 672 | if hasattr(slicer.modules, 'DeepInferWidget'): 673 | widget = slicer.modules.DeepInferWidget 674 | widget.onLogicEventStart() 675 | self.yieldPythonGIL() 676 | 677 | def cmdProgressEvent(self, progress): 678 | if hasattr(slicer.modules, 'DeepInferWidget'): 679 | widget = slicer.modules.DeepInferWidget 680 | widget.onLogicEventProgress(progress) 681 | self.yieldPythonGIL() 682 | 683 | def cmdAbortEvent(self): 684 | if hasattr(slicer.modules, 'DeepInferWidget'): 685 | widget = slicer.modules.DeepInferWidget 686 | widget.onLogicEventAbort() 687 | self.yieldPythonGIL() 688 | 689 | def cmdEndEvent(self): 690 | if hasattr(slicer.modules, 'DeepInferWidget'): 691 | widget = slicer.modules.DeepInferWidget 692 | widget.onLogicEventEnd() 693 | self.yieldPythonGIL() 694 | 695 | def checkDockerDaemon(self): 696 | cmd = list() 697 | cmd.append(self.dockerPath) 698 | cmd.append('ps') 699 | print(cmd) 700 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 701 | slicer.app.processEvents() 702 | line = p.stdout.readline() 703 | if line[:9] == 'CONTAINER': 704 | return True 705 | return False 706 | 707 | def executeDocker(self, dockerName, modelName, dataPath, iodict, inputs, params): 708 | try: 709 | assert self.checkDockerDaemon(), "Docker Daemon is not running" 710 | except Exception as e: 711 | print(e.message) 712 | self.abort = True 713 | 714 | modules = slicer.modules 715 | if hasattr(modules, 'DeepInferWidget'): 716 | widgetPresent = True 717 | else: 718 | widgetPresent = False 719 | 720 | if widgetPresent: 721 | self.cmdStartEvent() 722 | inputDict = dict() 723 | outputDict = dict() 724 | paramDict = dict() 725 | for item in iodict: 726 | if iodict[item]["iotype"] == "input": 727 | if iodict[item]["type"] == "volume": 728 | # print(inputs[item]) 729 | input_node_name = inputs[item].GetName() 730 | #try: 731 | img = sitk.ReadImage(sitkUtils.GetSlicerITKReadWriteAddress(input_node_name)) 732 | fileName = item + '.nrrd' 733 | inputDict[item] = fileName 734 | sitk.WriteImage(img, str(os.path.join(TMP_PATH, fileName))) 735 | #except Exception as e: 736 | # print(e.message) 737 | elif iodict[item]["type"] == "point_vec": 738 | input_node_name = inputs[item].GetName() 739 | fidListNode = getNode(input_node_name) 740 | fileName = item + '.fcsv' 741 | inputDict[item] = fileName 742 | output_path = str(os.path.join(TMP_PATH, fileName)) 743 | saveNode(fidListNode, output_path) 744 | elif iodict[item]["iotype"] == "output": 745 | if iodict[item]["type"] == "volume": 746 | outputDict[item] = item + '.nrrd' 747 | elif iodict[item]["type"] == "point_vec": 748 | outputDict[item] = item + '.fcsv' 749 | elif iodict[item]["iotype"] == "parameter": 750 | paramDict[item] = str(params[item]) 751 | 752 | if not dataPath: 753 | dataPath = '/home/deepinfer/data' 754 | 755 | print('docker run command:') 756 | cmd = list() 757 | cmd.append(self.dockerPath) 758 | cmd.extend(('run', '-t', '-v')) 759 | cmd.append(TMP_PATH + ':' + dataPath) 760 | cmd.append(dockerName) 761 | for key in inputDict.keys(): 762 | cmd.append('--' + key) 763 | cmd.append(dataPath + '/' + inputDict[key]) 764 | for key in outputDict.keys(): 765 | cmd.append('--' + key) 766 | cmd.append(dataPath + '/' + outputDict[key]) 767 | if modelName: 768 | cmd.append('--ModelName') 769 | cmd.append(modelName) 770 | for key in paramDict.keys(): 771 | if iodict[key]["type"] == "bool": 772 | if paramDict[key]: 773 | cmd.append('--' + key) 774 | else: 775 | cmd.append('--' + key) 776 | cmd.append(paramDict[key]) 777 | print('-'*100) 778 | print(cmd) 779 | 780 | # TODO: add a line to check wether the docker image is present or not. If not ask user to download it. 781 | # try: 782 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 783 | progress = 0 784 | # print('executing') 785 | while True: 786 | progress += 0.15 787 | slicer.app.processEvents() 788 | self.cmdCheckAbort(p) 789 | if widgetPresent: 790 | self.cmdProgressEvent(progress) 791 | line = p.stdout.readline() 792 | if not line: 793 | break 794 | print(line) 795 | 796 | 797 | def thread_doit(self, modelParameters): 798 | iodict = modelParameters.iodict 799 | inputs = modelParameters.inputs 800 | params = modelParameters.params 801 | outputs = modelParameters.outputs 802 | dockerName = modelParameters.dockerImageName 803 | modelName = modelParameters.modelName 804 | dataPath = modelParameters.dataPath 805 | #try: 806 | self.main_queue_start() 807 | self.executeDocker(dockerName, modelName, dataPath, iodict, inputs, params) 808 | if not self.abort: 809 | self.updateOutput(iodict, outputs) 810 | self.main_queue_stop() 811 | self.cmdEndEvent() 812 | 813 | ''' 814 | except Exception as e: 815 | msg = e.message 816 | qt.QMessageBox.critical(slicer.util.mainWindow(), "Exception during execution of ", msg) 817 | slicer.modules.DeepInferWidget.applyButton.enabled = True 818 | slicer.modules.DeepInferWidget.progress.hide = True 819 | self.abort = True 820 | self.yieldPythonGIL() 821 | ''' 822 | def main_queue_start(self): 823 | """Begins monitoring of main_queue for callables""" 824 | self.main_queue_running = True 825 | slicer.modules.DeepInferWidget.onLogicRunStart() 826 | qt.QTimer.singleShot(0, self.main_queue_process) 827 | 828 | def main_queue_stop(self): 829 | """End monitoring of main_queue for callables""" 830 | self.main_queue_running = False 831 | if self.thread.is_alive(): 832 | self.thread.join() 833 | slicer.modules.DeepInferWidget.onLogicRunStop() 834 | 835 | def main_queue_process(self): 836 | """processes the main_queue of callables""" 837 | try: 838 | while not self.main_queue.empty(): 839 | f = self.main_queue.get_nowait() 840 | if callable(f): 841 | f() 842 | 843 | if self.main_queue_running: 844 | # Yield the GIL to allow other thread to do some python work. 845 | # This is needed since pyQt doesn't yield the python GIL 846 | self.yieldPythonGIL(.01) 847 | qt.QTimer.singleShot(0, self.main_queue_process) 848 | 849 | except Exception as e: 850 | import sys 851 | sys.stderr.write("ModelLogic error in main_queue: \"{0}\"".format(e)) 852 | 853 | # if there was an error try to resume 854 | if not self.main_queue.empty() or self.main_queue_running: 855 | qt.QTimer.singleShot(0, self.main_queue_process) 856 | 857 | def updateOutput(self, iodict, outputs): 858 | # print('updateOutput method') 859 | output_volume_files = dict() 860 | output_fiduciallist_files = dict() 861 | for item in iodict: 862 | if iodict[item]["iotype"] == "output": 863 | if iodict[item]["type"] == "volume": 864 | fileName = str(os.path.join(TMP_PATH, item + '.nrrd')) 865 | output_volume_files[item] = fileName 866 | if iodict[item]["type"] == "point_vec": 867 | fileName = str(os.path.join(TMP_PATH, item + '.fcsv')) 868 | output_fiduciallist_files[item] = fileName 869 | for output_volume in output_volume_files.keys(): 870 | result = sitk.ReadImage(output_volume_files[output_volume]) 871 | output_node = outputs[output_volume] 872 | output_node_name = output_node.GetName() 873 | nodeWriteAddress = sitkUtils.GetSlicerITKReadWriteAddress(output_node_name) 874 | sitk.WriteImage(result, nodeWriteAddress) 875 | applicationLogic = slicer.app.applicationLogic() 876 | selectionNode = applicationLogic.GetSelectionNode() 877 | 878 | outputLabelMap = True 879 | if outputLabelMap: 880 | selectionNode.SetReferenceActiveLabelVolumeID(output_node.GetID()) 881 | else: 882 | selectionNode.SetReferenceActiveVolumeID(output_node.GetID()) 883 | 884 | applicationLogic.PropagateVolumeSelection(0) 885 | applicationLogic.FitSliceToAll() 886 | for fiduciallist in output_fiduciallist_files.keys(): 887 | # information about loading markups: https://www.slicer.org/wiki/Documentation/Nightly/Modules/Markups 888 | output_node = outputs[fiduciallist] 889 | _, node = slicer.util.loadMarkupsFiducialList(output_fiduciallist_files[fiduciallist], True) 890 | output_node.Copy(node) 891 | scene = slicer.mrmlScene 892 | # todo: currently due to a bug in markups module removing the node will create some unexpected behaviors 893 | # reported bug reference: https://issues.slicer.org/view.php?id=4414 894 | # scene.RemoveNode(node) 895 | 896 | 897 | def run(self, modelParamters): 898 | """ 899 | Run the actual algorithm 900 | """ 901 | if self.thread.is_alive(): 902 | import sys 903 | sys.stderr.write("ModelLogic is already executing!") 904 | return 905 | self.abort = False 906 | self.thread = threading.Thread(target=self.thread_doit(modelParameters=modelParamters)) 907 | 908 | 909 | # 910 | # Class to manage parameters 911 | # 912 | 913 | class ModelParameters(object): 914 | """ This class is for managing the widgets for the parameters for a model 915 | """ 916 | 917 | # class-scope regular expression to help covert from CamelCase 918 | reCamelCase = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))') 919 | 920 | def __init__(self, parent=None): 921 | self.parent = parent 922 | self.widgets = [] 923 | self.json = None 924 | # self.model = None 925 | self.inputs = [] 926 | self.outputs = [] 927 | self.prerun_callbacks = [] 928 | self.outputLabelMap = False 929 | self.iodict = dict() 930 | self.dockerImageName = '' 931 | self.modelName = None 932 | self.dataPath = None 933 | 934 | self.outputSelector = None 935 | self.outputLabelMapBox = None 936 | 937 | 938 | def __del__(self): 939 | self.widgets = [] 940 | 941 | def BeautifyCamelCase(self, str): 942 | return self.reCamelCase.sub(r' \1', str) 943 | 944 | def create_iodict(self, json_dict): 945 | iodict = dict() 946 | for member in json_dict["members"]: 947 | if "type" in member: 948 | t = member["type"] 949 | if t in ["uint8_t", "int8_t", 950 | "uint16_t", "int16_t", 951 | "uint32_t", "int32_t", 952 | "uint64_t", "int64_t", 953 | "unsigned int", "int", 954 | "double", "float"]: 955 | iodict[member["name"]] = {"type": member["type"], "iotype": member["iotype"], 956 | "value": member["default"]} 957 | 958 | else: 959 | iodict[member["name"]] = {"type": member["type"], "iotype": member["iotype"]} 960 | return iodict 961 | 962 | def create_model_info(self, json_dict): 963 | dockerImageName = json_dict['docker']['dockerhub_repository'] 964 | modelName = json_dict.get('model_name') 965 | dataPath = json_dict.get('data_path') 966 | return dockerImageName, modelName, dataPath 967 | 968 | def create(self, json_dict): 969 | if not self.parent: 970 | raise "no parent" 971 | # parametersFormLayout = self.parent.layout() 972 | 973 | # You can't use exec in a function that has a subfunction, unless you specify a context. 974 | # exec ('self.model = sitk.{0}()'.format(json["name"])) in globals(), locals() 975 | 976 | self.iodict = self.create_iodict(json_dict) 977 | self.dockerImageName, self.modelName, self.dataPath = self.create_model_info(json_dict) 978 | 979 | self.prerun_callbacks = [] 980 | self.inputs = dict() 981 | self.outputs = dict() 982 | self.params = dict() 983 | self.outputLabelMap = False 984 | 985 | # 986 | # Iterate over the members in the JSON to generate a GUI 987 | # 988 | for member in json_dict["members"]: 989 | w = None 990 | if "type" in member: 991 | t = member["type"] 992 | 993 | if "dim_vec" in member and int(member["dim_vec"]): 994 | if member["itk_type"].endswith("IndexType") or member["itk_type"].endswith("PointType"): 995 | isPoint = member["itk_type"].endswith("PointType") 996 | 997 | fiducialSelector = slicer.qMRMLNodeComboBox() 998 | self.widgets.append(fiducialSelector) 999 | fiducialSelector.nodeTypes = ("vtkMRMLMarkupsFiducialNode", "vtkMRMLAnnotationFiducialNode") 1000 | fiducialSelector.selectNodeUponCreation = True 1001 | fiducialSelector.addEnabled = False 1002 | fiducialSelector.removeEnabled = False 1003 | fiducialSelector.renameEnabled = True 1004 | fiducialSelector.noneEnabled = False 1005 | fiducialSelector.showHidden = False 1006 | fiducialSelector.showChildNodeTypes = True 1007 | fiducialSelector.setMRMLScene(slicer.mrmlScene) 1008 | fiducialSelector.setToolTip("Pick the Fiducial for the Point or Index") 1009 | 1010 | fiducialSelector.connect("nodeActivated(vtkMRMLNode*)", 1011 | lambda node, w=fiducialSelector, name=member["name"], 1012 | isPt=isPoint: self.onFiducialNode(name, w, isPt)) 1013 | self.prerun_callbacks.append( 1014 | lambda w=fiducialSelector, name=member["name"], isPt=isPoint: self.onFiducialNode(name, w, 1015 | isPt)) 1016 | 1017 | w1 = fiducialSelector 1018 | 1019 | fiducialSelectorLabel = qt.QLabel("{0}: ".format(member["name"])) 1020 | self.widgets.append(fiducialSelectorLabel) 1021 | 1022 | icon = qt.QIcon(ICON_DIR + "Fiducials.png") 1023 | 1024 | toggle = qt.QPushButton(icon, "") 1025 | toggle.setCheckable(True) 1026 | toggle.toolTip = "Toggle Fiducial Selection" 1027 | self.widgets.append(toggle) 1028 | 1029 | w2 = self.createVectorWidget(member["name"], t) 1030 | 1031 | hlayout = qt.QHBoxLayout() 1032 | hlayout.addWidget(fiducialSelector) 1033 | hlayout.setStretchFactor(fiducialSelector, 1) 1034 | hlayout.addWidget(w2) 1035 | hlayout.setStretchFactor(w2, 1) 1036 | hlayout.addWidget(toggle) 1037 | hlayout.setStretchFactor(toggle, 0) 1038 | w1.hide() 1039 | 1040 | self.widgets.append(hlayout) 1041 | 1042 | toggle.connect("clicked(bool)", 1043 | lambda checked, ptW=w2, fidW=w1: self.onToggledPointSelector(checked, ptW, fidW)) 1044 | 1045 | # parametersFormLayout.addRow(fiducialSelectorLabel, hlayout) 1046 | 1047 | else: 1048 | w = self.createVectorWidget(member["name"], t) 1049 | 1050 | elif t == "point_vec": 1051 | 1052 | fiducialSelector = slicer.qMRMLNodeComboBox() 1053 | self.widgets.append(fiducialSelector) 1054 | fiducialSelector.nodeTypes = ("vtkMRMLMarkupsFiducialNode", "vtkMRMLAnnotationHierarchyNode") 1055 | fiducialSelector.addAttribute("vtkMRMLAnnotationHierarchyNode", "MainChildType", 1056 | "vtkMRMLAnnotationFiducialNode") 1057 | fiducialSelector.selectNodeUponCreation = True 1058 | fiducialSelector.addEnabled = True 1059 | fiducialSelector.removeEnabled = False 1060 | fiducialSelector.renameEnabled = True 1061 | fiducialSelector.noneEnabled = False 1062 | fiducialSelector.showHidden = False 1063 | fiducialSelector.showChildNodeTypes = True 1064 | fiducialSelector.setMRMLScene(slicer.mrmlScene) 1065 | fiducialSelector.setToolTip("Pick the Markups node for the point list.") 1066 | 1067 | fiducialSelector.connect("nodeActivated(vtkMRMLNode*)", 1068 | lambda node, name=member["name"] 1069 | : self.onFiducialListNode(name, node, 1070 | member["iotype"])) 1071 | self.prerun_callbacks.append(lambda w=fiducialSelector, 1072 | name=member["name"], 1073 | : self.onFiducialListNode(name, 1074 | w.currentNode(), 1075 | member["iotype"])) 1076 | w = fiducialSelector 1077 | 1078 | elif "enum" in member: 1079 | w = self.createEnumWidget(member["name"], member["enum"]) 1080 | 1081 | elif member["name"].endswith("Direction") and "std::vector" in t: 1082 | # This member name is use for direction cosine matrix for image sources. 1083 | # We are going to ignore it 1084 | pass 1085 | elif t == "volume": 1086 | w = self.createVolumeWidget(member["name"], member["iotype"], member["voltype"], False) 1087 | 1088 | elif t == "InterpolatorEnum": 1089 | labels = ["Nearest Neighbor", 1090 | "Linear", 1091 | "BSpline", 1092 | "Gaussian", 1093 | "Label Gaussian", 1094 | "Hamming Windowed Sinc", 1095 | "Cosine Windowed Sinc", 1096 | "Welch Windowed Sinc", 1097 | "Lanczos Windowed Sinc", 1098 | "Blackman Windowed Sinc"] 1099 | values = ["sitk.sitkNearestNeighbor", 1100 | "sitk.sitkLinear", 1101 | "sitk.sitkBSpline", 1102 | "sitk.sitkGaussian", 1103 | "sitk.sitkLabelGaussian", 1104 | "sitk.sitkHammingWindowedSinc", 1105 | "sitk.sitkCosineWindowedSinc", 1106 | "sitk.sitkWelchWindowedSinc", 1107 | "sitk.sitkLanczosWindowedSinc", 1108 | "sitk.sitkBlackmanWindowedSinc"] 1109 | 1110 | w = self.createEnumWidget(member["name"], labels, values) 1111 | pass 1112 | elif t == "PixelIDValueEnum": 1113 | labels = ["int8_t", 1114 | "uint8_t", 1115 | "int16_t", 1116 | "uint16_t", 1117 | "uint32_t", 1118 | "int32_t", 1119 | "float", 1120 | "double"] 1121 | values = ["sitk.sitkInt8", 1122 | "sitk.sitkUInt8", 1123 | "sitk.sitkInt16", 1124 | "sitk.sitkUInt16", 1125 | "sitk.sitkInt32", 1126 | "sitk.sitkUInt32", 1127 | "sitk.sitkFloat32", 1128 | "sitk.sitkFloat64"] 1129 | w = self.createEnumWidget(member["name"], labels, values) 1130 | elif t in ["double", "float"]: 1131 | w = self.createDoubleWidget(member["name"], default=member["default"]) 1132 | elif t == "bool": 1133 | w = self.createBoolWidget(member["name"], default=member["default"]) 1134 | elif t in ["uint8_t", "int8_t", 1135 | "uint16_t", "int16_t", 1136 | "uint32_t", "int32_t", 1137 | "uint64_t", "int64_t", 1138 | "unsigned int", "int"]: 1139 | w = self.createIntWidget(member["name"], t, default=member["default"]) 1140 | else: 1141 | import sys 1142 | sys.stderr.write("Unknown member \"{0}\" of type \"{1}\"\n".format(member["name"], member["type"])) 1143 | 1144 | if w: 1145 | self.addWidgetWithToolTipAndLabel(w, member) 1146 | 1147 | def createVolumeWidget(self, name, iotype, voltype, noneEnabled=False): 1148 | # print("create volume widget : {0}".format(name)) 1149 | volumeSelector = slicer.qMRMLNodeComboBox() 1150 | self.widgets.append(volumeSelector) 1151 | if voltype == 'ScalarVolume': 1152 | volumeSelector.nodeTypes = ["vtkMRMLScalarVolumeNode", ] 1153 | elif voltype == 'LabelMap': 1154 | volumeSelector.nodeTypes = ["vtkMRMLLabelMapVolumeNode", ] 1155 | else: 1156 | print('Voltype must be either ScalarVolume or LabelMap!') 1157 | volumeSelector.selectNodeUponCreation = True 1158 | if iotype == "input": 1159 | volumeSelector.addEnabled = False 1160 | elif iotype == "output": 1161 | volumeSelector.addEnabled = True 1162 | volumeSelector.renameEnabled = True 1163 | volumeSelector.removeEnabled = True 1164 | volumeSelector.noneEnabled = noneEnabled 1165 | volumeSelector.showHidden = False 1166 | volumeSelector.showChildNodeTypes = False 1167 | volumeSelector.setMRMLScene(slicer.mrmlScene) 1168 | volumeSelector.setToolTip("Pick the volume.") 1169 | 1170 | # connect and verify parameters 1171 | volumeSelector.connect("currentNodeChanged(vtkMRMLNode*)", 1172 | lambda node, n=name, io=iotype: self.onVolumeSelect(node, n, io)) 1173 | if iotype == "input": 1174 | self.inputs[name] = volumeSelector.currentNode() 1175 | elif iotype == "output": 1176 | self.outputs[name] = volumeSelector.currentNode() 1177 | 1178 | return volumeSelector 1179 | 1180 | def createEnumWidget(self, name, enumList, valueList=None): 1181 | 1182 | w = qt.QComboBox() 1183 | self.widgets.append(w) 1184 | 1185 | # exec 'default=self.model.Get{0}()'.format(name) in globals(), locals() 1186 | 1187 | if valueList is None: 1188 | valueList = ["self.model." + e for e in enumList] 1189 | 1190 | for e, v in zip(enumList, valueList): 1191 | w.addItem(e, v) 1192 | 1193 | self.params[name] = w.currentText 1194 | w.connect("currentIndexChanged(int)", 1195 | lambda selectorIndex, n=name, selector=w: self.onEnumChanged(n, selectorIndex, selector)) 1196 | return w 1197 | 1198 | def createVectorWidget(self, name, type): 1199 | m = re.search(r"<([a-zA-Z ]+)>", type) 1200 | if m: 1201 | type = m.group(1) 1202 | 1203 | w = ctk.ctkCoordinatesWidget() 1204 | self.widgets.append(w) 1205 | 1206 | if type in ["double", "float"]: 1207 | w.setDecimals(5) 1208 | w.minimum = -3.40282e+038 1209 | w.maximum = 3.40282e+038 1210 | w.connect("coordinatesChanged(double*)", 1211 | lambda val, widget=w, name=name: self.onFloatVectorChanged(name, widget, val)) 1212 | elif type == "bool": 1213 | w.setDecimals(0) 1214 | w.minimum = 0 1215 | w.maximum = 1 1216 | w.connect("coordinatesChanged(double*)", 1217 | lambda val, widget=w, name=name: self.onBoolVectorChanged(name, widget, val)) 1218 | else: 1219 | w.setDecimals(0) 1220 | w.connect("coordinatesChanged(double*)", 1221 | lambda val, widget=w, name=name: self.onIntVectorChanged(name, widget, val)) 1222 | 1223 | # exec ('default = self.model.Get{0}()'.format(name)) in globals(), locals() 1224 | # w.coordinates = ",".join(str(x) for x in default) 1225 | return w 1226 | 1227 | def createIntWidget(self, name, type="int", default=None): 1228 | 1229 | w = qt.QSpinBox() 1230 | self.widgets.append(w) 1231 | 1232 | if type == "uint8_t": 1233 | w.setRange(0, 255) 1234 | elif type == "int8_t": 1235 | w.setRange(-128, 127) 1236 | elif type == "uint16_t": 1237 | w.setRange(0, 65535) 1238 | elif type == "int16_t": 1239 | w.setRange(-32678, 32767) 1240 | elif type == "uint32_t" or type == "uint64_t" or type == "unsigned int": 1241 | w.setRange(0, 2147483647) 1242 | elif type == "int32_t" or type == "uint64_t" or type == "int": 1243 | w.setRange(-2147483648, 2147483647) 1244 | 1245 | # exec ('default = self.model.Get{0}()'.format(name)) in globals(), locals() 1246 | if default is not None: 1247 | w.setValue(int(default)) 1248 | w.connect("valueChanged(int)", lambda val, name=name: self.onScalarChanged(name, val)) 1249 | return w 1250 | 1251 | def createBoolWidget(self, name, default): 1252 | # print('create bool widget') 1253 | w = qt.QCheckBox() 1254 | self.widgets.append(w) 1255 | if default == 'false': 1256 | checked = False 1257 | else: 1258 | checked = True 1259 | w.setChecked(checked) 1260 | self.params[name] = int(w.checked) 1261 | w.connect("stateChanged(int)", lambda val, name=name: self.onScalarChanged(name, int(val))) 1262 | 1263 | return w 1264 | 1265 | def createDoubleWidget(self, name, default=None): 1266 | # exec ('default = self.model.Get{0}()'.format(name)) in globals(), locals() 1267 | w = qt.QDoubleSpinBox() 1268 | self.widgets.append(w) 1269 | 1270 | w.setRange(-3.40282e+038, 3.40282e+038) 1271 | w.decimals = 5 1272 | 1273 | if default is not None: 1274 | w.setValue(default) 1275 | w.connect("valueChanged(double)", lambda val, name=name: self.onScalarChanged(name, val)) 1276 | 1277 | return w 1278 | 1279 | def addWidgetWithToolTipAndLabel(self, widget, memberJSON): 1280 | tip = "" 1281 | if "briefdescriptionSet" in memberJSON and len(memberJSON["briefdescriptionSet"]): 1282 | tip = memberJSON["briefdescriptionSet"] 1283 | elif "detaileddescriptionSet" in memberJSON: 1284 | tip = memberJSON["detaileddescriptionSet"] 1285 | 1286 | # remove trailing white space 1287 | tip = tip.rstrip() 1288 | 1289 | l = qt.QLabel(self.BeautifyCamelCase(memberJSON["name"]) + ": ") 1290 | self.widgets.append(l) 1291 | 1292 | widget.setToolTip(tip) 1293 | l.setToolTip(tip) 1294 | 1295 | parametersFormLayout = self.parent.layout() 1296 | parametersFormLayout.addRow(l, widget) 1297 | 1298 | def onToggledPointSelector(self, fidVisible, ptWidget, fiducialWidget): 1299 | ptWidget.setVisible(False) 1300 | fiducialWidget.setVisible(False) 1301 | 1302 | ptWidget.setVisible(not fidVisible) 1303 | fiducialWidget.setVisible(fidVisible) 1304 | 1305 | if ptWidget.visible: 1306 | # Update the coordinate values to envoke the changed signal. 1307 | # This will update the model from the widget 1308 | ptWidget.coordinates = ",".join(str(x) for x in ptWidget.coordinates.split(',')) 1309 | 1310 | def onVolumeSelect(self, mrmlNode, n, io): 1311 | # print("on volume select:{}".format(n)) 1312 | if io == "input": 1313 | self.inputs[n] = mrmlNode 1314 | elif io == "output": 1315 | self.outputs[n] = mrmlNode 1316 | 1317 | ''' 1318 | def onOutputSelect(self, mrmlNode): 1319 | self.output = mrmlNode 1320 | self.onOutputLabelMapChanged(mrmlNode.IsA("vtkMRMLLabelMapVolumeNode")) 1321 | 1322 | def onOutputLabelMapChanged(self, v): 1323 | self.outputLabelMap = v 1324 | self.outputLabelMapBox.setChecked(v) 1325 | ''' 1326 | 1327 | def onFiducialNode(self, name, mrmlWidget, isPoint): 1328 | if not mrmlWidget.visible: 1329 | return 1330 | annotationFiducialNode = mrmlWidget.currentNode() 1331 | 1332 | # point in physical space 1333 | coord = [0, 0, 0] 1334 | 1335 | if annotationFiducialNode.GetClassName() == "vtkMRMLMarkupsFiducialNode": 1336 | # slicer4 Markups node 1337 | if annotationFiducialNode.GetNumberOfFiducials() < 1: 1338 | return 1339 | annotationFiducialNode.GetNthFiducialPosition(0, coord) 1340 | else: 1341 | annotationFiducialNode.GetFiducialCoordinates(coord) 1342 | 1343 | # HACK transform from RAS to LPS 1344 | coord = [-coord[0], -coord[1], coord[2]] 1345 | 1346 | # FIXME: we should not need to copy the image 1347 | if not isPoint and len(self.inputs) and self.inputs[0]: 1348 | imgNodeName = self.inputs[0].GetName() 1349 | img = sitk.ReadImage(sitkUtils.GetSlicerITKReadWriteAddress(imgNodeName)) 1350 | coord = img.TransformPhysicalPointToIndex(coord) 1351 | # exec ('self.model.Set{0}(coord)'.format(name)) 1352 | 1353 | def onFiducialListNode(self, name, mrmlNode, io): 1354 | self.params[name] = mrmlNode 1355 | if name.lower().startswith("input"): 1356 | self.inputs[name] = mrmlNode 1357 | else: 1358 | self.outputs[name] = mrmlNode 1359 | 1360 | ''' 1361 | annotationHierarchyNode = mrmlNode 1362 | 1363 | # list of points in physical space 1364 | coords = [] 1365 | 1366 | if annotationHierarchyNode.GetClassName() == "vtkMRMLMarkupsFiducialNode": 1367 | # slicer4 Markups node 1368 | 1369 | for i in range(annotationHierarchyNode.GetNumberOfFiducials()): 1370 | coord = [0, 0, 0] 1371 | annotationHierarchyNode.GetNthFiducialPosition(i, coord) 1372 | coords.append(coord) 1373 | else: 1374 | # slicer4 style hierarchy nodes 1375 | 1376 | # get the first in the list 1377 | for listIndex in range(annotationHierarchyNode.GetNumberOfChildrenNodes()): 1378 | if annotationHierarchyNode.GetNthChildNode(listIndex) is None: 1379 | continue 1380 | 1381 | annotation = annotationHierarchyNode.GetNthChildNode(listIndex).GetAssociatedNode() 1382 | if annotation is None: 1383 | continue 1384 | 1385 | coord = [0, 0, 0] 1386 | annotation.GetFiducialCoordinates(coord) 1387 | coords.append(coord) 1388 | 1389 | if self.inputs[0]: 1390 | imgNodeName = self.inputs[0].GetName() 1391 | img = sitk.ReadImage(sitkUtils.GetSlicerITKReadWriteAddress(imgNodeName)) 1392 | 1393 | # HACK transform from RAS to LPS 1394 | coords = [[-pt[0], -pt[1], pt[2]] for pt in coords] 1395 | 1396 | idx_coords = [img.TransformPhysicalPointToIndex(pt) for pt in coords] 1397 | 1398 | # exec ('self.model.Set{0}(idx_coords)'.format(name)) 1399 | ''' 1400 | 1401 | def onScalarChanged(self, name, val): 1402 | # exec ('self.model.Set{0}(val)'.format(name)) 1403 | # print("onScalarChanged") 1404 | self.params[name] = val 1405 | 1406 | def onEnumChanged(self, name, selectorIndex, selector): 1407 | # data = selector.itemData(selectorIndex) 1408 | self.params[name] = selector.currentText 1409 | 1410 | def onBoolVectorChanged(self, name, widget, val): 1411 | coords = [bool(float(x)) for x in widget.coordinates.split(',')] 1412 | # exec ('self.model.Set{0}(coords)'.format(name)) 1413 | 1414 | def onIntVectorChanged(self, name, widget, val): 1415 | coords = [int(float(x)) for x in widget.coordinates.split(',')] 1416 | # exec ('self.model.Set{0}(coords)'.format(name)) 1417 | 1418 | def onFloatVectorChanged(self, name, widget, val): 1419 | coords = [float(x) for x in widget.coordinates.split(',')] 1420 | # exec ('self.model.Set{0}(coords)'.format(name)) 1421 | 1422 | def prerun(self): 1423 | print('prerun...') 1424 | for f in self.prerun_callbacks: 1425 | f() 1426 | 1427 | def destroy(self): 1428 | self.iodict = dict() 1429 | self.inputs = dict() 1430 | self.outputs = dict() 1431 | for w in self.widgets: 1432 | # self.parent.layout().removeWidget(w) 1433 | w.deleteLater() 1434 | w.setParent(None) 1435 | self.widgets = [] 1436 | -------------------------------------------------------------------------------- /DeepInfer/Resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/Resources/.DS_Store -------------------------------------------------------------------------------- /DeepInfer/Resources/Icons/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/Resources/Icons/.DS_Store -------------------------------------------------------------------------------- /DeepInfer/Resources/Icons/DeepInfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/Resources/Icons/DeepInfer.png -------------------------------------------------------------------------------- /DeepInfer/Resources/Icons/Fiducials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/Resources/Icons/Fiducials.png -------------------------------------------------------------------------------- /DeepInfer/Resources/json/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/Resources/json/.DS_Store -------------------------------------------------------------------------------- /DeepInfer/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Python) 2 | -------------------------------------------------------------------------------- /DeepInfer/Testing/Python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #slicer_add_python_unittest(SCRIPT ${MODULE_NAME}ModuleTest.py) 3 | -------------------------------------------------------------------------------- /DeepInfer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepInfer/Slicer-DeepInfer/a78b5fa90ef161a9438b525aa5fcc95cf5ce8d75/DeepInfer/__init__.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | For more information, please see: 3 | 4 | http://www.slicer.org 5 | 6 | The 3D Slicer license below is a BSD style license, with extensions 7 | to cover contributions and other issues specific to 3D Slicer. 8 | 9 | 10 | 3D Slicer Contribution and Software License Agreement ("Agreement") 11 | Version 1.0 (December 20, 2005) 12 | 13 | This Agreement covers contributions to and downloads from the 3D 14 | Slicer project ("Slicer") maintained by The Brigham and Women's 15 | Hospital, Inc. ("Brigham"). Part A of this Agreement applies to 16 | contributions of software and/or data to Slicer (including making 17 | revisions of or additions to code and/or data already in Slicer). Part 18 | B of this Agreement applies to downloads of software and/or data from 19 | Slicer. Part C of this Agreement applies to all transactions with 20 | Slicer. If you distribute Software (as defined below) downloaded from 21 | Slicer, all of the paragraphs of Part B of this Agreement must be 22 | included with and apply to such Software. 23 | 24 | Your contribution of software and/or data to Slicer (including prior 25 | to the date of the first publication of this Agreement, each a 26 | "Contribution") and/or downloading, copying, modifying, displaying, 27 | distributing or use of any software and/or data from Slicer 28 | (collectively, the "Software") constitutes acceptance of all of the 29 | terms and conditions of this Agreement. If you do not agree to such 30 | terms and conditions, you have no right to contribute your 31 | Contribution, or to download, copy, modify, display, distribute or use 32 | the Software. 33 | 34 | PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to 35 | Sublicense ("Contribution Agreement"). 36 | 37 | 1. As used in this Contribution Agreement, "you" means the individual 38 | contributing the Contribution to Slicer and the institution or 39 | entity which employs or is otherwise affiliated with such 40 | individual in connection with such Contribution. 41 | 42 | 2. This Contribution Agreement applies to all Contributions made to 43 | Slicer, including without limitation Contributions made prior to 44 | the date of first publication of this Agreement. If at any time you 45 | make a Contribution to Slicer, you represent that (i) you are 46 | legally authorized and entitled to make such Contribution and to 47 | grant all licenses granted in this Contribution Agreement with 48 | respect to such Contribution; (ii) if your Contribution includes 49 | any patient data, all such data is de-identified in accordance with 50 | U.S. confidentiality and security laws and requirements, including 51 | but not limited to the Health Insurance Portability and 52 | Accountability Act (HIPAA) and its regulations, and your disclosure 53 | of such data for the purposes contemplated by this Agreement is 54 | properly authorized and in compliance with all applicable laws and 55 | regulations; and (iii) you have preserved in the Contribution all 56 | applicable attributions, copyright notices and licenses for any 57 | third party software or data included in the Contribution. 58 | 59 | 3. Except for the licenses granted in this Agreement, you reserve all 60 | right, title and interest in your Contribution. 61 | 62 | 4. You hereby grant to Brigham, with the right to sublicense, a 63 | perpetual, worldwide, non-exclusive, no charge, royalty-free, 64 | irrevocable license to use, reproduce, make derivative works of, 65 | display and distribute the Contribution. If your Contribution is 66 | protected by patent, you hereby grant to Brigham, with the right to 67 | sublicense, a perpetual, worldwide, non-exclusive, no-charge, 68 | royalty-free, irrevocable license under your interest in patent 69 | rights covering the Contribution, to make, have made, use, sell and 70 | otherwise transfer your Contribution, alone or in combination with 71 | any other code. 72 | 73 | 5. You acknowledge and agree that Brigham may incorporate your 74 | Contribution into Slicer and may make Slicer available to members 75 | of the public on an open source basis under terms substantially in 76 | accordance with the Software License set forth in Part B of this 77 | Agreement. You further acknowledge and agree that Brigham shall 78 | have no liability arising in connection with claims resulting from 79 | your breach of any of the terms of this Agreement. 80 | 81 | 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION 82 | DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN 83 | SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting 84 | example, the GNU General Public License or other so-called 85 | "reciprocal" license that requires any derived work to be licensed 86 | under the GNU General Public License or other "open source 87 | license"). 88 | 89 | PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to 90 | Sublicense ("Software License"). 91 | 92 | 1. As used in this Software License, "you" means the individual 93 | downloading and/or using, reproducing, modifying, displaying and/or 94 | distributing the Software and the institution or entity which 95 | employs or is otherwise affiliated with such individual in 96 | connection therewith. The Brigham and Women?s Hospital, 97 | Inc. ("Brigham") hereby grants you, with right to sublicense, with 98 | respect to Brigham's rights in the software, and data, if any, 99 | which is the subject of this Software License (collectively, the 100 | "Software"), a royalty-free, non-exclusive license to use, 101 | reproduce, make derivative works of, display and distribute the 102 | Software, provided that: 103 | 104 | (a) you accept and adhere to all of the terms and conditions of this 105 | Software License; 106 | 107 | (b) in connection with any copy of or sublicense of all or any portion 108 | of the Software, all of the terms and conditions in this Software 109 | License shall appear in and shall apply to such copy and such 110 | sublicense, including without limitation all source and executable 111 | forms and on any user documentation, prefaced with the following 112 | words: "All or portions of this licensed product (such portions are 113 | the "Software") have been obtained under license from The Brigham and 114 | Women's Hospital, Inc. and are subject to the following terms and 115 | conditions:" 116 | 117 | (c) you preserve and maintain all applicable attributions, copyright 118 | notices and licenses included in or applicable to the Software; 119 | 120 | (d) modified versions of the Software must be clearly identified and 121 | marked as such, and must not be misrepresented as being the original 122 | Software; and 123 | 124 | (e) you consider making, but are under no obligation to make, the 125 | source code of any of your modifications to the Software freely 126 | available to others on an open source basis. 127 | 128 | 2. The license granted in this Software License includes without 129 | limitation the right to (i) incorporate the Software into 130 | proprietary programs (subject to any restrictions applicable to 131 | such programs), (ii) add your own copyright statement to your 132 | modifications of the Software, and (iii) provide additional or 133 | different license terms and conditions in your sublicenses of 134 | modifications of the Software; provided that in each case your use, 135 | reproduction or distribution of such modifications otherwise 136 | complies with the conditions stated in this Software License. 137 | 138 | 3. This Software License does not grant any rights with respect to 139 | third party software, except those rights that Brigham has been 140 | authorized by a third party to grant to you, and accordingly you 141 | are solely responsible for (i) obtaining any permissions from third 142 | parties that you need to use, reproduce, make derivative works of, 143 | display and distribute the Software, and (ii) informing your 144 | sublicensees, including without limitation your end-users, of their 145 | obligations to secure any such required permissions. 146 | 147 | 4. The Software has been designed for research purposes only and has 148 | not been reviewed or approved by the Food and Drug Administration 149 | or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL 150 | APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any 151 | commercialization of the Software is at the sole risk of the party 152 | or parties engaged in such commercialization. You further agree to 153 | use, reproduce, make derivative works of, display and distribute 154 | the Software in compliance with all applicable governmental laws, 155 | regulations and orders, including without limitation those relating 156 | to export and import control. 157 | 158 | 5. The Software is provided "AS IS" and neither Brigham nor any 159 | contributor to the software (each a "Contributor") shall have any 160 | obligation to provide maintenance, support, updates, enhancements 161 | or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY 162 | DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING, 163 | BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR 164 | A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 165 | BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT, 166 | INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES 167 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY 168 | RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS 169 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM 170 | EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL 171 | LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS, 172 | DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO 173 | INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND 174 | AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS 175 | ARISING THEREFROM. 176 | 177 | 6. None of the names, logos or trademarks of Brigham or any of 178 | Brigham's affiliates or any of the Contributors, or any funding 179 | agency, may be used to endorse or promote products produced in 180 | whole or in part by operation of the Software or derived from or 181 | based on the Software without specific prior written permission 182 | from the applicable party. 183 | 184 | 7. Any use, reproduction or distribution of the Software which is not 185 | in accordance with this Software License shall automatically revoke 186 | all rights granted to you under this Software License and render 187 | Paragraphs 1 and 2 of this Software License null and void. 188 | 189 | 8. This Software License does not grant any rights in or to any 190 | intellectual property owned by Brigham or any Contributor except 191 | those rights expressly granted hereunder. 192 | 193 | PART C. MISCELLANEOUS 194 | 195 | This Agreement shall be governed by and construed in accordance with 196 | the laws of The Commonwealth of Massachusetts without regard to 197 | principles of conflicts of law. This Agreement shall supercede and 198 | replace any license terms that you may have agreed to previously with 199 | respect to Slicer. 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slicer-DeepInfer 2 | 3D Slicer [1] module to provide GUI to communicate with DeepInfer model 3 | repository and to perform image analysis using models inside Slicer. 4 | 5 | [1] [3D Slicer](https://www.slicer.org/) 6 | --------------------------------------------------------------------------------