├── .gitignore ├── CMake └── FindZeroMQ.cmake ├── CMakeLists.txt ├── CTestConfig.cmake ├── JupyterKernel ├── CMakeLists.txt ├── Logic │ ├── CMakeLists.txt │ ├── vtkSlicerJupyterKernelLogic.cxx │ └── vtkSlicerJupyterKernelLogic.h ├── Resources │ ├── Icons │ │ ├── JupyterKernel.png │ │ ├── Slicer-32x32.png │ │ └── Slicer-64x64.png │ ├── UI │ │ └── qSlicerJupyterKernelModuleWidget.ui │ ├── kernel-configure.py │ ├── kernel-template.json.in │ └── qSlicerJupyterKernelModule.qrc ├── Testing │ ├── CMakeLists.txt │ └── Cxx │ │ └── CMakeLists.txt ├── qSlicerJupyterKernelModule.cxx ├── qSlicerJupyterKernelModule.h ├── qSlicerJupyterKernelModuleWidget.cxx ├── qSlicerJupyterKernelModuleWidget.h ├── xSlicerInterpreter.cxx ├── xSlicerInterpreter.h ├── xSlicerServer.cxx └── xSlicerServer.h ├── JupyterNotebooks ├── CMakeLists.txt ├── JupyterNotebooks.py ├── JupyterNotebooksLib │ ├── __init__.py │ ├── cli.py │ ├── display.py │ ├── files.py │ ├── interactive_view_widget.py │ └── widgets.py └── Resources │ └── Icons │ └── SlicerAdvancedGear-Small.png ├── LICENSE ├── README.md ├── SlicerJupyterLogo.png ├── SlicerJupyterLogo.xcf ├── SuperBuild.cmake ├── SuperBuild ├── External_ZeroMQ.cmake ├── External_cppzmq.cmake ├── External_nlohmann_json.cmake ├── External_pybind11.cmake ├── External_pybind11_json.cmake ├── External_python-packages.cmake ├── External_xeus-python.cmake ├── External_xeus.cmake └── External_xtl.cmake └── doc ├── AutoComplete.png ├── Inspect.png ├── InstallVideoThumbnail.png └── StartKernel.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMake/FindZeroMQ.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find ZMQ 2 | # Once done this will define 3 | # ZMQ_FOUND - System has ZMQ 4 | # ZMQ_INCLUDE_DIRS - The ZMQ include directories 5 | # ZMQ_LIBRARIES - The libraries needed to use ZMQ 6 | # ZMQ_DEFINITIONS - Compiler switches required for using ZMQ 7 | 8 | MESSAGE(WARNING "Trying to find zmq") 9 | 10 | find_path ( ZMQ_INCLUDE_DIR zmq.h ) 11 | find_library ( ZMQ_LIBRARY NAMES zmq ) 12 | 13 | set ( ZMQ_LIBRARIES ${ZMQ_LIBRARY} ) 14 | set ( ZMQ_INCLUDE_DIRS ${ZMQ_INCLUDE_DIR} ) 15 | 16 | include ( FindPackageHandleStandardArgs ) 17 | # handle the QUIETLY and REQUIRED arguments and set ZMQ_FOUND to TRUE 18 | # if all listed variables are TRUE 19 | find_package_handle_standard_args ( ZMQ DEFAULT_MSG ZMQ_LIBRARY ZMQ_INCLUDE_DIR ) 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(SlicerJupyter) 4 | 5 | #----------------------------------------------------------------------------- 6 | # Extension meta-information 7 | set(EXTENSION_HOMEPAGE "https://github.com/Slicer/SlicerJupyter") 8 | set(EXTENSION_CATEGORY "Developer Tools") 9 | set(EXTENSION_CONTRIBUTORS "Andras Lasso (PerkLab), Jean-Christophe Fillion-Robin (Kitware)") 10 | set(EXTENSION_DESCRIPTION "This extension provides a Jupyter kernel, which allows running Jupyter notebooks in 3D Slicer.") 11 | set(EXTENSION_ICONURL "https://raw.githubusercontent.com/Slicer/SlicerJupyter/master/SlicerJupyterLogo.png") 12 | set(EXTENSION_SCREENSHOTURLS "https://raw.githubusercontent.com/NA-MIC/ProjectWeek/master/PW28_2018_GranCanaria/Projects/SlicerJupyter/NotebookOnly.png https://raw.githubusercontent.com/NA-MIC/ProjectWeek/master/PW28_2018_GranCanaria/Projects/SlicerJupyter/NotebookSideBySide.png https://raw.githubusercontent.com/NA-MIC/ProjectWeek/master/PW28_2018_GranCanaria/Projects/SlicerJupyter/JupyterLab.png") 13 | set(EXTENSION_DEPENDS "NA") # Specified as a space separated string, a list or 'NA' if any 14 | set(EXTENSION_BUILD_SUBDIRECTORY inner-build) 15 | 16 | set(SUPERBUILD_TOPLEVEL_PROJECT inner) 17 | 18 | #----------------------------------------------------------------------------- 19 | # Extension dependencies 20 | find_package(Slicer REQUIRED) 21 | include(${Slicer_USE_FILE}) 22 | mark_as_superbuild(Slicer_DIR) 23 | 24 | find_package(Git REQUIRED) 25 | mark_as_superbuild(GIT_EXECUTABLE) 26 | 27 | #----------------------------------------------------------------------------- 28 | # SuperBuild setup 29 | option(${EXTENSION_NAME}_SUPERBUILD "Build ${EXTENSION_NAME} and the projects it depends on." ON) 30 | mark_as_advanced(${EXTENSION_NAME}_SUPERBUILD) 31 | if(${EXTENSION_NAME}_SUPERBUILD) 32 | include("${CMAKE_CURRENT_SOURCE_DIR}/SuperBuild.cmake") 33 | return() 34 | endif() 35 | 36 | #----------------------------------------------------------------------------- 37 | # Extension modules 38 | add_subdirectory(JupyterKernel) 39 | add_subdirectory(JupyterNotebooks) 40 | 41 | #----------------------------------------------------------------------------- 42 | install(CODE "message(\"Installing python packages.\")") 43 | install(DIRECTORY "${python_packages_DIR}/" 44 | DESTINATION ${Slicer_INSTALL_ROOT}${Slicer_BUNDLE_EXTENSIONS_LOCATION} 45 | COMPONENT RuntimeLibraries) 46 | 47 | #----------------------------------------------------------------------------- 48 | set(EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS) 49 | list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${xeus_DIR};xeus;ALL;/") # xeus has no runtime install component 50 | list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${xeus-python_DIR};xeus-python;ALL;/") 51 | list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${ZeroMQ_DIR};ZeroMQ;Runtime;/") # this is not needed on Windows, but maybe needed on other platforms 52 | list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${ZeroMQ_DIR};ZeroMQ;Unspecified;/") # on Windows, libzmq-v140-mt-4_2_5.dll installed by this 53 | set(${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS "${EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS}" CACHE STRING "List of external projects to install" FORCE) 54 | 55 | #----------------------------------------------------------------------------- 56 | list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${EXTENSION_NAME};ALL;/") 57 | list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS}") 58 | include(${Slicer_EXTENSION_GENERATE_CONFIG}) 59 | include(${Slicer_EXTENSION_CPACK}) 60 | -------------------------------------------------------------------------------- /CTestConfig.cmake: -------------------------------------------------------------------------------- 1 | set(CTEST_PROJECT_NAME "SlicerJupyter") 2 | set(CTEST_NIGHTLY_START_TIME "3:00:00 UTC") 3 | 4 | set(CTEST_DROP_METHOD "http") 5 | set(CTEST_DROP_SITE "slicer.cdash.org") 6 | set(CTEST_DROP_LOCATION "/submit.php?project=SlicerPreview") 7 | set(CTEST_DROP_SITE_CDASH TRUE) 8 | -------------------------------------------------------------------------------- /JupyterKernel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(MODULE_NAME JupyterKernel) 4 | set(MODULE_TITLE ${MODULE_NAME}) 5 | 6 | string(TOUPPER ${MODULE_NAME} MODULE_NAME_UPPER) 7 | 8 | find_package(xeus REQUIRED) 9 | find_package(xeus-python REQUIRED) 10 | find_package(pybind11 REQUIRED) 11 | 12 | #----------------------------------------------------------------------------- 13 | add_subdirectory(Logic) 14 | 15 | #----------------------------------------------------------------------------- 16 | set(MODULE_EXPORT_DIRECTIVE "Q_SLICER_QTMODULES_${MODULE_NAME_UPPER}_EXPORT") 17 | 18 | # Current_{source,binary} and Slicer_{Libs,Base} already included 19 | set(MODULE_INCLUDE_DIRECTORIES 20 | ${CMAKE_CURRENT_SOURCE_DIR}/Logic 21 | ${CMAKE_CURRENT_BINARY_DIR}/Logic 22 | ${pybind11_INCLUDE_DIR} 23 | ) 24 | 25 | set(MODULE_SRCS 26 | qSlicer${MODULE_NAME}Module.cxx 27 | qSlicer${MODULE_NAME}Module.h 28 | qSlicer${MODULE_NAME}ModuleWidget.cxx 29 | qSlicer${MODULE_NAME}ModuleWidget.h 30 | xSlicerInterpreter.cxx 31 | xSlicerInterpreter.h 32 | xSlicerServer.cxx 33 | xSlicerServer.h 34 | ) 35 | 36 | set(MODULE_MOC_SRCS 37 | qSlicer${MODULE_NAME}Module.h 38 | qSlicer${MODULE_NAME}ModuleWidget.h 39 | ) 40 | 41 | set(MODULE_UI_SRCS 42 | Resources/UI/qSlicer${MODULE_NAME}ModuleWidget.ui 43 | ) 44 | 45 | set(MODULE_TARGET_LIBRARIES 46 | vtkSlicer${MODULE_NAME}ModuleLogic 47 | xeus 48 | xeus-python 49 | ) 50 | 51 | set(MODULE_RESOURCES 52 | Resources/qSlicer${MODULE_NAME}Module.qrc 53 | ) 54 | 55 | #----------------------------------------------------------------------------- 56 | slicerMacroBuildLoadableModule( 57 | NAME ${MODULE_NAME} 58 | TITLE ${MODULE_TITLE} 59 | EXPORT_DIRECTIVE ${MODULE_EXPORT_DIRECTIVE} 60 | INCLUDE_DIRECTORIES ${MODULE_INCLUDE_DIRECTORIES} 61 | SRCS ${MODULE_SRCS} 62 | MOC_SRCS ${MODULE_MOC_SRCS} 63 | UI_SRCS ${MODULE_UI_SRCS} 64 | TARGET_LIBRARIES ${MODULE_TARGET_LIBRARIES} 65 | RESOURCES ${MODULE_RESOURCES} 66 | WITH_GENERIC_TESTS 67 | ) 68 | 69 | #----------------------------------------------------------------------------- 70 | # Create kernel specification template 71 | 72 | # Build tree 73 | configure_file( 74 | Resources/kernel-template.json.in 75 | ${CMAKE_BINARY_DIR}/${Slicer_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION}/kernel-template.json 76 | @ONLY 77 | ) 78 | configure_file( 79 | Resources/kernel-configure.py 80 | ${CMAKE_BINARY_DIR}/${Slicer_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION}/kernel-configure.py 81 | @ONLY 82 | ) 83 | # Install tree 84 | configure_file( 85 | Resources/kernel-template.json.in 86 | ${SlicerJupyter_BINARY_DIR}/kernel-template.json 87 | @ONLY 88 | ) 89 | install( 90 | FILES ${SlicerJupyter_BINARY_DIR}/kernel-template.json 91 | DESTINATION ${Slicer_INSTALL_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION} COMPONENT Runtime 92 | ) 93 | install( 94 | FILES Resources/kernel-configure.py 95 | DESTINATION ${Slicer_INSTALL_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION} COMPONENT Runtime 96 | ) 97 | 98 | foreach(res IN ITEMS 32 64) 99 | # Build tree 100 | configure_file( 101 | Resources/Icons/Slicer-${res}x${res}.png 102 | ${CMAKE_BINARY_DIR}/${Slicer_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION}/logo-${res}x${res}.png 103 | COPYONLY 104 | ) 105 | # Install tree 106 | install( 107 | FILES Resources/Icons/Slicer-${res}x${res}.png 108 | DESTINATION ${Slicer_INSTALL_QTLOADABLEMODULES_SHARE_DIR}/${MODULE_NAME}/${Slicer_MAIN_PROJECT_APPLICATION_NAME}-${Slicer_VERSION} COMPONENT Runtime 109 | ) 110 | endforeach() 111 | 112 | #----------------------------------------------------------------------------- 113 | if(BUILD_TESTING) 114 | add_subdirectory(Testing) 115 | endif() 116 | -------------------------------------------------------------------------------- /JupyterKernel/Logic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(vtkSlicer${MODULE_NAME}ModuleLogic) 2 | 3 | set(KIT ${PROJECT_NAME}) 4 | 5 | set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_LOGIC_EXPORT") 6 | 7 | set(${KIT}_INCLUDE_DIRECTORIES 8 | ) 9 | 10 | set(${KIT}_SRCS 11 | vtkSlicer${MODULE_NAME}Logic.cxx 12 | vtkSlicer${MODULE_NAME}Logic.h 13 | ) 14 | 15 | set(${KIT}_TARGET_LIBRARIES 16 | ${ITK_LIBRARIES} 17 | ) 18 | 19 | #----------------------------------------------------------------------------- 20 | SlicerMacroBuildModuleLogic( 21 | NAME ${KIT} 22 | EXPORT_DIRECTIVE ${${KIT}_EXPORT_DIRECTIVE} 23 | INCLUDE_DIRECTORIES ${${KIT}_INCLUDE_DIRECTORIES} 24 | SRCS ${${KIT}_SRCS} 25 | TARGET_LIBRARIES ${${KIT}_TARGET_LIBRARIES} 26 | ) 27 | -------------------------------------------------------------------------------- /JupyterKernel/Logic/vtkSlicerJupyterKernelLogic.cxx: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | // JupyterKernel Logic includes 19 | #include "vtkSlicerJupyterKernelLogic.h" 20 | 21 | // MRML includes 22 | #include 23 | 24 | // VTK includes 25 | #include 26 | #include 27 | #include 28 | 29 | // STD includes 30 | #include 31 | #include 32 | 33 | //---------------------------------------------------------------------------- 34 | vtkStandardNewMacro(vtkSlicerJupyterKernelLogic); 35 | 36 | //---------------------------------------------------------------------------- 37 | vtkSlicerJupyterKernelLogic::vtkSlicerJupyterKernelLogic() 38 | { 39 | } 40 | 41 | //---------------------------------------------------------------------------- 42 | vtkSlicerJupyterKernelLogic::~vtkSlicerJupyterKernelLogic() 43 | { 44 | } 45 | 46 | //---------------------------------------------------------------------------- 47 | void vtkSlicerJupyterKernelLogic::PrintSelf(ostream& os, vtkIndent indent) 48 | { 49 | this->Superclass::PrintSelf(os, indent); 50 | } 51 | 52 | //--------------------------------------------------------------------------- 53 | void vtkSlicerJupyterKernelLogic::SetMRMLSceneInternal(vtkMRMLScene * newScene) 54 | { 55 | vtkNew events; 56 | events->InsertNextValue(vtkMRMLScene::NodeAddedEvent); 57 | events->InsertNextValue(vtkMRMLScene::NodeRemovedEvent); 58 | events->InsertNextValue(vtkMRMLScene::EndBatchProcessEvent); 59 | this->SetAndObserveMRMLSceneEventsInternal(newScene, events.GetPointer()); 60 | } 61 | 62 | //----------------------------------------------------------------------------- 63 | void vtkSlicerJupyterKernelLogic::RegisterNodes() 64 | { 65 | assert(this->GetMRMLScene() != 0); 66 | } 67 | 68 | //--------------------------------------------------------------------------- 69 | void vtkSlicerJupyterKernelLogic::UpdateFromMRMLScene() 70 | { 71 | assert(this->GetMRMLScene() != 0); 72 | } 73 | 74 | //--------------------------------------------------------------------------- 75 | void vtkSlicerJupyterKernelLogic 76 | ::OnMRMLSceneNodeAdded(vtkMRMLNode* vtkNotUsed(node)) 77 | { 78 | } 79 | 80 | //--------------------------------------------------------------------------- 81 | void vtkSlicerJupyterKernelLogic 82 | ::OnMRMLSceneNodeRemoved(vtkMRMLNode* vtkNotUsed(node)) 83 | { 84 | } 85 | -------------------------------------------------------------------------------- /JupyterKernel/Logic/vtkSlicerJupyterKernelLogic.h: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | // .NAME vtkSlicerJupyterKernelLogic - slicer logic class for volumes manipulation 19 | // .SECTION Description 20 | // This class manages the logic associated with reading, saving, 21 | // and changing propertied of the volumes 22 | 23 | 24 | #ifndef __vtkSlicerJupyterKernelLogic_h 25 | #define __vtkSlicerJupyterKernelLogic_h 26 | 27 | // Slicer includes 28 | #include "vtkSlicerModuleLogic.h" 29 | 30 | // MRML includes 31 | 32 | // STD includes 33 | #include 34 | 35 | #include "vtkSlicerJupyterKernelModuleLogicExport.h" 36 | 37 | 38 | /// \ingroup Slicer_QtModules_ExtensionTemplate 39 | class VTK_SLICER_JUPYTERKERNEL_MODULE_LOGIC_EXPORT vtkSlicerJupyterKernelLogic : 40 | public vtkSlicerModuleLogic 41 | { 42 | public: 43 | 44 | static vtkSlicerJupyterKernelLogic *New(); 45 | vtkTypeMacro(vtkSlicerJupyterKernelLogic, vtkSlicerModuleLogic); 46 | void PrintSelf(ostream& os, vtkIndent indent) override; 47 | 48 | protected: 49 | vtkSlicerJupyterKernelLogic(); 50 | virtual ~vtkSlicerJupyterKernelLogic(); 51 | 52 | virtual void SetMRMLSceneInternal(vtkMRMLScene* newScene) override; 53 | /// Register MRML Node classes to Scene. Gets called automatically when the MRMLScene is attached to this logic class. 54 | virtual void RegisterNodes() override; 55 | virtual void UpdateFromMRMLScene() override; 56 | virtual void OnMRMLSceneNodeAdded(vtkMRMLNode* node) override; 57 | virtual void OnMRMLSceneNodeRemoved(vtkMRMLNode* node) override; 58 | 59 | private: 60 | 61 | vtkSlicerJupyterKernelLogic(const vtkSlicerJupyterKernelLogic&); // Not implemented 62 | void operator=(const vtkSlicerJupyterKernelLogic&); // Not implemented 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /JupyterKernel/Resources/Icons/JupyterKernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/JupyterKernel/Resources/Icons/JupyterKernel.png -------------------------------------------------------------------------------- /JupyterKernel/Resources/Icons/Slicer-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/JupyterKernel/Resources/Icons/Slicer-32x32.png -------------------------------------------------------------------------------- /JupyterKernel/Resources/Icons/Slicer-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/JupyterKernel/Resources/Icons/Slicer-64x64.png -------------------------------------------------------------------------------- /JupyterKernel/Resources/UI/qSlicerJupyterKernelModuleWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | qSlicerJupyterKernelModuleWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 409 10 | 384 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Jupyter server in Slicer Python environment 21 | 22 | 23 | 24 | 25 | 26 | Notebook directory: 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Stop Jupyter server 44 | 45 | 46 | 47 | 48 | 49 | 50 | Start Jupyter server 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Qt::Vertical 61 | 62 | 63 | QSizePolicy::Fixed 64 | 65 | 66 | 67 | 20 68 | 5 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 0 78 | 0 79 | 80 | 81 | 82 | Jupyter server in external Python environment 83 | 84 | 85 | true 86 | 87 | 88 | 89 | 90 | 91 | To install Slicer Kernel, execute this command in a terminal in the external Python environment: 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 16777215 100 | 50 101 | 102 | 103 | 104 | true 105 | 106 | 107 | 108 | 109 | 110 | 111 | Copy command to clipboard 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | Qt::Vertical 122 | 123 | 124 | 125 | 0 126 | 0 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | qSlicerWidget 136 | QWidget 137 |
qSlicerWidget.h
138 | 1 139 |
140 | 141 | ctkCollapsibleButton 142 | QWidget 143 |
ctkCollapsibleButton.h
144 | 1 145 |
146 | 147 | ctkPathLineEdit 148 | QWidget 149 |
ctkPathLineEdit.h
150 |
151 |
152 | 153 | 154 |
155 | -------------------------------------------------------------------------------- /JupyterKernel/Resources/kernel-configure.py: -------------------------------------------------------------------------------- 1 | # Content of this file is executed after the kernel is started. 2 | # Previously it was used for setting up a custom display hook, but currently 3 | # no additional intilization steps are needed. 4 | -------------------------------------------------------------------------------- /JupyterKernel/Resources/kernel-template.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "{slicer_application_name} {slicer_version_major}.{slicer_version_minor}", 3 | "language" : "python", 4 | "argv": [ 5 | "{slicer_launcher_executable}", 6 | "--no-splash", 7 | "--python-code", 8 | "connection_file=r'{connection_file}';print('JupyterConnectionFile:['+connection_file+']');slicer.modules.jupyterkernel.startKernel(connection_file);slicer.util.mainWindow().showMinimized()" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /JupyterKernel/Resources/qSlicerJupyterKernelModule.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Icons/JupyterKernel.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /JupyterKernel/Testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Cxx) 2 | -------------------------------------------------------------------------------- /JupyterKernel/Testing/Cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(KIT qSlicer${MODULE_NAME}Module) 2 | 3 | #----------------------------------------------------------------------------- 4 | set(KIT_TEST_SRCS 5 | #qSlicer${MODULE_NAME}ModuleTest.cxx 6 | ) 7 | 8 | #----------------------------------------------------------------------------- 9 | slicerMacroConfigureModuleCxxTestDriver( 10 | NAME ${KIT} 11 | SOURCES ${KIT_TEST_SRCS} 12 | WITH_VTK_DEBUG_LEAKS_CHECK 13 | WITH_VTK_ERROR_OUTPUT_CHECK 14 | ) 15 | 16 | #----------------------------------------------------------------------------- 17 | #simple_test(qSlicer${MODULE_NAME}ModuleTest) 18 | -------------------------------------------------------------------------------- /JupyterKernel/qSlicerJupyterKernelModule.cxx: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | // JupyterKernel Logic includes 19 | #include 20 | 21 | // JupyterKernel includes 22 | #include "qSlicerJupyterKernelModule.h" 23 | #include "qSlicerJupyterKernelModuleWidget.h" 24 | 25 | #include "qSlicerApplication.h" 26 | #include "qSlicerPythonManager.h" 27 | 28 | #include "PythonQt.h" 29 | 30 | // On Windows, pyerrors.h redefines snprintf to _snprintf 31 | // which causes error C2039: '_snprintf': is not a member of 'std' 32 | // while trying to compile std::snprintf in json.hpp (in nlohmann_json). 33 | // Until this is fixed in Python or made more robust in nlohman_json, 34 | // fix this here by undefining snprintf. 35 | #if defined(WIN32) && defined(snprintf) 36 | #undef snprintf 37 | #endif 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | // XEUS includes 48 | #include "xeus/xkernel.hpp" 49 | #include "xeus/xkernel_configuration.hpp" 50 | #include "xeus-python/xdebugger.hpp" 51 | 52 | #include "xSlicerInterpreter.h" 53 | #include "xSlicerServer.h" 54 | 55 | // Slicer includes 56 | #include "qSlicerApplication.h" 57 | #include "qSlicerCommandOptions.h" 58 | 59 | // Qt includes 60 | #include 61 | #include 62 | #include 63 | 64 | //----------------------------------------------------------------------------- 65 | #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) 66 | #include 67 | Q_EXPORT_PLUGIN2(qSlicerJupyterKernelModule, qSlicerJupyterKernelModule); 68 | #endif 69 | 70 | //----------------------------------------------------------------------------- 71 | /// \ingroup Slicer_QtModules_ExtensionTemplate 72 | class qSlicerJupyterKernelModulePrivate 73 | { 74 | protected: 75 | qSlicerJupyterKernelModule * const q_ptr; 76 | 77 | public: 78 | qSlicerJupyterKernelModulePrivate(qSlicerJupyterKernelModule& object); 79 | 80 | QProcess InternalJupyterServer; 81 | bool Started; 82 | QString ConnectionFile; 83 | xeus::xkernel * Kernel; 84 | xeus::xconfiguration Config; 85 | QLabel* StatusLabel; 86 | }; 87 | 88 | //----------------------------------------------------------------------------- 89 | // qSlicerJupyterKernelModulePrivate methods 90 | 91 | //----------------------------------------------------------------------------- 92 | qSlicerJupyterKernelModulePrivate::qSlicerJupyterKernelModulePrivate(qSlicerJupyterKernelModule& object) 93 | : q_ptr(&object) 94 | , Started(false) 95 | , Kernel(NULL) 96 | , StatusLabel(NULL) 97 | { 98 | } 99 | 100 | //----------------------------------------------------------------------------- 101 | // qSlicerJupyterKernelModule methods 102 | 103 | //----------------------------------------------------------------------------- 104 | qSlicerJupyterKernelModule::qSlicerJupyterKernelModule(QObject* _parent) 105 | : Superclass(_parent) 106 | , d_ptr(new qSlicerJupyterKernelModulePrivate(*this)) 107 | { 108 | } 109 | 110 | //----------------------------------------------------------------------------- 111 | qSlicerJupyterKernelModule::~qSlicerJupyterKernelModule() 112 | { 113 | } 114 | 115 | //----------------------------------------------------------------------------- 116 | QString qSlicerJupyterKernelModule::helpText() const 117 | { 118 | return "This extension provides a Jupyter kernel, which allows running Jupyter notebooks in 3D Slicer. See extension documentation for more details."; 119 | } 120 | 121 | //----------------------------------------------------------------------------- 122 | QString qSlicerJupyterKernelModule::acknowledgementText() const 123 | { 124 | return "This work was partially funded by CANARIE's Research Software Program"; 125 | } 126 | 127 | //----------------------------------------------------------------------------- 128 | QStringList qSlicerJupyterKernelModule::contributors() const 129 | { 130 | QStringList moduleContributors; 131 | moduleContributors << QString("Jean-Christoph Fillion-Robin (Kitware)") << QString("Andras Lasso (PerkLab)"); 132 | return moduleContributors; 133 | } 134 | 135 | //----------------------------------------------------------------------------- 136 | QIcon qSlicerJupyterKernelModule::icon() const 137 | { 138 | return QIcon(":/Icons/JupyterKernel.png"); 139 | } 140 | 141 | //----------------------------------------------------------------------------- 142 | QStringList qSlicerJupyterKernelModule::categories() const 143 | { 144 | return QStringList() << "Developer Tools"; 145 | } 146 | 147 | //----------------------------------------------------------------------------- 148 | QStringList qSlicerJupyterKernelModule::dependencies() const 149 | { 150 | return QStringList(); 151 | } 152 | 153 | //----------------------------------------------------------------------------- 154 | void qSlicerJupyterKernelModule::setup() 155 | { 156 | this->Superclass::setup(); 157 | } 158 | 159 | //----------------------------------------------------------------------------- 160 | bool qSlicerJupyterKernelModule::updateKernelSpec() 161 | { 162 | QString kernelFolder = this->resourceFolderPath(); 163 | if (kernelFolder.isEmpty()) 164 | { 165 | qWarning() << Q_FUNC_INFO << " failed: invalid kernel folder path"; 166 | return false; 167 | } 168 | 169 | QString kernelJsonTemplatePath = kernelFolder + "/kernel-template.json"; 170 | QFile templateFile(kernelJsonTemplatePath); 171 | if (!templateFile.exists()) 172 | { 173 | qWarning() << Q_FUNC_INFO << " kernel template file does not exist: " << kernelJsonTemplatePath; 174 | return false; 175 | } 176 | if (!templateFile.open(QIODevice::ReadOnly | QIODevice::Text)) 177 | { 178 | qWarning() << Q_FUNC_INFO << " failed to open kernel template file: " << kernelJsonTemplatePath; 179 | return false; 180 | } 181 | QTextStream in(&templateFile); 182 | QString kernelJson = in.readAll(); 183 | templateFile.close(); 184 | 185 | qSlicerApplication* app = qSlicerApplication::application(); 186 | if (kernelJson.indexOf("{slicer_application_name}") != -1) 187 | { 188 | kernelJson.replace("{slicer_application_name}", app->applicationName()); 189 | } 190 | if (kernelJson.indexOf("{slicer_version_full}") != -1) 191 | { 192 | kernelJson.replace("{slicer_version_full}", app->applicationVersion()); 193 | } 194 | if (kernelJson.indexOf("{slicer_version_major}") != -1) 195 | { 196 | kernelJson.replace("{slicer_version_major}", QString::number(app->majorVersion())); 197 | } 198 | if (kernelJson.indexOf("{slicer_version_minor}") != -1) 199 | { 200 | kernelJson.replace("{slicer_version_minor}", QString::number(app->minorVersion())); 201 | } 202 | if (kernelJson.indexOf("{slicer_launcher_executable}") != -1) 203 | { 204 | QString realExecutable = app->launcherExecutableFilePath(); 205 | if (realExecutable.isEmpty()) 206 | { 207 | realExecutable = app->applicationFilePath(); 208 | } 209 | 210 | kernelJson.replace("{slicer_launcher_executable}", realExecutable); 211 | } 212 | 213 | // Compare to existing kernel 214 | 215 | QString kernelJsonPath = kernelFolder + "/kernel.json"; 216 | QFile kernelFile(kernelJsonPath); 217 | if (!kernelFile.open(QIODevice::ReadWrite | QIODevice::Text)) 218 | { 219 | qWarning() << Q_FUNC_INFO << " failed: cannot write file " << kernelJsonPath; 220 | return false; 221 | } 222 | QTextStream existingKernelContentStream(&templateFile); 223 | QString existingKernelJson = existingKernelContentStream.readAll(); 224 | if (existingKernelJson != kernelJson) 225 | { 226 | // Kernel modified 227 | kernelFile.seek(0); 228 | kernelFile.write(kernelJson.toUtf8()); 229 | kernelFile.resize(kernelFile.pos()); // remove any potential extra content 230 | } 231 | 232 | kernelFile.close(); 233 | return true; 234 | } 235 | 236 | //----------------------------------------------------------------------------- 237 | qSlicerAbstractModuleRepresentation* qSlicerJupyterKernelModule 238 | ::createWidgetRepresentation() 239 | { 240 | return new qSlicerJupyterKernelModuleWidget; 241 | } 242 | 243 | //----------------------------------------------------------------------------- 244 | vtkMRMLAbstractLogic* qSlicerJupyterKernelModule::createLogic() 245 | { 246 | return vtkSlicerJupyterKernelLogic::New(); 247 | } 248 | 249 | //----------------------------------------------------------------------------- 250 | void qSlicerJupyterKernelModule::startKernel(const QString& connectionFile) 251 | { 252 | Q_D(qSlicerJupyterKernelModule); 253 | d->ConnectionFile = connectionFile; 254 | if (!QFileInfo::exists(connectionFile)) 255 | { 256 | qWarning() << "startKernel" << "connectionFile does not exist" << connectionFile; 257 | return; 258 | } 259 | if (d->Started) 260 | { 261 | qWarning() << "Kernel already started"; 262 | } 263 | else 264 | { 265 | d->Config = xeus::load_configuration(connectionFile.toStdString()); 266 | 267 | using interpreter_ptr = std::unique_ptr; 268 | interpreter_ptr interpreter = interpreter_ptr(new xSlicerInterpreter()); 269 | interpreter->set_jupyter_kernel_module(this); 270 | 271 | using history_manager_ptr = std::unique_ptr; 272 | history_manager_ptr hist = xeus::make_in_memory_history_manager(); 273 | 274 | auto context = xeus::make_context(); 275 | 276 | nl::json debugger_config; 277 | debugger_config["python"] = QStandardPaths::findExecutable("PythonSlicer").toStdString(); 278 | 279 | d->Kernel = new xeus::xkernel(d->Config, 280 | "slicer", 281 | std::move(context), 282 | std::move(interpreter), 283 | make_xSlicerServer, 284 | std::move(hist), 285 | nullptr // console logger 286 | 287 | // debugger is disabled for now 288 | // (see https://github.com/Slicer/SlicerJupyter/issues/69) 289 | // , xpyt::make_python_debugger, debugger_config 290 | 291 | ); 292 | 293 | d->Kernel->start(); 294 | 295 | d->Started = true; 296 | 297 | QString kernelConfigurePy = this->resourceFolderPath() + "/kernel-configure.py"; 298 | QFile kernelConfigurePyFile(kernelConfigurePy); 299 | if (kernelConfigurePyFile.open(QFile::ReadOnly | QFile::Text)) 300 | { 301 | QTextStream in(&kernelConfigurePyFile); 302 | QString kernelConfigurePyContent = in.readAll(); 303 | qSlicerPythonManager* pythonManager = qSlicerApplication::application()->pythonManager(); 304 | pythonManager->executeString(kernelConfigurePyContent); 305 | } 306 | else 307 | { 308 | qWarning() << Q_FUNC_INFO << " failed: cannot open kernel configure " << kernelConfigurePy; 309 | } 310 | 311 | QStatusBar* statusBar = NULL; 312 | if (qSlicerApplication::application()->mainWindow()) 313 | { 314 | statusBar = qSlicerApplication::application()->mainWindow()->statusBar(); 315 | } 316 | if (statusBar) 317 | { 318 | if (!d->StatusLabel) 319 | { 320 | d->StatusLabel = new QLabel; 321 | statusBar->insertPermanentWidget(0, d->StatusLabel); 322 | } 323 | d->StatusLabel->setText(tr("Application is managed by Jupyter")); 324 | } 325 | emit kernelStarted(); 326 | } 327 | } 328 | 329 | //----------------------------------------------------------------------------- 330 | void qSlicerJupyterKernelModule::stopKernel() 331 | { 332 | Q_D(qSlicerJupyterKernelModule); 333 | // Kernel shutdown requested 334 | emit kernelStopRequested(); 335 | if (d->StatusLabel) 336 | { 337 | d->StatusLabel->setText(""); 338 | } 339 | qSlicerApplication::application()->exit(0); 340 | } 341 | 342 | 343 | //----------------------------------------------------------------------------- 344 | bool qSlicerJupyterKernelModule::slicerKernelSpecInstallCommandArgs(QString& executable, QStringList &args) 345 | { 346 | Q_D(qSlicerJupyterKernelModule); 347 | 348 | vtkSlicerJupyterKernelLogic* kernelLogic = vtkSlicerJupyterKernelLogic::SafeDownCast(this->logic()); 349 | if (!kernelLogic) 350 | { 351 | qWarning() << Q_FUNC_INFO << " failed: invalid logic"; 352 | return false; 353 | } 354 | 355 | // There can be multiple Slicer installations of the same version that all refer to the same extensions folder. 356 | // Therefore we need to generate the kernel.json file from kernel-template.json file with information 357 | // specific to the current Slicer instance. 358 | if (!this->updateKernelSpec()) 359 | { 360 | qWarning() << Q_FUNC_INFO << " failed: error creating/updating kernel.json file"; 361 | return false; 362 | } 363 | 364 | executable = "jupyter-kernelspec"; 365 | args = QStringList() << "install" 366 | << (QString("\"") + this->resourceFolderPath() + QString("\"")) 367 | << "--replace" << "--user"; 368 | 369 | return true; 370 | } 371 | 372 | //----------------------------------------------------------------------------- 373 | bool qSlicerJupyterKernelModule::installInternalJupyterServer() 374 | { 375 | Q_D(qSlicerJupyterKernelModule); 376 | 377 | PythonQt::init(); 378 | PythonQtObjectPtr context = PythonQt::self()->getMainModule(); 379 | context.evalScript(QString("success=False; import JupyterNotebooks; server=JupyterNotebooks.SlicerJupyterServerHelper(); success=server.installRequiredPackages()")); 380 | bool success = context.getVariable("success").toBool(); 381 | return success; 382 | } 383 | 384 | //----------------------------------------------------------------------------- 385 | bool qSlicerJupyterKernelModule::startInternalJupyterServer(QString notebookDirectory, bool detached/*=false*/, bool classic/*=false*/) 386 | { 387 | Q_D(qSlicerJupyterKernelModule); 388 | QString pythonExecutable = QStandardPaths::findExecutable("PythonSlicer"); 389 | d->InternalJupyterServer.setProgram(pythonExecutable); 390 | QStringList args; 391 | if (classic) 392 | { 393 | args << "-m" << "notebook"; 394 | } 395 | else 396 | { 397 | args << "-m" << "jupyter" << "lab"; 398 | } 399 | args << "--notebook-dir" << notebookDirectory; 400 | d->InternalJupyterServer.setArguments(args); 401 | bool success = false; 402 | if (detached) 403 | { 404 | success = d->InternalJupyterServer.startDetached(); 405 | } 406 | else 407 | { 408 | d->InternalJupyterServer.start(); 409 | success = d->InternalJupyterServer.waitForStarted(); 410 | } 411 | return success; 412 | } 413 | 414 | //----------------------------------------------------------------------------- 415 | bool qSlicerJupyterKernelModule::isInternalJupyterServerRunning() const 416 | { 417 | Q_D(const qSlicerJupyterKernelModule); 418 | return d->InternalJupyterServer.state() == QProcess::Running; 419 | } 420 | 421 | //----------------------------------------------------------------------------- 422 | bool qSlicerJupyterKernelModule::stopInternalJupyterServer() 423 | { 424 | Q_D(qSlicerJupyterKernelModule); 425 | // TOOD: currently, none of these methods work for stopping a server that is 426 | // started using AppLauncher. 427 | // terminate() is not strong enough. 428 | // kill() immediately kills the launcher but not the launched application. 429 | d->InternalJupyterServer.terminate(); 430 | //d->InternalJupyterServer.kill(); 431 | return d->InternalJupyterServer.waitForFinished(5000); 432 | } 433 | 434 | //--------------------------------------------------------------------------- 435 | QString qSlicerJupyterKernelModule::kernelSpecPath() 436 | { 437 | vtkSlicerJupyterKernelLogic* kernelLogic = vtkSlicerJupyterKernelLogic::SafeDownCast(this->logic()); 438 | if (!kernelLogic) 439 | { 440 | qWarning() << Q_FUNC_INFO << " failed: invalid logic"; 441 | return ""; 442 | } 443 | qSlicerApplication* app = qSlicerApplication::application(); 444 | QString path = QString("%1/%2-%3.%4").arg(kernelLogic->GetModuleShareDirectory().c_str()) 445 | .arg(app->applicationName()).arg(app->majorVersion()).arg(app->minorVersion()); 446 | return path; 447 | } 448 | 449 | QString qSlicerJupyterKernelModule::resourceFolderPath() 450 | { 451 | return this->kernelSpecPath(); 452 | } 453 | 454 | //--------------------------------------------------------------------------- 455 | double qSlicerJupyterKernelModule::pollIntervalSec() 456 | { 457 | Q_D(qSlicerJupyterKernelModule); 458 | if (d->Kernel == nullptr) 459 | { 460 | qCritical() << Q_FUNC_INFO << " failed: kernel has not started yet"; 461 | return 0.0; 462 | } 463 | return reinterpret_cast(&d->Kernel->get_server())->pollIntervalSec(); 464 | 465 | return 0.0; 466 | } 467 | 468 | //--------------------------------------------------------------------------- 469 | void qSlicerJupyterKernelModule::setPollIntervalSec(double intervalSec) 470 | { 471 | Q_D(qSlicerJupyterKernelModule); 472 | if (d->Kernel == nullptr) 473 | { 474 | qCritical() << Q_FUNC_INFO << " failed: kernel has not started yet"; 475 | return; 476 | } 477 | 478 | reinterpret_cast(&d->Kernel->get_server())->setPollIntervalSec(intervalSec); 479 | } 480 | 481 | //--------------------------------------------------------------------------- 482 | QString qSlicerJupyterKernelModule::connectionFile() 483 | { 484 | Q_D(qSlicerJupyterKernelModule); 485 | return d->ConnectionFile; 486 | } 487 | -------------------------------------------------------------------------------- /JupyterKernel/qSlicerJupyterKernelModule.h: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | #ifndef __qSlicerJupyterKernelModule_h 19 | #define __qSlicerJupyterKernelModule_h 20 | 21 | // SlicerQt includes 22 | #include "qSlicerLoadableModule.h" 23 | 24 | #include "qSlicerJupyterKernelModuleExport.h" 25 | 26 | class qSlicerJupyterKernelModulePrivate; 27 | 28 | /// \ingroup Slicer_QtModules_ExtensionTemplate 29 | class Q_SLICER_QTMODULES_JUPYTERKERNEL_EXPORT 30 | qSlicerJupyterKernelModule 31 | : public qSlicerLoadableModule 32 | { 33 | Q_OBJECT 34 | #ifdef Slicer_HAVE_QT5 35 | Q_PLUGIN_METADATA(IID "org.slicer.modules.loadable.qSlicerLoadableModule/1.0"); 36 | #endif 37 | Q_INTERFACES(qSlicerLoadableModule); 38 | Q_PROPERTY(double pollIntervalSec READ pollIntervalSec WRITE setPollIntervalSec) 39 | Q_PROPERTY(QString connectionFile READ connectionFile) 40 | Q_PROPERTY(bool internalJupyterServerRunning READ isInternalJupyterServerRunning) 41 | public: 42 | 43 | typedef qSlicerLoadableModule Superclass; 44 | explicit qSlicerJupyterKernelModule(QObject *parent=0); 45 | virtual ~qSlicerJupyterKernelModule(); 46 | 47 | qSlicerGetTitleMacro(QTMODULE_TITLE); 48 | 49 | QString helpText()const override; 50 | QString acknowledgementText()const override; 51 | QStringList contributors()const override; 52 | 53 | QIcon icon()const override; 54 | 55 | QStringList categories()const override; 56 | QStringList dependencies()const override; 57 | 58 | Q_INVOKABLE virtual bool updateKernelSpec(); 59 | 60 | /// Get path where KernelSpec is created. 61 | Q_INVOKABLE virtual QString kernelSpecPath(); 62 | 63 | Q_INVOKABLE virtual bool slicerKernelSpecInstallCommandArgs(QString& executable, QStringList& args); 64 | 65 | /// Install Jupyter server in Slicer's Python environment 66 | Q_INVOKABLE virtual bool installInternalJupyterServer(); 67 | 68 | /// Start Jupyter server in Slicer's Python environment. 69 | /// Set detached=false to run the server as a child process and shutdown along with the application. 70 | /// Set classic=true to launch a classic notebook server instead of JupyterLab. 71 | Q_INVOKABLE virtual bool startInternalJupyterServer(QString notebookDirectory, bool detached=true, bool classic=false); 72 | 73 | /// Stop Jupyter server in Slicer's Python environment. 74 | /// Currently it does not work (it only terminates the launcher and not the actual application). 75 | /// In the future, this method will be fixed or removed. 76 | Q_INVOKABLE virtual bool stopInternalJupyterServer(); 77 | 78 | /// Returns true if internal Jupyter server is successfully started and still running. 79 | /// Only applicable to server that is started with detached=false. 80 | bool isInternalJupyterServerRunning() const; 81 | 82 | /// Deprecated. Use kernelSpecPath() instead. 83 | Q_INVOKABLE virtual QString resourceFolderPath(); 84 | 85 | double pollIntervalSec(); 86 | 87 | QString connectionFile(); 88 | 89 | public slots: 90 | 91 | void startKernel(const QString& connectionFile); 92 | void stopKernel(); 93 | void setPollIntervalSec(double intervalSec); 94 | 95 | signals: 96 | // Called after kernel has successfully started 97 | void kernelStarted(); 98 | 99 | // Called when Jupyter requested stopping of the kernel. 100 | void kernelStopRequested(); 101 | 102 | protected: 103 | 104 | /// Initialize the module. Register the volumes reader/writer 105 | void setup() override; 106 | 107 | /// Create and return the widget representation associated to this module 108 | qSlicerAbstractModuleRepresentation * createWidgetRepresentation() override; 109 | 110 | /// Create and return the logic associated to this module 111 | vtkMRMLAbstractLogic* createLogic() override; 112 | 113 | protected: 114 | QScopedPointer d_ptr; 115 | 116 | private: 117 | Q_DECLARE_PRIVATE(qSlicerJupyterKernelModule); 118 | Q_DISABLE_COPY(qSlicerJupyterKernelModule); 119 | 120 | }; 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /JupyterKernel/qSlicerJupyterKernelModuleWidget.cxx: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | // Qt includes 19 | #include 20 | #include 21 | 22 | // SlicerQt includes 23 | #include "qSlicerJupyterKernelModuleWidget.h" 24 | #include "ui_qSlicerJupyterKernelModuleWidget.h" 25 | 26 | #include "vtkSlicerJupyterKernelLogic.h" 27 | #include "qSlicerJupyterKernelModule.h" 28 | 29 | //----------------------------------------------------------------------------- 30 | /// \ingroup Slicer_QtModules_ExtensionTemplate 31 | class qSlicerJupyterKernelModuleWidgetPrivate: public Ui_qSlicerJupyterKernelModuleWidget 32 | { 33 | public: 34 | qSlicerJupyterKernelModuleWidgetPrivate(); 35 | }; 36 | 37 | //----------------------------------------------------------------------------- 38 | // qSlicerJupyterKernelModuleWidgetPrivate methods 39 | 40 | //----------------------------------------------------------------------------- 41 | qSlicerJupyterKernelModuleWidgetPrivate::qSlicerJupyterKernelModuleWidgetPrivate() 42 | { 43 | } 44 | 45 | //----------------------------------------------------------------------------- 46 | // qSlicerJupyterKernelModuleWidget methods 47 | 48 | //----------------------------------------------------------------------------- 49 | qSlicerJupyterKernelModuleWidget::qSlicerJupyterKernelModuleWidget(QWidget* _parent) 50 | : Superclass( _parent ) 51 | , d_ptr( new qSlicerJupyterKernelModuleWidgetPrivate ) 52 | { 53 | } 54 | 55 | //----------------------------------------------------------------------------- 56 | qSlicerJupyterKernelModuleWidget::~qSlicerJupyterKernelModuleWidget() 57 | { 58 | } 59 | 60 | //----------------------------------------------------------------------------- 61 | void qSlicerJupyterKernelModuleWidget::setup() 62 | { 63 | Q_D(qSlicerJupyterKernelModuleWidget); 64 | d->setupUi(this); 65 | this->Superclass::setup(); 66 | 67 | d->NotebookPathLineEdit->setFilters(ctkPathLineEdit::Dirs); 68 | d->NotebookPathLineEdit->setSettingKey("JupyterKernelNotebookDir"); 69 | 70 | qSlicerJupyterKernelModule* kernelModule = dynamic_cast(this->module()); 71 | if (kernelModule) 72 | { 73 | QString executable; 74 | QStringList args; 75 | if (kernelModule->slicerKernelSpecInstallCommandArgs(executable, args)) 76 | { 77 | QString manualInstallCommand = executable + " " + args.join(" "); 78 | d->ManualInstallCommandTextEdit->setText(manualInstallCommand); 79 | } 80 | } 81 | 82 | // Stopping of the server does not work and it is not really needed either. 83 | // We hide it for now, later the features will be either fixed or completely removed. 84 | d->StopJupyterNotebookPushButton->hide(); 85 | 86 | connect(d->StartJupyterNotebookPushButton, SIGNAL(clicked()), this, SLOT(startJupyterServer())); 87 | connect(d->StopJupyterNotebookPushButton, SIGNAL(clicked()), this, SLOT(stopJupyterServer())); 88 | 89 | connect(d->CopyCommandToClipboardPushButton, SIGNAL(clicked()), this, SLOT(copyInstallCommandToClipboard())); 90 | } 91 | 92 | //----------------------------------------------------------------------------- 93 | void qSlicerJupyterKernelModuleWidget::copyInstallCommandToClipboard() 94 | { 95 | Q_D(qSlicerJupyterKernelModuleWidget); 96 | QApplication::clipboard()->setText(d->ManualInstallCommandTextEdit->toPlainText()); 97 | } 98 | 99 | //----------------------------------------------------------------------------- 100 | bool qSlicerJupyterKernelModuleWidget::installJupyterServer() 101 | { 102 | Q_D(qSlicerJupyterKernelModuleWidget); 103 | qSlicerJupyterKernelModule* kernelModule = dynamic_cast(this->module()); 104 | if (!kernelModule) 105 | { 106 | qWarning() << Q_FUNC_INFO << " failed: invalid module"; 107 | return false; 108 | } 109 | 110 | d->JupyterServerStatusLabel->setText(tr("Jupyter installation is in progress... it may take 10-15 minutes.")); 111 | QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); 112 | bool success = kernelModule->installInternalJupyterServer(); 113 | QApplication::restoreOverrideCursor(); 114 | if (success) 115 | { 116 | d->JupyterServerStatusLabel->setText(tr("Jupyter installation completed successfully.")); 117 | } 118 | else 119 | { 120 | d->JupyterServerStatusLabel->setText(tr("Jupyter installation failed. See application log for details.")); 121 | } 122 | return success; 123 | } 124 | 125 | //----------------------------------------------------------------------------- 126 | void qSlicerJupyterKernelModuleWidget::startJupyterServer() 127 | { 128 | Q_D(qSlicerJupyterKernelModuleWidget); 129 | d->NotebookPathLineEdit->addCurrentPathToHistory(); 130 | qSlicerJupyterKernelModule* kernelModule = dynamic_cast(this->module()); 131 | if (!kernelModule) 132 | { 133 | d->JupyterServerStatusLabel->setText(tr("Jupyter server start failed.")); 134 | qWarning() << Q_FUNC_INFO << " failed: invalid module"; 135 | return; 136 | } 137 | if (kernelModule->isInternalJupyterServerRunning()) 138 | { 139 | d->JupyterServerStatusLabel->setText(tr("Jupyter server is already running.")); 140 | return; 141 | } 142 | bool installationSuccess = this->installJupyterServer(); 143 | if (installationSuccess) 144 | { 145 | d->JupyterServerStatusLabel->setText(tr("Starting Jupyter server...")); 146 | } 147 | else 148 | { 149 | d->JupyterServerStatusLabel->setText(tr("Error occurred during installation, attempting to start Jupyter server anyway...")); 150 | } 151 | // Start in detached mode so that Slicer can be shut down without impacting the server. 152 | bool detached = true; 153 | if (kernelModule->startInternalJupyterServer(d->NotebookPathLineEdit->currentPath(), detached)) 154 | { 155 | if (detached) 156 | { 157 | d->JupyterServerStatusLabel->setText(tr("Jupyter server started successfully.\nThis application can be stopped now. Jupyter server will keep running.")); 158 | } 159 | else 160 | { 161 | d->JupyterServerStatusLabel->setText(tr("Jupyter server started successfully.")); 162 | } 163 | } 164 | else 165 | { 166 | d->JupyterServerStatusLabel->setText(tr("Jupyter server start failed. See application log for details.")); 167 | } 168 | } 169 | 170 | //----------------------------------------------------------------------------- 171 | void qSlicerJupyterKernelModuleWidget::stopJupyterServer() 172 | { 173 | Q_D(qSlicerJupyterKernelModuleWidget); 174 | qSlicerJupyterKernelModule* kernelModule = dynamic_cast(this->module()); 175 | if (!kernelModule) 176 | { 177 | qWarning() << Q_FUNC_INFO << " failed: invalid module"; 178 | d->JupyterServerStatusLabel->setText(tr("Jupyter server stop failed.")); 179 | return; 180 | } 181 | if (!kernelModule->isInternalJupyterServerRunning()) 182 | { 183 | d->JupyterServerStatusLabel->setText(tr("Jupyter server is already stopped.")); 184 | return; 185 | } 186 | kernelModule->stopInternalJupyterServer(); 187 | if (kernelModule->isInternalJupyterServerRunning()) 188 | { 189 | d->JupyterServerStatusLabel->setText(tr("Jupyter server stop failed. See application log for details.")); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /JupyterKernel/qSlicerJupyterKernelModuleWidget.h: -------------------------------------------------------------------------------- 1 | /*============================================================================== 2 | 3 | Program: 3D Slicer 4 | 5 | Portions (c) Copyright Brigham and Women's Hospital (BWH) All Rights Reserved. 6 | 7 | See COPYRIGHT.txt 8 | or http://www.slicer.org/copyright/copyright.txt for details. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ==============================================================================*/ 17 | 18 | #ifndef __qSlicerJupyterKernelModuleWidget_h 19 | #define __qSlicerJupyterKernelModuleWidget_h 20 | 21 | // SlicerQt includes 22 | #include "qSlicerAbstractModuleWidget.h" 23 | 24 | #include "qSlicerJupyterKernelModuleExport.h" 25 | 26 | class qSlicerJupyterKernelModuleWidgetPrivate; 27 | class vtkMRMLNode; 28 | 29 | /// \ingroup Slicer_QtModules_ExtensionTemplate 30 | class Q_SLICER_QTMODULES_JUPYTERKERNEL_EXPORT qSlicerJupyterKernelModuleWidget : 31 | public qSlicerAbstractModuleWidget 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | 37 | typedef qSlicerAbstractModuleWidget Superclass; 38 | qSlicerJupyterKernelModuleWidget(QWidget *parent=0); 39 | virtual ~qSlicerJupyterKernelModuleWidget(); 40 | 41 | public slots: 42 | 43 | void copyInstallCommandToClipboard(); 44 | 45 | bool installJupyterServer(); 46 | void startJupyterServer(); 47 | void stopJupyterServer(); 48 | 49 | protected: 50 | QScopedPointer d_ptr; 51 | 52 | virtual void setup(); 53 | 54 | private: 55 | Q_DECLARE_PRIVATE(qSlicerJupyterKernelModuleWidget); 56 | Q_DISABLE_COPY(qSlicerJupyterKernelModuleWidget); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /JupyterKernel/xSlicerInterpreter.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "xSlicerInterpreter.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "qSlicerJupyterKernelModule.h" 11 | 12 | #include 13 | 14 | #include 15 | 16 | xSlicerInterpreter::xSlicerInterpreter() 17 | : xpyt::interpreter(false, false) 18 | // Disable built-in output and display redirection, as it would prevent 19 | // console output and execution results from appearing in Slicer's 20 | // Python console and application log. 21 | // We call publish_stream instead when PythonQt emits outputs. 22 | { 23 | // GIL is already released, so we need to prevent 24 | // the interpreter from attempting to release it again. 25 | m_release_gil_at_startup = false; 26 | } 27 | 28 | 29 | void xSlicerInterpreter::configure_impl() 30 | { 31 | xpyt::interpreter::configure_impl(); 32 | 33 | auto handle_comm_opened = [](xeus::xcomm&& comm, const xeus::xmessage&) { 34 | std::cout << "Comm opened for target: " << comm.target().name() << std::endl; 35 | }; 36 | comm_manager().register_comm_target("echo_target", handle_comm_opened); 37 | 38 | // Custom output redirection 39 | QObject::connect(PythonQt::self(), &PythonQt::pythonStdOut, 40 | [=](const QString& text) { 41 | publish_stream("stdout", text.toStdString()); 42 | }); 43 | QObject::connect(PythonQt::self(), &PythonQt::pythonStdErr, 44 | [=](const QString& text) { 45 | publish_stream("stderr", text.toStdString()); 46 | }); 47 | 48 | // Custom display redirection 49 | // Make xeus-python display hook available as slicer.xeusPythonDisplayHook 50 | py::module slicer_module = py::module::import("slicer"); 51 | slicer_module.attr("xeusPythonDisplayHook") = m_displayhook; 52 | } 53 | 54 | nl::json xSlicerInterpreter::execute_request_impl(int execution_counter, 55 | const std::string& code, 56 | bool store_history, 57 | bool silent, 58 | nl::json user_expressions, 59 | bool allow_stdin) 60 | { 61 | if (m_print_debug_output) 62 | { 63 | std::cout << "Received execute_request" << std::endl; 64 | std::cout << "execution_counter: " << execution_counter << std::endl; 65 | std::cout << "code: " << code << std::endl; 66 | std::cout << "store_history: " << store_history << std::endl; 67 | std::cout << "silent: " << silent << std::endl; 68 | std::cout << "allow_stdin: " << allow_stdin << std::endl; 69 | std::cout << std::endl; 70 | } 71 | 72 | qSlicerPythonManager* pythonManager = qSlicerApplication::application()->pythonManager(); 73 | 74 | nl::json pub_data; 75 | nl::json result; 76 | QString qscode = QString::fromUtf8(code.c_str()); 77 | if (qscode.endsWith(QString("__kernel_debug_enable()"))) 78 | { 79 | m_print_debug_output = true; 80 | pub_data["text/plain"] = "Kernel debug info print enabled."; 81 | result["status"] = "ok"; 82 | } 83 | else if (qscode.endsWith(QString("__kernel_debug_disable()"))) 84 | { 85 | m_print_debug_output = false; 86 | pub_data["text/plain"] = "Kernel debug info print disabled."; 87 | result["status"] = "ok"; 88 | } 89 | else 90 | { 91 | return xpyt::interpreter::execute_request_impl(execution_counter, code, store_history, silent, user_expressions, allow_stdin); 92 | } 93 | 94 | publish_execution_result(execution_counter, std::move(pub_data), nl::json::object()); 95 | return result; 96 | } 97 | 98 | nl::json xSlicerInterpreter::complete_request_impl(const std::string& code, 99 | int cursor_pos) 100 | { 101 | if (m_print_debug_output) 102 | { 103 | std::cout << "Received complete_request" << std::endl; 104 | std::cout << "code: " << code << std::endl; 105 | std::cout << "cursor_pos: " << cursor_pos << std::endl; 106 | std::cout << std::endl; 107 | } 108 | 109 | return xpyt::interpreter::complete_request_impl(code, cursor_pos); 110 | } 111 | 112 | nl::json xSlicerInterpreter::inspect_request_impl(const std::string& code, 113 | int cursor_pos, 114 | int detail_level) 115 | { 116 | if (m_print_debug_output) 117 | { 118 | std::cout << "Received inspect_request" << std::endl; 119 | std::cout << "code: " << code << std::endl; 120 | std::cout << "cursor_pos: " << cursor_pos << std::endl; 121 | std::cout << "detail_level: " << detail_level << std::endl; 122 | std::cout << std::endl; 123 | } 124 | 125 | return xpyt::interpreter::inspect_request_impl(code, cursor_pos, detail_level); 126 | } 127 | 128 | nl::json xSlicerInterpreter::is_complete_request_impl(const std::string& code) 129 | { 130 | if (m_print_debug_output) 131 | { 132 | std::cout << "Received is_complete_request" << std::endl; 133 | std::cout << "code: " << code << std::endl; 134 | std::cout << std::endl; 135 | } 136 | return xpyt::interpreter::is_complete_request_impl(code); 137 | 138 | } 139 | 140 | nl::json xSlicerInterpreter::kernel_info_request_impl() 141 | { 142 | return xpyt::interpreter::kernel_info_request_impl(); 143 | } 144 | 145 | void xSlicerInterpreter::shutdown_request_impl() 146 | { 147 | if (m_print_debug_output) 148 | { 149 | std::cout << "Received shutdown_request" << std::endl; 150 | std::cout << std::endl; 151 | } 152 | return xpyt::interpreter::shutdown_request_impl(); 153 | } 154 | 155 | void xSlicerInterpreter::set_jupyter_kernel_module(qSlicerJupyterKernelModule* module) 156 | { 157 | m_jupyter_kernel_module = module; 158 | } 159 | -------------------------------------------------------------------------------- /JupyterKernel/xSlicerInterpreter.h: -------------------------------------------------------------------------------- 1 | #ifndef xSlicerInterpreter_h 2 | #define xSlicerInterpreter_h 3 | 4 | //#include 5 | 6 | #include 7 | 8 | #include 9 | 10 | //using xpyt::interpreter; 11 | 12 | class qSlicerJupyterKernelModule; 13 | 14 | class xSlicerInterpreter : public xpyt::interpreter 15 | { 16 | 17 | public: 18 | 19 | xSlicerInterpreter(); 20 | virtual ~xSlicerInterpreter() = default; 21 | 22 | void set_jupyter_kernel_module(qSlicerJupyterKernelModule* module); 23 | 24 | private: 25 | 26 | void configure_impl() override; 27 | 28 | nl::json execute_request_impl(int execution_counter, 29 | const std::string& code, 30 | bool store_history, 31 | bool silent, 32 | nl::json user_expressions, 33 | bool allow_stdin) override; 34 | 35 | nl::json complete_request_impl(const std::string& code, 36 | int cursor_pos) override; 37 | 38 | nl::json inspect_request_impl(const std::string& code, 39 | int cursor_pos, 40 | int detail_level) override; 41 | 42 | nl::json is_complete_request_impl(const std::string& code) override; 43 | 44 | nl::json kernel_info_request_impl() override; 45 | 46 | void shutdown_request_impl() override; 47 | 48 | bool m_print_debug_output = false; 49 | qSlicerJupyterKernelModule* m_jupyter_kernel_module = nullptr; 50 | }; 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /JupyterKernel/xSlicerServer.cxx: -------------------------------------------------------------------------------- 1 | #include "xSlicerServer.h" 2 | 3 | // Slicer includes 4 | #include 5 | #include 6 | #include "qSlicerJupyterKernelModule.h" 7 | 8 | // STL includes 9 | #include 10 | #include 11 | 12 | // zmq includes 13 | #include 14 | 15 | // Qt includes 16 | #include 17 | #include 18 | 19 | xSlicerServer::xSlicerServer(zmq::context_t& context, 20 | const xeus::xconfiguration& c, 21 | nl::json::error_handler_t eh) 22 | : xserver_zmq(context, c, eh) 23 | { 24 | // 10ms interval is short enough so that users will not notice significant latency 25 | // yet it is long enough to minimize CPU load caused by polling. 26 | // 50ms causes too long delay in interactive widgets that handle mousemove events. 27 | m_pollTimer = new QTimer(); 28 | m_pollTimer->setInterval(10); 29 | QObject::connect(m_pollTimer, &QTimer::timeout, [=]() { poll(0); }); 30 | } 31 | 32 | xSlicerServer::~xSlicerServer() 33 | { 34 | m_pollTimer->stop(); 35 | delete m_pollTimer; 36 | } 37 | 38 | void xSlicerServer::start_impl(xeus::xpub_message message) 39 | { 40 | qDebug() << "Starting Jupyter kernel server"; 41 | 42 | start_publisher_thread(); 43 | start_heartbeat_thread(); 44 | 45 | m_request_stop = false; 46 | 47 | m_pollTimer->start(); 48 | 49 | publish(std::move(message), xeus::channel::SHELL); 50 | } 51 | 52 | void xSlicerServer::stop_impl() 53 | { 54 | qDebug() << "Stopping Jupyter kernel server"; 55 | this->xserver_zmq::stop_impl(); 56 | m_pollTimer->stop(); 57 | stop_channels(); 58 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 59 | 60 | // Notify JupyterKernel module about kernel stop. 61 | qSlicerJupyterKernelModule* kernelModule = qobject_cast(qSlicerCoreApplication::application()->moduleManager()->module("JupyterKernel")); 62 | if (kernelModule) 63 | { 64 | kernelModule->stopKernel(); 65 | } 66 | } 67 | 68 | std::unique_ptr make_xSlicerServer(xeus::xcontext& context, 69 | const xeus::xconfiguration& config, 70 | nl::json::error_handler_t eh) 71 | { 72 | return std::make_unique(context.get_wrapped_context(), config, eh); 73 | } 74 | 75 | void xSlicerServer::setPollIntervalSec(double intervalSec) 76 | { 77 | m_pollTimer->setInterval(intervalSec*1000.0); 78 | } 79 | 80 | double xSlicerServer::pollIntervalSec() 81 | { 82 | return m_pollTimer->interval() / 1000.0; 83 | } 84 | -------------------------------------------------------------------------------- /JupyterKernel/xSlicerServer.h: -------------------------------------------------------------------------------- 1 | #ifndef XSLICER_SERVER_HPP 2 | #define XSLICER_SERVER_HPP 3 | 4 | // xeus includes 5 | #include 6 | #include 7 | 8 | #include "qSlicerJupyterKernelModuleExport.h" 9 | 10 | // Qt includes 11 | #include 12 | #include 13 | #include 14 | 15 | class QTimer; 16 | 17 | class xSlicerServer : public xeus::xserver_zmq 18 | { 19 | 20 | public: 21 | using socket_notifier_ptr = QSharedPointer; 22 | 23 | xSlicerServer(zmq::context_t& context, 24 | const xeus::xconfiguration& config, 25 | nl::json::error_handler_t eh); 26 | 27 | virtual ~xSlicerServer(); 28 | 29 | void setPollIntervalSec(double intervalSec); 30 | double pollIntervalSec(); 31 | 32 | protected: 33 | 34 | void start_impl(xeus::xpub_message message) override; 35 | void stop_impl() override; 36 | 37 | // Socket notifier for stdin socket continuously generates signals 38 | // on Windows and on some Linux distributions, which would cause 100% CPU 39 | // usage even when the application is idle. 40 | // It is not clear why stdin socket behaves like this, but using a timer 41 | // to check for inputs at regular intervals solves the issue. 42 | QTimer* m_pollTimer; 43 | }; 44 | 45 | Q_SLICER_QTMODULES_JUPYTERKERNEL_EXPORT 46 | std::unique_ptr make_xSlicerServer(xeus::xcontext& context, 47 | const xeus::xconfiguration& config, 48 | nl::json::error_handler_t eh); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /JupyterNotebooks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | set(MODULE_NAME JupyterNotebooks) 4 | 5 | #----------------------------------------------------------------------------- 6 | set(MODULE_PYTHON_SCRIPTS 7 | ${MODULE_NAME}.py 8 | ${MODULE_NAME}Lib/__init__ 9 | ${MODULE_NAME}Lib/interactive_view_widget 10 | ${MODULE_NAME}Lib/cli 11 | ${MODULE_NAME}Lib/files 12 | ${MODULE_NAME}Lib/display 13 | ${MODULE_NAME}Lib/widgets 14 | ) 15 | 16 | set(MODULE_PYTHON_RESOURCES 17 | ) 18 | 19 | #----------------------------------------------------------------------------- 20 | slicerMacroBuildScriptedModule( 21 | NAME ${MODULE_NAME} 22 | SCRIPTS ${MODULE_PYTHON_SCRIPTS} 23 | RESOURCES ${MODULE_PYTHON_RESOURCES} 24 | WITH_GENERIC_TESTS 25 | ) 26 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import unittest 5 | import vtk, qt, ctk, slicer 6 | from slicer.ScriptedLoadableModule import * 7 | from slicer.util import TESTING_DATA_URL 8 | 9 | # 10 | # JupyterNotebooks 11 | # 12 | 13 | class JupyterNotebooks(ScriptedLoadableModule): 14 | def __init__(self, parent): 15 | ScriptedLoadableModule.__init__(self, parent) 16 | import string 17 | parent.title = "JupyterNotebooks" 18 | parent.categories = ["Developer Tools"] 19 | parent.contributors = ["Andras Lasso (PerkLab)"] 20 | parent.hidden = True # don't show this module, there is no user interface yet 21 | parent.helpText = string.Template(""" 22 | This module adds utility functions to make Slicer features conveniently available in Jupyter notebooks. 23 | """) 24 | parent.acknowledgementText = """ 25 | The module is originally developed by Andras Lasso. 26 | """ 27 | 28 | class SlicerJupyterServerHelper: 29 | def installRequiredPackages(self, force=False): 30 | """Installed required Python packages for running a Jupyter server in Slicer's Python environment.""" 31 | # Need to install if forced or any packages cannot be imported 32 | needToInstall = force 33 | if not needToInstall: 34 | try: 35 | import jupyter 36 | import jupyterlab 37 | import ipywidgets 38 | import pandas 39 | import ipyevents 40 | import ipycanvas 41 | except: 42 | needToInstall = True 43 | 44 | if needToInstall: 45 | # Install required packages 46 | import os 47 | if os.name != 'nt': 48 | # PIL may be corrupted on linux, reinstall from pillow 49 | slicer.util.pip_install('--upgrade pillow --force-reinstall') 50 | 51 | slicer.util.pip_install("jupyter jupyterlab ipywidgets pandas ipyevents ipycanvas --no-warn-script-location") 52 | 53 | # Install Slicer Jupyter kernel 54 | # Create Slicer kernel 55 | slicer.modules.jupyterkernel.updateKernelSpec() 56 | # Install Slicer kernel 57 | import jupyter_client 58 | jupyter_client.kernelspec.KernelSpecManager().install_kernel_spec(slicer.modules.jupyterkernel.kernelSpecPath(), user=True, replace=True) 59 | 60 | class JupyterNotebooksTest(ScriptedLoadableModuleTest): 61 | """ 62 | This is the test case for your scripted module. 63 | Uses ScriptedLoadableModuleTest base class, available at: 64 | https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py 65 | """ 66 | 67 | def setUp(self): 68 | """ Do whatever is needed to reset the state - typically a scene clear will be enough. 69 | """ 70 | pass 71 | 72 | def runTest(self): 73 | """Run as few or as many tests as needed here. 74 | """ 75 | self.setUp() 76 | self.test_JupyterNotebooks1() 77 | 78 | def test_JupyterNotebooks1(self): 79 | """ Ideally you should have several levels of tests. At the lowest level 80 | tests should exercise the functionality of the logic with different inputs 81 | (both valid and invalid). At higher levels your tests should emulate the 82 | way the user would interact with your code and confirm that it still works 83 | the way you intended. 84 | One of the most important features of the tests is that it should alert other 85 | developers when their changes will have an impact on the behavior of your 86 | module. For example, if a developer removes a feature that you depend on, 87 | your test should break so they know that the feature is needed. 88 | """ 89 | 90 | self.delayDisplay("Starting the test") 91 | 92 | # TODO: implement test 93 | 94 | self.delayDisplay('Test passed!') 95 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: separate widgets (can be inserted into layouts) from functions (providing data) 2 | 3 | # Displayable objects from views 4 | from .display import ViewDisplay, ViewSliceDisplay, View3DDisplay, ViewLightboxDisplay 5 | 6 | # Rendering utility functions 7 | from .display import setViewLayout, showSliceViewAnnotations, showVolumeRendering, reset3DView 8 | 9 | # Convert MRML nodes and other common data types to objects that can be 10 | # nicely displayed in notebooks 11 | from .display import displayable, ModelDisplay, TransformDisplay, MatplotlibDisplay 12 | 13 | # cli 14 | from .cli import cliRunSync 15 | 16 | # util (file management, useful widgets) 17 | from .files import downloadFromURL, localPath, notebookPath, notebookSaveCheckpoint, notebookExportToHtml, installExtensions 18 | 19 | # widgets 20 | try: 21 | import ipywidgets 22 | except ImportError: 23 | print("ipywidgets is not installed in 3D Slicer's Python environment. These classes will not be available: ViewSliceWidget, ViewSliceBaseWidget, View3DWidget, FileUploadWidget, AppWindow, ViewInteractiveWidget") 24 | else: 25 | from .widgets import ViewSliceWidget, ViewSliceBaseWidget, View3DWidget, FileUploadWidget, AppWindow 26 | from .interactive_view_widget import ViewInteractiveWidget 27 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/cli.py: -------------------------------------------------------------------------------- 1 | import slicer 2 | 3 | def cliRunSync(module, node=None, parameters=None, delete_temporary_files=True, update_display=True): 4 | """Run CLI module. If ipywidgets are installed then it reports progress. 5 | :param module: CLI module object (derived from qSlicerCLIModule), for example `slicer.modules.thresholdscalarvolume`. 6 | List of all module objects (not just CLI modules): `dir(slicer.modules)`. 7 | :param node: if a parameter node (vtkMRMLCommandLineModuleNode) is specified here then that is used. 8 | If left at default then a new node will be created and that node will be returned. 9 | :param parameters: dicttionary containing list of input nodes and parameters and output nodes. 10 | :param delete_temproary_files: set it to True to preserve all input files. 11 | May be useful for debugging, but it consumes disk space. 12 | :param update_display: show output volumes in slice views (resets view position and zoom factor). 13 | :return: Used parameter node. If the CLI moddule does not need to be executed again then the 14 | node can be removed from the scene by calling `slicer.mrmlScene.RemoveNode(parameterNode)` 15 | to avoid clutter in the scene. 16 | """ 17 | 18 | try: 19 | from ipywidgets import IntProgress 20 | from IPython.display import display 21 | 22 | # Asynchronous run, with progerss reporting using widget 23 | node = slicer.cli.run(module, node=node, parameters=parameters, wait_for_completion=False, 24 | delete_temporary_files=delete_temporary_files, update_display=update_display) 25 | import time 26 | progress = IntProgress() 27 | display(progress) # display progress bar 28 | while node.IsBusy(): 29 | progress.value = node.GetProgress() 30 | slicer.app.processEvents() 31 | time.sleep(.3) 32 | progress.layout.display = 'none' # hide progress bar 33 | 34 | except ImportError: 35 | # No widgets, therefore no progress reporting - do just a simpe synchronous CLI run 36 | node = slicer.cli.runSync(module, node=node, parameters=parameters, wait_for_completion=False, 37 | delete_temporary_files=delete_temporary_files, update_display=update_display) 38 | 39 | return node 40 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/display.py: -------------------------------------------------------------------------------- 1 | import ctk, qt, slicer, vtk 2 | 3 | def displayable(obj): 4 | """Convert Slicer-specific objects to displayable objects. 5 | Currently, it supports vtkMRMLMarkupsNode, vtkMRMLTableNode, vtkMRMLModelNode, vtkMRMLTransformNode. 6 | """ 7 | try: 8 | 9 | # Workaround until xeus-python support matplotlib inline backend 10 | if str(type(obj)) == "": 11 | return MatplotlibDisplay(obj) 12 | 13 | if hasattr(obj, "IsA"): 14 | # MRML node 15 | if obj.IsA("vtkMRMLMarkupsNode"): 16 | return slicer.util.dataframeFromMarkups(obj) 17 | elif obj.IsA("vtkMRMLTableNode"): 18 | return slicer.util.dataframeFromTable(obj) 19 | elif obj.IsA("vtkMRMLModelNode"): 20 | return ModelDisplay(obj) 21 | elif obj.IsA("vtkMRMLTransformNode"): 22 | return TransformDisplay(obj) 23 | 24 | except: 25 | # Error occurred, most likely the input was not a known object type 26 | return obj 27 | 28 | # Unknown object 29 | return obj 30 | 31 | class ModelDisplay(object): 32 | """This class displays a model node in a Jupyter notebook cell by rendering it as an image. 33 | :param modelNode: model node to display. 34 | :param imageSize: list containing width and height of the generated image, in pixels (default is `[300, 300]`). 35 | :param zoom: allows zooming in on the rendered model (default: 1.0). 36 | :param orientation: roll, pitch, yaw rotation angles of the camera, in degrees. 37 | :param showFeatureEdges: outline sharp edges with lines to improve visibility. 38 | """ 39 | 40 | def __init__(self, modelNode, imageSize=None, orientation=None, zoom=None, showFeatureEdges=False): 41 | # rollPitchYawDeg 42 | orientation = [0,0,0] if orientation is None else orientation 43 | zoom = 1.0 if zoom is None else zoom 44 | imageSize = [300,300] if imageSize is None else imageSize 45 | showFeatureEdges = showFeatureEdges 46 | 47 | modelPolyData = modelNode.GetPolyData() 48 | 49 | renderer = vtk.vtkRenderer() 50 | renderer.SetBackground(1,1,1) 51 | renderer.SetUseDepthPeeling(1) 52 | renderer.SetMaximumNumberOfPeels(100) 53 | renderer.SetOcclusionRatio(0.1) 54 | renWin = vtk.vtkRenderWindow() 55 | renWin.OffScreenRenderingOn() 56 | renWin.SetSize(imageSize[0], imageSize[1]) 57 | renWin.SetAlphaBitPlanes(1) # for depth peeling 58 | renWin.SetMultiSamples(0) # for depth peeling 59 | renWin.AddRenderer(renderer) 60 | 61 | renderer.GetActiveCamera() # create active camera 62 | 63 | # Must be called after iren and renderer are linked and camera is created or there will be problems 64 | renderer.Render() 65 | 66 | modelNormals = vtk.vtkPolyDataNormals() 67 | modelNormals.SetInputData(modelPolyData) 68 | 69 | modelMapper = vtk.vtkPolyDataMapper() 70 | modelMapper.SetInputConnection(modelNormals.GetOutputPort()) 71 | 72 | modelActor = vtk.vtkActor() 73 | modelActor.SetMapper(modelMapper) 74 | modelActor.GetProperty().SetColor(0.9, 0.9, 0.9) 75 | modelActor.GetProperty().SetOpacity(0.8) 76 | renderer.AddActor(modelActor) 77 | 78 | triangleFilter = vtk.vtkTriangleFilter() 79 | triangleFilter.SetInputConnection(modelNormals.GetOutputPort()) 80 | 81 | if showFeatureEdges: 82 | 83 | edgeExtractor = vtk.vtkFeatureEdges() 84 | edgeExtractor.SetInputConnection(triangleFilter.GetOutputPort()) 85 | edgeExtractor.ColoringOff() 86 | edgeExtractor.BoundaryEdgesOn() 87 | edgeExtractor.ManifoldEdgesOn() 88 | edgeExtractor.NonManifoldEdgesOn() 89 | 90 | modelEdgesMapper = vtk.vtkPolyDataMapper() 91 | modelEdgesMapper.SetInputConnection(edgeExtractor.GetOutputPort()) 92 | modelEdgesMapper.SetResolveCoincidentTopologyToPolygonOffset() 93 | modelEdgesActor = vtk.vtkActor() 94 | modelEdgesActor.SetMapper(modelEdgesMapper) 95 | modelEdgesActor.GetProperty().SetColor(0.0, 0.0, 0.0) 96 | renderer.AddActor(modelEdgesActor) 97 | 98 | # Set projection to parallel to enable estimate distances 99 | renderer.GetActiveCamera().ParallelProjectionOn() 100 | renderer.GetActiveCamera().Roll(orientation[0]) 101 | renderer.GetActiveCamera().Pitch(orientation[1]) 102 | renderer.GetActiveCamera().Yaw(orientation[2]) 103 | renderer.ResetCamera() 104 | renderer.GetActiveCamera().Zoom(zoom) 105 | 106 | windowToImageFilter = vtk.vtkWindowToImageFilter() 107 | windowToImageFilter.SetInput(renWin) 108 | windowToImageFilter.Update() 109 | 110 | screenshot = ctk.ctkVTKWidgetsUtils.vtkImageDataToQImage(windowToImageFilter.GetOutput()) 111 | 112 | bArray = qt.QByteArray() 113 | buffer = qt.QBuffer(bArray) 114 | buffer.open(qt.QIODevice.WriteOnly) 115 | screenshot.save(buffer, "PNG") 116 | self.dataValue = bArray.toBase64().data().decode() 117 | self.dataType = "image/png" 118 | 119 | def _repr_mimebundle_(self, include=None, exclude=None): 120 | return { self.dataType: self.dataValue } 121 | 122 | class TransformDisplay(object): 123 | """This class displays information about a transform in a Jupyter notebook cell. 124 | """ 125 | def __init__(self, transform): 126 | if transform.IsLinear(): 127 | # Always print linear transforms as transform to parent matrix 128 | self.dataValue = "Transform to parent:
"+np.array2string(slicer.util.arrayFromTransformMatrix(transform))+"
" 129 | else: 130 | # Non-linear transform 131 | if transform.GetTransformToParentAs('vtkAbstractTransform', False, True): 132 | # toParent is set (fromParent is just computed) 133 | self.dataValue = 'Transform to parent:
' 134 | self.dataValue += transform.GetTransformToParentInfo().replace('\n','
') 135 | else: 136 | # fromParent is set (toParent is just computed) 137 | self.dataValue = 'Transform from parent:
' 138 | self.dataValue += transform.GetTransformFromParentInfo().replace('\n','
') 139 | def _repr_mimebundle_(self, include=None, exclude=None): 140 | return { "text/html": self.dataValue } 141 | 142 | 143 | class ViewDisplay(object): 144 | """This class captures current views and makes it available 145 | for display in the output of a Jupyter notebook cell. 146 | :param viewLayout: view layout name, most common ones are 147 | `FourUp`, `Conventional`, `OneUp3D`, `OneUpRedSlice`, `OneUpYellowSlice`, 148 | `OneUpPlot`, `OneUpGreenSlice`, `Dual3D`, `FourOverFour`, `DicomBrowser`. 149 | See :py:meth:`setViewLayout` for more details on viewLayout names. 150 | :param center: re-center slice and 3D views on current view content. 151 | """ 152 | def __init__(self, viewLayout=None, center=True): 153 | layoutManager = slicer.app.layoutManager() 154 | if viewLayout: 155 | setViewLayout(viewLayout) 156 | if center: 157 | slicer.util.resetSliceViews() 158 | for viewId in range(layoutManager.threeDViewCount): 159 | reset3DView(viewId) 160 | slicer.util.setViewControllersVisible(False) 161 | slicer.app.processEvents() 162 | slicer.util.forceRenderAllViews() 163 | screenshot = layoutManager.viewport().grab() 164 | slicer.util.setViewControllersVisible(True) 165 | bArray = qt.QByteArray() 166 | buffer = qt.QBuffer(bArray) 167 | buffer.open(qt.QIODevice.WriteOnly) 168 | screenshot.save(buffer, "PNG") 169 | self.dataValue = bArray.toBase64().data().decode() 170 | self.dataType = "image/png" 171 | def _repr_mimebundle_(self, include=None, exclude=None): 172 | return { self.dataType: self.dataValue } 173 | 174 | class ViewSliceDisplay(object): 175 | """This class captures a slice view and makes it available 176 | for display in the output of a Jupyter notebook cell. 177 | :param viewName: name of the slice view, such as `Red`, `Green`, `Yellow`. 178 | Get list of all current slice view names by calling `slicer.app.layoutManager().sliceViewNames()`. 179 | """ 180 | def __init__(self, viewName=None, positionPercent=None): 181 | if not viewName: 182 | viewName = "Red" 183 | layoutManager = slicer.app.layoutManager() 184 | slicer.app.processEvents() 185 | sliceWidget = layoutManager.sliceWidget(viewName) 186 | if positionPercent is not None: 187 | sliceBounds = [0,0,0,0,0,0] 188 | sliceWidget.sliceLogic().GetLowestVolumeSliceBounds(sliceBounds) 189 | positionMin = sliceBounds[4] 190 | positionMax = sliceBounds[5] 191 | position = positionMin + positionPercent / 100.0 * (positionMax - positionMin) 192 | sliceWidget.sliceController().sliceOffsetSlider().setValue(position) 193 | sliceView = sliceWidget.sliceView() 194 | sliceView.forceRender() 195 | screenshot = sliceView.grab() 196 | bArray = qt.QByteArray() 197 | buffer = qt.QBuffer(bArray) 198 | buffer.open(qt.QIODevice.WriteOnly) 199 | #screenshot.save(buffer, "PNG") 200 | screenshot.save(buffer, "JPG") 201 | self.dataValue = bArray.toBase64().data().decode() 202 | #self.dataType = "image/png" 203 | self.dataType = "image/jpeg" 204 | def _repr_mimebundle_(self, include=None, exclude=None): 205 | return { self.dataType: self.dataValue } 206 | 207 | class View3DDisplay(object): 208 | """This class captures a 3D view and makes it available 209 | for display in the output of a Jupyter notebook cell. 210 | :param viewID: integer index of the 3D view node. Valid values are between 0 and `slicer.app.layoutManager().threeDViewCount-1`. 211 | :param orientation: rotation angles of the camera around R, A, S axes, in degrees. 212 | """ 213 | def __init__(self, viewID=0, orientation=None): 214 | slicer.app.processEvents() 215 | widget = slicer.app.layoutManager().threeDWidget(viewID) 216 | view = widget.threeDView() 217 | if orientation is not None: 218 | camera = view.interactorStyle().GetCameraNode().GetCamera() 219 | cameraToWorld = vtk.vtkTransform() 220 | cameraToWorld.RotateX(90) 221 | cameraToWorld.RotateY(180) 222 | cameraToWorld.RotateY(orientation[2]) 223 | cameraToWorld.RotateX(orientation[1]) 224 | cameraToWorld.RotateZ(orientation[0]) 225 | cameraToWorld.Translate(0, 0, camera.GetDistance()) 226 | viewUp = [0,1,0,0] 227 | slicer.vtkAddonMathUtilities.GetOrientationMatrixColumn(cameraToWorld.GetMatrix(), 1, viewUp) 228 | position = cameraToWorld.GetPosition() 229 | focalPoint = camera.GetFocalPoint() 230 | camera.SetPosition(focalPoint[0]+position[0], focalPoint[1]+position[1], focalPoint[2]+position[2]) 231 | camera.SetViewUp(viewUp[0:3]) 232 | camera.OrthogonalizeViewUp() 233 | view.forceRender() 234 | screenshot = view.grab() 235 | bArray = qt.QByteArray() 236 | buffer = qt.QBuffer(bArray) 237 | buffer.open(qt.QIODevice.WriteOnly) 238 | #screenshot.save(buffer, "PNG") 239 | screenshot.save(buffer, "JPG") 240 | self.dataValue = bArray.toBase64().data().decode() 241 | #dataType = "image/png" 242 | self.dataType = "image/jpeg" 243 | def _repr_mimebundle_(self, include=None, exclude=None): 244 | return { self.dataType: self.dataValue } 245 | 246 | 247 | class ViewLightboxDisplay(object): 248 | """This class returns an image containing content of a slice view as it is sweeped over the displayed volume 249 | as an object to be displayed in a Jupyter notebook cell. 250 | :param viewName: :param viewName: name of the slice view, such as `Red`, `Green`, `Yellow`. 251 | Get list of all current slice view names by calling `slicer.app.layoutManager().sliceViewNames()`. 252 | :param rows: number of image rows. 253 | :param columns: number of image columns. 254 | :param positionRange: list of two float values, specifying start and end distance from the origin 255 | along the slice normal. 256 | :param rangeShrink: list of two float values, which modify the position range (positive value shrinks the range, on both sides). 257 | Useful for cropping irrelevant regions near the image boundaries. 258 | """ 259 | 260 | def __init__(self, viewName=None, rows=None, columns=None, filename=None, positionRange=None, rangeShrink=None): 261 | viewName = viewName if viewName else "Red" 262 | rows = rows if rows else 4 263 | columns = columns if columns else 6 264 | 265 | sliceWidget = slicer.app.layoutManager().sliceWidget(viewName) 266 | 267 | if positionRange is None: 268 | sliceBounds = [0,0,0,0,0,0] 269 | sliceWidget.sliceLogic().GetLowestVolumeSliceBounds(sliceBounds) 270 | slicePositionRange = [sliceBounds[4], sliceBounds[5]] 271 | else: 272 | slicePositionRange = [positionRange[0], positionRange[1]] 273 | 274 | if rangeShrink: 275 | slicePositionRange[0] += rangeShrink[0] 276 | slicePositionRange[1] -= rangeShrink[1] 277 | 278 | # Capture red slice view, 30 images, from position -125.0 to 75.0 279 | # into current folder, with name image_00001.png, image_00002.png, ... 280 | import ScreenCapture 281 | screenCaptureLogic = ScreenCapture.ScreenCaptureLogic() 282 | destinationFolder = 'outputs/Capture-SliceSweep' 283 | numberOfFrames = rows*columns 284 | filenamePattern = "_lightbox_tmp_image_%05d.png" 285 | viewNode = sliceWidget.mrmlSliceNode() 286 | # Suppress log messages 287 | def noLog(msg): 288 | pass 289 | screenCaptureLogic.addLog=noLog 290 | # Capture images 291 | screenCaptureLogic.captureSliceSweep(viewNode, slicePositionRange[0], slicePositionRange[1], 292 | numberOfFrames, destinationFolder, filenamePattern) 293 | # Create lightbox image 294 | resultImageFilename = filename if filename else filenamePattern % numberOfFrames 295 | screenCaptureLogic.createLightboxImage(columns, destinationFolder, filenamePattern, numberOfFrames, resultImageFilename) 296 | 297 | # Save result 298 | with open(destinationFolder+"/"+resultImageFilename, "rb") as file: 299 | self.dataValue = file.read() 300 | self.dataType = "image/png" 301 | # This could be used to create an image widget: img = Image(value=image, format='png') 302 | 303 | # Clean up 304 | screenCaptureLogic.deleteTemporaryFiles(destinationFolder, filenamePattern, numberOfFrames if filename else numberOfFrames+1) 305 | 306 | def _repr_mimebundle_(self, include=None, exclude=None): 307 | import base64 308 | return { self.dataType: base64.b64encode(self.dataValue).decode() } 309 | 310 | class MatplotlibDisplay(object): 311 | """Display matplotlib plot in a notebook cell. 312 | 313 | This helper function will probably not needed after this issue is fixed: 314 | https://github.com/jupyter-xeus/xeus-python/issues/224 315 | 316 | Important: set matplotlib to use agg backend:: 317 | 318 | import matplotlib 319 | matplotlib.use('agg') 320 | 321 | Example usage:: 322 | 323 | import matplotlib 324 | matplotlib.use('agg') 325 | 326 | import matplotlib.pyplot as plt 327 | import numpy as np 328 | # Data for plotting 329 | t = np.arange(0.0, 2.0, 0.01) 330 | s = 1 + np.sin(2 * np.pi * t) 331 | # Setup plot 332 | fig, ax = plt.subplots() 333 | ax.plot(t, s) 334 | ax.set(xlabel='time (s)', ylabel='voltage (mV)', 335 | title='About as simple as it gets, folks') 336 | ax.grid() 337 | 338 | slicernb.displayMatplotlib(plt) 339 | 340 | """ 341 | def __init__(self, fig): 342 | filename = "__matplotlib_temp.png" 343 | fig.savefig(filename) 344 | with open(filename, "rb") as file: 345 | self.dataValue = file.read() 346 | self.dataType = "image/png" 347 | import os 348 | os.remove(filename) 349 | 350 | def _repr_mimebundle_(self, include=None, exclude=None): 351 | import base64 352 | return { self.dataType: base64.b64encode(self.dataValue).decode() } 353 | 354 | # Utility functions for customizing what is shown in views 355 | 356 | def showVolumeRendering(volumeNode, show=True, presetName=None): 357 | """Display volume node in 3D views using volume rendering. 358 | :param volumeNode: volume node to show/hide. 359 | :param show: set to True to show the volume, False to hide it. 360 | :param presetName: volume rendering preset name, such as `CT-AAA`, `CT-AAA2`, `CT-Bone`, `CT-Bones`, 361 | `CT-Cardiac`, `CT-Cardiac2`, `CT-Cardiac3`, `CT-Chest-Contrast-Enhanced`, `CT-Chest-Vessels`, 362 | `CT-Coronary-Arteries`, `CT-Coronary-Arteries-2`, `CT-Coronary-Arteries-3`, 363 | `CT-Cropped-Volume-Bone`, `CT-Fat`, `CT-Liver-Vasculature`, `CT-Lung`, `CT-MIP`, 364 | `CT-Muscle`, `CT-Pulmonary-Arteries`, `CT-Soft-Tissue`, `CT-Air`, `CT-X-ray`, 365 | `MR-Angio`, `MR-Default`, `MR-MIP`, `MR-T2-Brain`, `DTI-FA-Brain`, `US-Fetal`. 366 | 367 | To get all the volume rendering preset names:: 368 | 369 | presets = slicer.modules.volumerendering.logic().GetPresetsScene().GetNodesByClass("vtkMRMLVolumePropertyNode") 370 | presets.UnRegister(None) 371 | for presetIndex in range(presets.GetNumberOfItems()): 372 | print(presets.GetItemAsObject(presetIndex).GetName()) 373 | 374 | """ 375 | volRenLogic = slicer.modules.volumerendering.logic() 376 | if show: 377 | displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode) 378 | if not displayNode: 379 | displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode) 380 | displayNode.SetVisibility(True) 381 | scalarRange = volumeNode.GetImageData().GetScalarRange() 382 | if not presetName: 383 | if scalarRange[1]-scalarRange[0] < 1500: 384 | # small dynamic range, probably MRI 385 | presetName = 'MR-Default' 386 | else: 387 | # larger dynamic range, probably CT 388 | presetName = 'CT-Chest-Contrast-Enhanced' 389 | displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName(presetName)) 390 | else: 391 | # hide 392 | volRenLogic = slicer.modules.volumerendering.logic() 393 | displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode) 394 | if displayNode: 395 | displayNode.SetVisibility(False) 396 | 397 | def reset3DView(viewID=0): 398 | """Centers the selected 3D view on the currently displayed content. 399 | :param viewID: integer index of the 3D view node. Valid values are between 0 and `slicer.app.layoutManager().threeDViewCount-1`. 400 | """ 401 | threeDWidget = slicer.app.layoutManager().threeDWidget(viewID) 402 | threeDView = threeDWidget.threeDView() 403 | threeDView.resetFocalPoint() 404 | 405 | def setViewLayout(layoutName): 406 | """Set view arrangement, wchich specifies what kind of views are rendered, and their location and sizes. 407 | 408 | :param layoutName: String that specifies the layout name. Most commonly used layouts are: 409 | `FourUp`, `Conventional`, `OneUp3D`, `OneUpRedSlice`, `OneUpYellowSlice`, 410 | `OneUpPlot`, `OneUpGreenSlice`, `Dual3D`, `FourOverFour`, `DicomBrowser`. 411 | 412 | Get full list of layout names:: 413 | 414 | for att in dir(slicer.vtkMRMLLayoutNode): 415 | if att.startswith("SlicerLayout"): 416 | print(att[12:-4]) 417 | 418 | """ 419 | layoutId = eval("slicer.vtkMRMLLayoutNode.SlicerLayout"+layoutName+"View") 420 | slicer.app.layoutManager().setLayout(layoutId) 421 | 422 | def showSliceViewAnnotations(show): 423 | """Show/hide corner annotations (node name, patient name, etc.) in all slice views. 424 | """ 425 | # Disable slice annotations immediately 426 | slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=show 427 | slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI() 428 | # Disable slice annotations persistently (after Slicer restarts) 429 | settings = qt.QSettings() 430 | settings.setValue('DataProbe/sliceViewAnnotations.enabled', 1 if show else 0) 431 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/files.py: -------------------------------------------------------------------------------- 1 | import slicer 2 | 3 | def getFileNameFromURL(url): 4 | """Get filename from a download URL. 5 | Attempts to query the recommended filename from the server, 6 | and if it fails then is uses 7 | """ 8 | import urllib 9 | req = urllib.request.Request(url, method='HEAD') 10 | r = urllib.request.urlopen(req) 11 | filename = r.info().get_filename() 12 | if not filename: 13 | # No filename is available, try to get it from the url 14 | import os 15 | from urllib.parse import urlparse 16 | parsedUrl = urlparse(url) 17 | filename = os.path.basename(parsedUrl.path) 18 | return filename 19 | 20 | def downloadFromURL(uris=None, fileNames=None, nodeNames=None, checksums=None, loadFiles=None, 21 | customDownloader=None, loadFileTypes=None, loadFileProperties={}): 22 | """Download data from custom URL with progress bar. 23 | :param uris: Download URL(s). 24 | :param fileNames: File name(s) that will be downloaded (and loaded). 25 | :param nodeNames: Node name(s) in the scene. 26 | :param checksums: Checksum(s) formatted as ``:`` to verify the downloaded file(s). For example, ``SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93``. 27 | :param loadFiles: Boolean indicating if file(s) should be loaded. By default, the function decides. 28 | :param customDownloader: Custom function for downloading. 29 | :param loadFileTypes: file format name(s) ('VolumeFile' by default). 30 | :param loadFileProperties: custom properties passed to the IO plugin. 31 | 32 | If the given ``fileNames`` are not found in the application cache directory, they 33 | are downloaded using the associated URIs. 34 | See ``slicer.mrmlScene.GetCacheManager().GetRemoteCacheDirectory()`` 35 | 36 | If not explicitly provided or if set to ``None``, the ``loadFileTypes`` are 37 | guessed based on the corresponding filename extensions. 38 | 39 | If a given fileName has the ``.mrb`` or ``.mrml`` extension, it will **not** be loaded 40 | by default. To ensure the file is loaded, ``loadFiles`` must be set. 41 | 42 | The ``loadFileProperties`` are common for all files. If different properties 43 | need to be associated with files of different types, downloadFromURL must 44 | be called for each. 45 | """ 46 | import SampleData 47 | sampleDataLogic = SampleData.SampleDataLogic() 48 | 49 | try: 50 | from ipywidgets import IntProgress 51 | from IPython.display import display 52 | progress = IntProgress() 53 | except ImportError: 54 | progress = None 55 | 56 | def reporthook(msg, level=None): 57 | # Download will only account for 90 percent of the time 58 | # (10% is left for loading time). 59 | progress.value=sampleDataLogic.downloadPercent*0.9 60 | 61 | if progress: 62 | sampleDataLogic.logMessage = reporthook 63 | display(progress) # show progress bar 64 | 65 | computeFileNames = not fileNames 66 | computeNodeNames = not nodeNames 67 | if computeFileNames or computeNodeNames: 68 | urisList = uris if type(uris) == list else [uris] 69 | if computeFileNames: 70 | fileNames = [] 71 | else: 72 | filenamesList = fileNames if type(fileNames) == list else [fileNames] 73 | if computeNodeNames: 74 | nodeNames = [] 75 | else: 76 | nodeNamesList = nodeNames if type(nodeNamesList) == list else [nodeNames] 77 | import os 78 | for index, uri in enumerate(urisList): 79 | if computeFileNames: 80 | fileName = getFileNameFromURL(uri) 81 | fileNames.append(fileName) 82 | else: 83 | fileName = fileNames[index] 84 | if computeNodeNames: 85 | fileNameWithoutExtension, _ = os.path.splitext(fileName) 86 | nodeNames.append(fileNameWithoutExtension) 87 | 88 | if type(uris) != list: 89 | if type(fileNames) == list: 90 | fileNames = fileNames[0] 91 | if type(nodeNames) == list: 92 | nodeNames = nodeNames[0] 93 | 94 | downloaded = sampleDataLogic.downloadFromURL(uris, fileNames, nodeNames, checksums, loadFiles, 95 | customDownloader, loadFileTypes, loadFileProperties) 96 | 97 | if progress: 98 | progress.layout.display = 'none' # hide progress bar 99 | 100 | return downloaded[0] if len(downloaded) == 1 else downloaded 101 | 102 | def localPath(filename=None): 103 | """Create a full path from a filename, relative to the notebook location. 104 | This is useful for creating file path for saving nodes. 105 | 106 | Example: `slicer.util.saveNode(modelNode, slicernb.localPath("MyModel.ply"))` 107 | """ 108 | import os 109 | notebookDir = slicer.app.startupWorkingPath 110 | if not filename: 111 | return notebookDir 112 | else: 113 | return os.path.join(notebookDir, filename) 114 | 115 | def notebookPath(verbose=False): 116 | """Returns the absolute path of the Notebook. 117 | """ 118 | from jupyter_server import serverapp as app 119 | import json 120 | import os 121 | import urllib 122 | connection_file = os.path.basename(slicer.modules.jupyterkernel.connectionFile) 123 | kernel_id = connection_file.split('-', 1)[1].split('.')[0] 124 | for srv in app.list_running_servers(): 125 | try: 126 | if srv['token']=='' and not srv['password']: # No token and no password, ahem... 127 | req = urllib.request.urlopen(srv['url']+'api/sessions') 128 | else: 129 | req = urllib.request.urlopen(srv['url']+'api/sessions?token='+srv['token']) 130 | sessions = json.load(req) 131 | for sess in sessions: 132 | if sess['kernel']['id'] == kernel_id: 133 | return os.path.join(srv['root_dir'],sess['notebook']['path']) 134 | except: 135 | pass # There may be stale entries in the runtime directory 136 | return None 137 | 138 | def notebookSaveCheckpoint(): 139 | """Save a checkpoint of current notebook. Returns True on success.""" 140 | try: 141 | from IPython.display import Javascript 142 | from IPython.display import display 143 | except ModuleNotFoundError: 144 | import logging 145 | logging.error("notebookSaveCheckpoint requires ipywidgets. It can be installed by running this command:\n\n pip_install('ipywidgets')\n") 146 | return False 147 | 148 | script = ''' 149 | require(["base/js/namespace"],function(Jupyter) { 150 | Jupyter.notebook.save_checkpoint(); 151 | }); 152 | ''' 153 | display(Javascript(script)) 154 | return True 155 | 156 | def notebookExportToHtml(outputFilePath=None): 157 | """Export current notebook to HTML. 158 | If outputFilePath is not specified then filename will be generated from the notebook filename 159 | with current timestamp appended. 160 | It returns full path of the saved html file. 161 | It requires nbformat and nbconvert packages. You can use this command to install them:: 162 | 163 | pip_install("nbformat nbconvert") 164 | 165 | """ 166 | try: 167 | import nbformat 168 | from nbconvert import HTMLExporter 169 | except ModuleNotFoundError: 170 | import logging 171 | logging.error("notebookExportToHtml requires nbformat and nbconvert. They can be installed by running this command:\n\n pip_install('nbformat nbconvert')\n") 172 | 173 | import datetime, json, os 174 | 175 | notebook_path = notebookPath() 176 | 177 | # Generate output file path from notebook name and timestamp (if not specified) 178 | if not outputFilePath: 179 | this_notebook_name = os.path.splitext(os.path.basename(notebook_path))[0] 180 | save_timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') 181 | save_file_name = this_notebook_name + "_" + save_timestamp + ".html" 182 | notebooks_save_path = os.path.dirname(notebook_path) 183 | outputFilePath = os.path.join(notebooks_save_path, save_file_name) 184 | 185 | with open(notebook_path, mode="r") as f: 186 | file_json = json.load(f) 187 | 188 | notebook_content = nbformat.reads(json.dumps(file_json), as_version=4) 189 | 190 | html_exporter = HTMLExporter() 191 | (body, resources) = html_exporter.from_notebook_node(notebook_content) 192 | 193 | f = open(outputFilePath, 'wb') 194 | f.write(body.encode()) 195 | f.close() 196 | 197 | return outputFilePath 198 | 199 | def installExtensions(extensionNames): 200 | """Download and install extensions. All extensions required by the listed extensions 201 | will be automatically installed, too. 202 | :param extensionNames: list of strings containing the extension names. 203 | """ 204 | success = True 205 | import logging 206 | emm = slicer.app.extensionsManagerModel() 207 | if hasattr(emm,'interactive'): 208 | # Disable popups asking to confirm installation of required extensions, 209 | # as a popup would block the application. 210 | emm.interactive = False 211 | installedExtensions = [] 212 | failedToInstallExtensions = [] 213 | notFoundExtensions = [] 214 | for extensionName in extensionNames: 215 | if emm.isExtensionInstalled(extensionName): 216 | continue 217 | extensionMetaData = emm.retrieveExtensionMetadataByName(extensionName) 218 | if slicer.app.majorVersion*100+slicer.app.minorVersion < 413: 219 | # Slicer-4.11 220 | if not extensionMetaData or 'item_id' not in extensionMetaData: 221 | logging.debug(f"{extensionName} extension was not found on Extensions Server") 222 | notFoundExtensions.append(extensionName) 223 | continue 224 | itemId = extensionMetaData['item_id'] 225 | url = f"{emm.serverUrl().toString()}/download?items={itemId}" 226 | else: 227 | # Slicer-4.13 228 | if not extensionMetaData or '_id' not in extensionMetaData: 229 | logging.debug(f"{extensionName} extension was not found on Extensions Server") 230 | notFoundExtensions.append(extensionName) 231 | continue 232 | itemId = extensionMetaData['_id'] 233 | url = f"{emm.serverUrl().toString()}/api/v1/item/{itemId}/download" 234 | extensionPackageFilename = slicer.app.temporaryPath+'/'+itemId 235 | try: 236 | slicer.util.downloadFile(url, extensionPackageFilename) 237 | except: 238 | logging.debug(f"{extensionName} download failed from {url}") 239 | failedToInstallExtensions.append(extensionName) 240 | continue 241 | if not emm.installExtension(extensionPackageFilename): 242 | logging.debug(f"{extensionName} install failed") 243 | failedToInstallExtensions.append(extensionName) 244 | continue 245 | installedExtensions.append(extensionName) 246 | 247 | if notFoundExtensions: 248 | logging.warning("Extensions not found: " + ", ".join(notFoundExtensions)) 249 | success = False 250 | if failedToInstallExtensions: 251 | logging.warning("Extensions failed to install: " + ", ".join(failedToInstallExtensions)) 252 | success = False 253 | if installedExtensions: 254 | print("Extensions installed: " + ", ".join(installedExtensions)) 255 | logging.warning("Restart the kernel to make the installed extensions available in this notebook.") 256 | 257 | return success 258 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/interactive_view_widget.py: -------------------------------------------------------------------------------- 1 | import qt, slicer 2 | from ipycanvas import Canvas 3 | 4 | class ViewInteractiveWidget(Canvas): 5 | """Remote controller for Slicer viewers. 6 | :param layoutLabel: specify view by label (displayed in the view's header in the layout, such as R, Y, G, 1) 7 | :param renderView: specify view by renderView object (ctkVTKRenderView). 8 | """ 9 | 10 | def __init__(self, layoutLabel=None, renderView=None, **kwargs): 11 | from ipyevents import Event 12 | #import time 13 | 14 | super().__init__(**kwargs) 15 | 16 | # Find renderView from layoutLabel 17 | layoutManager = slicer.app.layoutManager() 18 | # Find it among 3D views 19 | if not renderView: 20 | for threeDViewIndex in range(layoutManager.threeDViewCount): 21 | threeDWidget = layoutManager.threeDWidget(threeDViewIndex) 22 | if (threeDWidget.mrmlViewNode().GetLayoutLabel() == layoutLabel) or (layoutLabel is None): 23 | renderView = threeDWidget.threeDView() 24 | break 25 | # Find it among slice views 26 | if not renderView: 27 | sliceViewNames = layoutManager.sliceViewNames() 28 | for sliceViewName in sliceViewNames: 29 | sliceWidget = layoutManager.sliceWidget(sliceViewName) 30 | if (sliceWidget.mrmlSliceNode().GetLayoutLabel() == layoutLabel) or (layoutLabel is None): 31 | renderView = sliceWidget.sliceView() 32 | break 33 | 34 | if not renderView: 35 | if layoutLabel: 36 | raise ValueError("renderView is not specified and view cannot be found by layout label "+layoutLabel) 37 | else: 38 | raise ValueError("renderView is not specified") 39 | 40 | self.renderView = renderView 41 | 42 | # Frame rate (1/renderDelay) 43 | self.lastRenderTime = 0 44 | self.quickRenderDelaySec = 0.1 45 | self.quickRenderDelaySecRange = [0.02, 2.0] 46 | self.adaptiveRenderDelay = True 47 | self.lastMouseMoveEvent = None 48 | 49 | # Quality vs performance 50 | self.compressionQuality = 50 51 | self.trackMouseMove = False # refresh if mouse is just moving (not dragging) 52 | 53 | self.messageTimestampOffset = None 54 | 55 | # If not receiving new rendering request for 10ms then a render is requested 56 | self.fullRenderRequestTimer = qt.QTimer() 57 | self.fullRenderRequestTimer.setSingleShot(True) 58 | self.fullRenderRequestTimer.setInterval(500) 59 | self.fullRenderRequestTimer.connect('timeout()', self.fullRender) 60 | 61 | # If not receiving new rendering request for 10ms then a render is requested 62 | self.quickRenderRequestTimer = qt.QTimer() 63 | self.quickRenderRequestTimer.setSingleShot(True) 64 | self.quickRenderRequestTimer.setInterval(self.quickRenderDelaySec*1000) 65 | self.quickRenderRequestTimer.connect('timeout()', self.quickRender) 66 | 67 | # Get image size 68 | image = self.getImage() 69 | self.width=image.width 70 | self.height=image.height 71 | self.draw_image(image) 72 | 73 | self.interactor = self.renderView.interactorStyle().GetInteractor() 74 | 75 | self.dragging=False 76 | 77 | self.interactionEvents = Event() 78 | self.interactionEvents.source = self 79 | self.interactionEvents.watched_events = [ 80 | 'dragstart', 'mouseenter', 'mouseleave', 81 | 'mousedown', 'mouseup', 'mousemove', 82 | #'wheel', # commented out so that user can scroll through the notebook using mousewheel 83 | 'keyup', 'keydown', 84 | 'contextmenu' # prevent context menu from appearing on right-click 85 | ] 86 | #self.interactionEvents.msg_throttle = 1 # does not seem to have effect 87 | self.interactionEvents.prevent_default_action = True 88 | self.interactionEvents.on_dom_event(self.handleInteractionEvent) 89 | 90 | self.keyToSym = { 91 | 'ArrowLeft': 'Left', 92 | 'ArrowRight': 'Right', 93 | 'ArrowUp': 'Up', 94 | 'ArrowDown': 'Down', 95 | 'BackSpace': 'BackSpace', 96 | 'Tab': 'Tab', 97 | 'Enter': 'Return', 98 | #'Shift': 'Shift_L', 99 | #'Control': 'Control_L', 100 | #'Alt': 'Alt_L', 101 | 'CapsLock': 'Caps_Lock', 102 | 'Escape': 'Escape', 103 | ' ': 'space', 104 | 'PageUp': 'Prior', 105 | 'PageDown': 'Next', 106 | 'Home': 'Home', 107 | 'End': 'End', 108 | 'Delete': 'Delete', 109 | 'Insert': 'Insert', 110 | '*': 'asterisk', 111 | '+': 'plus', 112 | '|': 'bar', 113 | '-': 'minus', 114 | '.': 'period', 115 | '/': 'slash', 116 | 'F1': 'F1', 117 | 'F2': 'F2', 118 | 'F3': 'F3', 119 | 'F4': 'F4', 120 | 'F5': 'F5', 121 | 'F6': 'F6', 122 | 'F7': 'F7', 123 | 'F8': 'F8', 124 | 'F9': 'F9', 125 | 'F10': 'F10', 126 | 'F11': 'F11', 127 | 'F12': 'F12' 128 | } 129 | 130 | # Errors are not displayed when a widget is displayed, 131 | # this variable can be used to retrieve error messages 132 | self.error = None 133 | 134 | # Enable logging of UI events 135 | self.logEvents = False 136 | self.loggedEvents = [] 137 | self.elapsedTimes = [] 138 | self.ageOfProcessedMessages = [] 139 | 140 | def setQuickRenderDelay(self, delaySec): 141 | """Delay this much after a view update before sending a low-resolution update.""" 142 | if delaySecself.quickRenderDelaySecRange[1]: 145 | delaySec = self.quickRenderDelaySecRange[1] 146 | self.quickRenderDelaySec = delaySec 147 | self.quickRenderRequestTimer.setInterval(self.quickRenderDelaySec*1000) 148 | 149 | def setFullRenderDelay(self, delaySec): 150 | """Delay this much after a view update before sending a full-resolution update.""" 151 | self.fullRenderRequestTimer.setInterval(delaySec) 152 | 153 | def getImage(self, compress=True, forceRender=True): 154 | """Retrieve an image from the view.""" 155 | from ipywidgets import Image 156 | slicer.app.processEvents() 157 | if forceRender: 158 | self.renderView.forceRender() 159 | screenshot = self.renderView.grab() 160 | bArray = qt.QByteArray() 161 | buffer = qt.QBuffer(bArray) 162 | buffer.open(qt.QIODevice.WriteOnly) 163 | if compress: 164 | screenshot.save(buffer, "JPG", self.compressionQuality) 165 | else: 166 | screenshot.save(buffer, "PNG") 167 | return Image(value=bArray.data(), width=screenshot.width(), height=screenshot.height()) 168 | 169 | def fullRender(self): 170 | """Perform a full render now.""" 171 | try: 172 | import time 173 | self.fullRenderRequestTimer.stop() 174 | self.quickRenderRequestTimer.stop() 175 | self.draw_image(self.getImage(compress=False, forceRender=True)) 176 | self.lastRenderTime = time.time() 177 | except Exception as e: 178 | self.error = str(e) 179 | 180 | def sendPendingMouseMoveEvent(self): 181 | if self.lastMouseMoveEvent is not None: 182 | self.updateInteractorEventData(self.lastMouseMoveEvent) 183 | self.interactor.MouseMoveEvent() 184 | self.lastMouseMoveEvent = None 185 | 186 | def quickRender(self): 187 | """Perform a quick render now.""" 188 | try: 189 | import time 190 | self.fullRenderRequestTimer.stop() 191 | self.quickRenderRequestTimer.stop() 192 | self.sendPendingMouseMoveEvent() 193 | self.draw_image(self.getImage(compress=True, forceRender=False)) 194 | self.fullRenderRequestTimer.start() 195 | if self.logEvents: self.elapsedTimes.append(time.time()-self.lastRenderTime) 196 | self.lastRenderTime = time.time() 197 | except Exception as e: 198 | self.error = str(e) 199 | 200 | def updateInteractorEventData(self, event): 201 | try: 202 | if event['event']=='keydown' or event['event']=='keyup': 203 | key = event['key'] 204 | sym = self.keyToSym[key] if key in self.keyToSym.keys() else key 205 | self.interactor.SetKeySym(sym) 206 | if len(key) == 1: 207 | self.interactor.SetKeyCode(key) 208 | self.interactor.SetRepeatCount(1) 209 | else: 210 | self.interactor.SetEventPosition(event['offsetX'], self.height-event['offsetY']) 211 | self.interactor.SetShiftKey(event['shiftKey']) 212 | self.interactor.SetControlKey(event['ctrlKey']) 213 | self.interactor.SetAltKey(event['altKey']) 214 | except Exception as e: 215 | self.error = str(e) 216 | 217 | def handleInteractionEvent(self, event): 218 | try: 219 | if self.logEvents: 220 | self.loggedEvents.append(event) 221 | if event['event']=='mousemove': 222 | import time 223 | if self.messageTimestampOffset is None: 224 | self.messageTimestampOffset = time.time()-event['timeStamp']*0.001 225 | self.lastMouseMoveEvent = event 226 | if not self.dragging and not self.trackMouseMove: 227 | return 228 | if self.adaptiveRenderDelay: 229 | ageOfProcessedMessage = time.time()-(event['timeStamp']*0.001+self.messageTimestampOffset) 230 | if ageOfProcessedMessage > 1.5 * self.quickRenderDelaySec: 231 | # we are falling behind, try to render less frequently 232 | self.setQuickRenderDelay(self.quickRenderDelaySec * 1.05) 233 | elif ageOfProcessedMessage < 0.5 * self.quickRenderDelaySec: 234 | # we can keep up with events, try to render more frequently 235 | self.setQuickRenderDelay(self.quickRenderDelaySec / 1.05) 236 | if self.logEvents: self.ageOfProcessedMessages.append([ageOfProcessedMessage, self.quickRenderDelaySec]) 237 | # We need to render something now it no rendering since self.quickRenderDelaySec 238 | if time.time()-self.lastRenderTime > self.quickRenderDelaySec: 239 | self.quickRender() 240 | else: 241 | self.quickRenderRequestTimer.start() 242 | elif event['event']=='mouseenter': 243 | self.updateInteractorEventData(event) 244 | self.interactor.EnterEvent() 245 | self.lastMouseMoveEvent = None 246 | self.quickRenderRequestTimer.start() 247 | elif event['event']=='mouseleave': 248 | self.updateInteractorEventData(event) 249 | self.interactor.LeaveEvent() 250 | self.lastMouseMoveEvent = None 251 | self.quickRenderRequestTimer.start() 252 | elif event['event']=='mousedown': 253 | self.dragging=True 254 | self.sendPendingMouseMoveEvent() 255 | self.updateInteractorEventData(event) 256 | if event['button'] == 0: 257 | self.interactor.LeftButtonPressEvent() 258 | elif event['button'] == 2: 259 | self.interactor.RightButtonPressEvent() 260 | elif event['button'] == 1: 261 | self.interactor.MiddleButtonPressEvent() 262 | self.fullRender() 263 | elif event['event']=='mouseup': 264 | self.sendPendingMouseMoveEvent() 265 | self.updateInteractorEventData(event) 266 | if event['button'] == 0: 267 | self.interactor.LeftButtonReleaseEvent() 268 | elif event['button'] == 2: 269 | self.interactor.RightButtonReleaseEvent() 270 | elif event['button'] == 1: 271 | self.interactor.MiddleButtonReleaseEvent() 272 | self.dragging=False 273 | self.fullRender() 274 | elif event['event']=='keydown': 275 | self.sendPendingMouseMoveEvent() 276 | self.updateInteractorEventData(event) 277 | self.interactor.KeyPressEvent() 278 | self.interactor.CharEvent() 279 | if event['key'] != 'Shift' and event['key'] != 'Control' and event['key'] != 'Alt': 280 | self.fullRender() 281 | elif event['event']=='keyup': 282 | self.sendPendingMouseMoveEvent() 283 | self.updateInteractorEventData(event) 284 | self.interactor.KeyReleaseEvent() 285 | if event['key'] != 'Shift' and event['key'] != 'Control' and event['key'] != 'Alt': 286 | self.fullRender() 287 | except Exception as e: 288 | self.error = str(e) 289 | -------------------------------------------------------------------------------- /JupyterNotebooks/JupyterNotebooksLib/widgets.py: -------------------------------------------------------------------------------- 1 | import qt, slicer 2 | from traitlets import CFloat, Unicode, Int, validate, observe 3 | from ipywidgets import Image, FloatSlider, VBox, FileUpload, link 4 | from IPython.display import IFrame 5 | 6 | class ViewSliceBaseWidget(Image): 7 | """This class captures a slice view and makes it available 8 | for display in the output of a Jupyter notebook cell. 9 | :param viewName: name of the slice view, such as `Red`, `Green`, `Yellow`. 10 | Get list of all current slice view names by calling `slicer.app.layoutManager().sliceViewNames()`. 11 | """ 12 | 13 | offsetMin = CFloat(100.0, help="Max value").tag(sync=True) 14 | offsetMax = CFloat(0.0, help="Min value").tag(sync=True) 15 | offset = CFloat(0.0, help="Slice offset").tag(sync=True) 16 | viewName = Unicode(default_value='Red', help="Slice view name.").tag(sync=True) 17 | 18 | def __init__(self, viewName=None, **kwargs): 19 | if viewName: 20 | self.viewName=viewName 21 | 22 | self.offsetSlider = FloatSlider(description='Offset') 23 | 24 | self.updateImage() 25 | 26 | self._updateOffsetRange() 27 | self.l1 = link((self.offsetSlider, 'value'), (self, 'offset')) 28 | self.l2 = link((self, 'offsetMin'), (self.offsetSlider, 'min')) 29 | self.l3 = link((self, 'offsetMax'), (self.offsetSlider, 'max')) 30 | 31 | super().__init__(**kwargs) 32 | 33 | @observe('offset') 34 | def _propagate_offset(self, change): 35 | offset = change['new'] 36 | sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName) 37 | sliceBounds = [0,0,0,0,0,0] 38 | sliceWidget.sliceController().sliceOffsetSlider().setValue(offset) 39 | self.updateImage() 40 | 41 | @observe('viewName') 42 | def _propagate_viewName(self, change): 43 | self._updateOffsetRange() 44 | self.updateImage() 45 | 46 | def _updateOffsetRange(self): 47 | sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName) 48 | sliceBounds = [0,0,0,0,0,0] 49 | sliceWidget.sliceLogic().GetLowestVolumeSliceBounds(sliceBounds) 50 | positionMin = sliceBounds[4] 51 | positionMax = sliceBounds[5] 52 | if self.offsetMin != positionMin: 53 | self.offsetMin = positionMin 54 | if self.offsetMax != positionMax: 55 | self.offsetMax = positionMax 56 | if self.offset < self.offsetMin: 57 | self.offset = self.offsetMin 58 | elif self.offset > self.offsetMax: 59 | self.offset = self.offsetMax 60 | 61 | def updateImage(self): 62 | if not self.viewName: 63 | return 64 | slicer.app.processEvents() 65 | sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName) 66 | sliceView = sliceWidget.sliceView() 67 | sliceView.forceRender() 68 | screenshot = sliceView.grab() 69 | bArray = qt.QByteArray() 70 | buffer = qt.QBuffer(bArray) 71 | buffer.open(qt.QIODevice.WriteOnly) 72 | screenshot.save(buffer, "PNG") 73 | self.value = bArray.data() 74 | 75 | 76 | class ViewSliceWidget(VBox): 77 | """This class captures a slice view and makes it available 78 | for display in the output of a Jupyter notebook cell. 79 | It has a slider widget to browse slices. 80 | :param viewName: name of the slice view, such as `Red`, `Green`, `Yellow`. 81 | Get list of all current slice view names by calling `slicer.app.layoutManager().sliceViewNames()`. 82 | """ 83 | def __init__(self, view=None, **kwargs): 84 | self.sliceView = ViewSliceBaseWidget(view) 85 | super().__init__(children=[self.sliceView.offsetSlider, self.sliceView], **kwargs) 86 | 87 | def updateImage(self): 88 | self.sliceView.updateImage() 89 | 90 | class View3DWidget(Image): 91 | """This class captures a 3D view and makes it available 92 | for display in the output of a Jupyter notebook cell. 93 | :param viewID: integer index of the 3D view node. Valid values are between 0 and `slicer.app.layoutManager().threeDViewCount-1`. 94 | """ 95 | 96 | viewIndex = Int(default_value=0, help="3D view index.").tag(sync=True) 97 | 98 | def __init__(self, viewID=None, **kwargs): 99 | if viewID: 100 | self.viewIndex=viewID 101 | 102 | self.updateImage() 103 | 104 | super().__init__(**kwargs) 105 | 106 | @observe('viewIndex') 107 | def _propagate_viewIndex(self, change): 108 | self.updateImage() 109 | 110 | def updateImage(self): 111 | if self.viewIndex == None: 112 | return 113 | slicer.app.processEvents() 114 | widget = slicer.app.layoutManager().threeDWidget(self.viewIndex) 115 | view = widget.threeDView() 116 | view.forceRender() 117 | screenshot = view.grab() 118 | bArray = qt.QByteArray() 119 | buffer = qt.QBuffer(bArray) 120 | buffer.open(qt.QIODevice.WriteOnly) 121 | screenshot.save(buffer, "PNG") 122 | self.value = bArray.data() 123 | 124 | class FileUploadWidget(FileUpload): 125 | """Experimental file upload widget.""" 126 | def __init__(self, **kwargs): 127 | self.path = None 128 | self.filename = None 129 | self.observe(self._handle_upload, names='data') 130 | super().__init__(**kwargs) 131 | 132 | def _repr_mimebundle_(self, include=None, exclude=None): 133 | return self.widget 134 | 135 | def _handle_upload(self, change=None, a=None, b=None): 136 | import os 137 | metadata = self.widget.metadata[0] 138 | self.filename = metadata['name'] 139 | baseDir = os.path.dirname(notebookPath()) 140 | self.path = os.path.join(baseDir, self.filename) 141 | content = self.widget.value[self.filename]['content'] 142 | with open(self.path, 'wb') as f: f.write(content) 143 | print('Uploaded {0} ({1} bytes)'.format(self.filename, metadata['size'])) 144 | 145 | class AppWindow(IFrame): 146 | """Shows interactive screen of a remote desktop session. Requires remote desktop view configured to be displayed at .../desktop URL. 147 | If multiple kernels are used then the sceen space is shared between them. Make application window full-screen and call `show()` 148 | to ensure that current window is on top. 149 | src argument allows specifying the URL if it is cannot be found at the default location. 150 | """ 151 | def __init__(self, contents=None, windowScale=None, windowWidth=None, windowHeight=None, src=None, **kwargs): 152 | # Set default size to fill in notebook cell 153 | if kwargs.get('width', None) is None: 154 | kwargs['width'] = 960 155 | if kwargs.get('height', None) is None: 156 | kwargs['height'] = 768 157 | AppWindow.setWindowSize(windowWidth, windowHeight, windowScale) 158 | if contents is None: 159 | contents = "viewers" 160 | AppWindow.setContents(contents) 161 | AppWindow.show() 162 | if src is None: 163 | src = AppWindow.defaultDesktopUrl() 164 | super().__init__(src, **kwargs) 165 | 166 | @staticmethod 167 | def defaultDesktopUrl(): 168 | """Returns default URL of the remote desktop page.""" 169 | import os 170 | baseUrl = os.getenv("JUPYTERHUB_SERVICE_PREFIX") 171 | if baseUrl: 172 | # launched by JupyterHub 173 | # (example: https://hub.gke2.mybinder.org/user/lassoan-slicernotebooks-n05pb33x/desktop/) 174 | url = baseUrl + "/desktop/" 175 | else: 176 | # runs locally 177 | # (example: http://127.0.0.1:8888/desktop/) 178 | url = '/desktop/' 179 | return url 180 | 181 | 182 | @staticmethod 183 | def setWindowSize(width=None, height=None, scale=None): 184 | """Set application main window size. 185 | :param width: image width in pixels (by default: 1280). 186 | :param height: image height in pixels (by default: 1024). 187 | :param scale: if specified then width and heigh is scaled by this value. 188 | """ 189 | if width is None or height is None: 190 | displaySize = slicer.app.desktop().availableGeometry().size() 191 | if width is None: 192 | width = displaySize.width() 193 | if height is None: 194 | height = displaySize.height() 195 | if scale is not None: 196 | width *= scale 197 | height *= scale 198 | # make sure the window is not maximized, because then we cannot adjust its size 199 | currentWindowState = slicer.util.mainWindow().windowState() 200 | if currentWindowState & qt.Qt.WindowMaximized: 201 | slicer.util.mainWindow().setWindowState(currentWindowState & ~qt.Qt.WindowMaximized) 202 | slicer.util.mainWindow().size = qt.QSize(width, height) 203 | 204 | @staticmethod 205 | def setContents(contents): 206 | """Set application view contents. 207 | - `viewers`: show only viewers. 208 | - `full`: show the full application user interface. 209 | """ 210 | if contents=="viewers": 211 | slicer.util.findChild(slicer.util.mainWindow(), "PanelDockWidget").hide() 212 | slicer.util.setStatusBarVisible(False) 213 | slicer.util.setMenuBarsVisible(False) 214 | slicer.util.setToolbarsVisible(False) 215 | elif contents=="full": 216 | slicer.util.findChild(slicer.util.mainWindow(), "PanelDockWidget").show() 217 | slicer.util.setStatusBarVisible(True) 218 | slicer.util.setMenuBarsVisible(True) 219 | slicer.util.setToolbarsVisible(True) 220 | else: 221 | raise ValueError("contents must be 'viewers' or 'full'") 222 | 223 | @staticmethod 224 | def show(): 225 | """Brings the current application window to the top. 226 | This can be called to ensure that the application window is visible via a remote desktop view. 227 | """ 228 | mw = slicer.util.mainWindow() 229 | import os 230 | if os.name=='nt': 231 | # On Windows, the main window would just flash if we simply activate it, but it is not raised. 232 | # We can force raising by minimizing it then un-minimizing it. 233 | mw.setWindowState(mw.windowState() | qt.Qt.WindowMinimized) # minimize 234 | mw.setWindowState(mw.windowState() & ~qt.Qt.WindowMinimized) # un-minimize 235 | mw.activateWindow() 236 | else: 237 | mw.setWindowState(mw.windowState() & ~qt.Qt.WindowMinimized) # un-minimize 238 | mw.raise_() 239 | -------------------------------------------------------------------------------- /JupyterNotebooks/Resources/Icons/SlicerAdvancedGear-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/JupyterNotebooks/Resources/Icons/SlicerAdvancedGear-Small.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andras Lasso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlicerJupyter 2 | Extension for 3D Slicer that allows the application to be used from Jupyter notebook 3 | 4 | Demo video: https://youtu.be/oZ3_cRXX2QM 5 | 6 | [![](https://img.youtube.com/vi/oZ3_cRXX2QM/0.jpg)](https://www.youtube.com/watch?v=oZ3_cRXX2QM "Slicer Jupyter kernel demo") 7 | 8 | # Usage 9 | 10 | ## Option 1. Run using Binder 11 | 12 | You can use this option for a quick start. No installation or setup is needed, just click the link below and start using Slicer via Jupyter notebook in your web browser. 13 | 14 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/Slicer/SlicerNotebooks/master) 15 | 16 | When you click on the link, Binder launches 3D Slicer with SlicerJupyter extension on their cloud servers. Binder is a free service and server resources are quite limited. Also, there is no interactive access to the graphical user interface. Therefore, this option is only recommended for testing, demos, or simple computations or visualizations. 17 | 18 | ## Option 2. Run on your own computer 19 | 20 | * Install [3D Slicer](https://download.slicer.org/) and launch it 21 | * Install `SlicerJupyter` extension in Extension Manager (in the application menu choose View/Extension Manager, click Install button of SlicerJupyter, wait for the installation to complete, and click `Restart`) 22 | * Switch to `JupyterKernel` module (open the module finder by click the "Search" icon on the toolbar, or hitting Ctrl/Cmd-F, then type its name) 23 | * Click `Start Jupyter server` button 24 | 25 | ### Run classic notebook interface 26 | 27 | Follow all the installation steps above (start Jupyter server once with the default JupyterLab user interface), then restart Slicer. After this, a classic notebook server can be started by typing this into the Python console in Slicer: 28 | 29 | ``` 30 | slicer.util._executePythonModule('notebook',['--notebook-dir', 'some/path/to/workspace']) 31 | ``` 32 | 33 | ### Using external Jupyter server 34 | 35 | Slicer's Python kernel can be used in Jupyter servers in external Python environments. Kernel specification installation command is displayed in `Jupyter server in external Python environment` section in `JupyterKernel` module. 36 | 37 | You need to install and set up these Python packages: `jupyter jupyterlab ipywidgets pandas ipyevents ipycanvas`. 38 | 39 | ## Option 3. Run using docker on your computer 40 | 41 | - Install [docker](https://www.docker.com/) 42 | - Run the docker image as described [here](https://github.com/Slicer/SlicerDocker/blob/master/README.rst#usage-of-slicer-notebook-image) 43 | 44 | # Using Slicer from a notebook 45 | 46 | * Create a new notebook, selecting _Slicer 4.x_ kernel (for example, _Slicer 4.13_). Jupyter will open a new Slicer instance automatically when kernel start is requested. This Slicer instance will be automatically closed when kernel is shut down. If you manually close the Slicer application (e.g., File/Exit menu is used in Slicer) then Jupyter will automaticall restart the application in a few seconds. 47 | 48 | ![Select Slicer kernel](doc/StartKernel.png) 49 | 50 | * While the kernel is starting, "Kernel starting, please wait.." message is displayed. After maximum few ten seconds Slicer kernel should start. 51 | * Do a quick test - show views content in the notebook: 52 | 53 | ``` 54 | import JupyterNotebooksLib as slicernb 55 | slicernb.ViewDisplay() 56 | ``` 57 | 58 | * Try the interactive view widget: 59 | 60 | ``` 61 | slicernb.ViewInteractiveWidget() 62 | ``` 63 | 64 | * Hit `Tab` key for auto-complete 65 | * Hit `Shift`+`Tab` for showing documentation for a method (hit multiple times to show more details). Note: method name must be complete (you can use `Tab` key to complete the name) and the cursor must be inside the name or right after it (not in the parentheses). For example, type `slicer.util.getNode` and hit `Shift`+`Tab`. 66 | 67 | ![Hit Tab key to auto-complete](doc/AutoComplete.png) 68 | 69 | ![Hit Shift-Tab key to inspect](doc/Inspect.png) 70 | 71 | ## Notes 72 | 73 | ### Upgrading pip 74 | 75 | You may see warning messages about upgrading pip, such as this: 76 | 77 | ``` 78 | WARNING: You are using pip version 20.1.1; however, version 20.3.3 is available. 79 | You should consider upgrading via the '/Applications/Slicer.app/Contents/bin/./python-real -m pip install --upgrade pip' command. 80 | ``` 81 | 82 | In general, it is not necessary to upgrade pip, so you can ignore this warning. If you do want to upgrade it then you need to use the Slicer's "Python launcher" (instead of python-real). Slicer's Python launcher is called `PythonSlicer` and it sets up Slicer's virtual Python environment so that the real Python executable (python-real) can run correctly. 83 | 84 | ### Script not on PATH 85 | 86 | You may get warning about installing scripts in folder that is not on PATH: 87 | 88 | ``` 89 | WARNING: The script pyjson5 is installed in ‘/Applications/Slicer.app/Contents/lib/Python/bin’ which is not on PATH. 90 | ``` 91 | 92 | This warning is displayed to warn you that the installed script will not run by simply typing its name anywhere in a terminal. This can be safely ignored. 93 | 94 | ### Shutdown all Slicer Jupyter kernels 95 | 96 | If a Jupyter server is kept running then it will automatically restart all kernel instances (Slicer applications) that it manages. 97 | If the browser window is accidentally closed before shutting down the server, then you can get the address of all running servers by typing the following into any Slicer Python console: 98 | 99 | ``` 100 | slicer.util._executePythonModule('jupyter', ['notebook', 'list']) 101 | ``` 102 | 103 | Open the address in a web browser and click "Quit" button to shutdown the server. 104 | 105 | # Examples 106 | 107 | You can get started by looking at [example Slicer notebooks here](https://github.com/Slicer/SlicerNotebooks). 108 | 109 | # For developers 110 | 111 | ## Build instructions 112 | 113 | - [Build 3D Slicer](https://slicer.readthedocs.io/en/latest/developer_guide/build_instructions/index.html) 114 | - Configure this project using CMake, set `Slicer_DIR` CMake variable to the `.../Slicer-build` 115 | - Install prerequisites 116 | 117 | ## Install kernel manually 118 | 119 | Example: 120 | 121 | ``` 122 | jupyter-kernelspec install /tmp/SlicerJupyter-build/inner-build/share/Slicer-4.13/qt-loadable-modules/JupyterKernel/Slicer-4.13/ --replace --user 123 | ``` 124 | 125 | ## Launch a kernel manually 126 | 127 | Type this into Slicer's Python console to manually start a kernel that a notebook can connect to: 128 | 129 | ```python 130 | connection_file=r'C:\Users\andra\AppData\Roaming\jupyter\runtime\kernel-3100f53f-3433-40f9-8978-c72ed8f88515.json' 131 | print('Jupyter connection file: ['+connection_file+']') 132 | slicer.modules.jupyterkernel.startKernel(connection_file) 133 | ``` 134 | 135 | Path of `connection_file` is printed on jupyter notebook's terminal window. 136 | 137 | ## Special commands 138 | 139 | These commands must be the last commands in a cell. 140 | 141 | - `__kernel_debug_enable()`: enable detailed logging of all incoming Jupyter requests 142 | - `__kernel_debug_disable()`: enable detailed logging of all incoming Jupyter requests 143 | -------------------------------------------------------------------------------- /SlicerJupyterLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/SlicerJupyterLogo.png -------------------------------------------------------------------------------- /SlicerJupyterLogo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/SlicerJupyterLogo.xcf -------------------------------------------------------------------------------- /SuperBuild.cmake: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------------------------------------------------------- 3 | # External project common settings 4 | #----------------------------------------------------------------------------- 5 | 6 | set(ep_common_c_flags "${CMAKE_C_FLAGS_INIT} ${ADDITIONAL_C_FLAGS}") 7 | set(ep_common_cxx_flags "${CMAKE_CXX_FLAGS_INIT} ${ADDITIONAL_CXX_FLAGS}") 8 | 9 | #----------------------------------------------------------------------------- 10 | # Top-level "external" project 11 | #----------------------------------------------------------------------------- 12 | 13 | foreach(dep ${EXTENSION_DEPENDS}) 14 | mark_as_superbuild(${dep}_DIR) 15 | endforeach() 16 | 17 | set(proj ${SUPERBUILD_TOPLEVEL_PROJECT}) 18 | 19 | # Project dependencies 20 | set(${proj}_DEPENDS 21 | xeus 22 | xeus-python 23 | pybind11 24 | python-packages 25 | ) 26 | 27 | ExternalProject_Include_Dependencies(${proj} 28 | PROJECT_VAR proj 29 | SUPERBUILD_VAR ${EXTENSION_NAME}_SUPERBUILD 30 | ) 31 | 32 | ExternalProject_Add(${proj} 33 | ${${proj}_EP_ARGS} 34 | DOWNLOAD_COMMAND "" 35 | INSTALL_COMMAND "" 36 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} 37 | BINARY_DIR ${EXTENSION_BUILD_SUBDIRECTORY} 38 | CMAKE_CACHE_ARGS 39 | # Compiler settings 40 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 41 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 42 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 43 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 44 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 45 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 46 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 47 | # Output directories 48 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_RUNTIME_OUTPUT_DIRECTORY} 49 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 50 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 51 | # Packaging 52 | -DMIDAS_PACKAGE_EMAIL:STRING=${MIDAS_PACKAGE_EMAIL} 53 | -DMIDAS_PACKAGE_API_KEY:STRING=${MIDAS_PACKAGE_API_KEY} 54 | # Superbuild 55 | -D${EXTENSION_NAME}_SUPERBUILD:BOOL=OFF 56 | -DEXTENSION_SUPERBUILD_BINARY_DIR:PATH=${${EXTENSION_NAME}_BINARY_DIR} 57 | 58 | -Dpybind11_json_DIR:PATH=${pybind11_json_DIR} 59 | -Dpybind11_DIR:PATH=${pybind11_DIR} 60 | 61 | DEPENDS 62 | ${${proj}_DEPENDS} 63 | ) 64 | 65 | ExternalProject_AlwaysConfigure(${proj}) 66 | -------------------------------------------------------------------------------- /SuperBuild/External_ZeroMQ.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj ZeroMQ) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS "") 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED ZeroMQ_DIR AND NOT EXISTS ${ZeroMQ_DIR}) 16 | message(FATAL_ERROR "ZeroMQ_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/zeromq/libzmq.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "v4.3.5" 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | 36 | ExternalProject_Add(${proj} 37 | ${${proj}_EP_ARGS} 38 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 39 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 40 | SOURCE_DIR ${EP_SOURCE_DIR} 41 | BINARY_DIR ${EP_BINARY_DIR} 42 | CMAKE_CACHE_ARGS 43 | # Compiler settings 44 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 45 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 46 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 47 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 48 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 49 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 50 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 51 | # Output directories 52 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 53 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 54 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 55 | # Install directories 56 | -DZeroMQ_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 57 | -DZeroMQ_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 58 | -DCMAKE_INSTALL_LIBDIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} # Skip default initialization by GNUInstallDirs CMake module 59 | # Options 60 | -DZMQ_BUILD_TESTS:BOOL=OFF 61 | INSTALL_COMMAND "" 62 | DEPENDS 63 | ${${proj}_DEPENDS} 64 | ) 65 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 66 | 67 | else() 68 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 69 | endif() 70 | 71 | mark_as_superbuild(${proj}_DIR:PATH) 72 | 73 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 74 | -------------------------------------------------------------------------------- /SuperBuild/External_cppzmq.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj cppzmq) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS ZeroMQ) 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED cppzmq_DIR AND NOT EXISTS ${cppzmq_DIR}) 16 | message(FATAL_ERROR "cppzmq_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/slicer/cppzmq.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "5ad14cbd692f0dcf42f3d12d3ece788333c7b65f" # slicer-v4.7.0-2020-04-25-3746e5c 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | 36 | ExternalProject_Add(${proj} 37 | ${${proj}_EP_ARGS} 38 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 39 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 40 | SOURCE_DIR ${EP_SOURCE_DIR} 41 | BINARY_DIR ${EP_BINARY_DIR} 42 | CMAKE_CACHE_ARGS 43 | # Compiler settings 44 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 45 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 46 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 47 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 48 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 49 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 50 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 51 | # Output directories 52 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 53 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 54 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 55 | # Install directories 56 | -Dcppzmq_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 57 | -Dcppzmq_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 58 | # Options 59 | -DCPPZMQ_BUILD_TESTS:BOOL=OFF 60 | # Depdendencies 61 | -DZeroMQ_DIR:PATH=${ZeroMQ_DIR} 62 | INSTALL_COMMAND "" 63 | DEPENDS 64 | ${${proj}_DEPENDS} 65 | ) 66 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 67 | 68 | else() 69 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 70 | endif() 71 | 72 | mark_as_superbuild(${proj}_DIR:PATH) 73 | 74 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 75 | -------------------------------------------------------------------------------- /SuperBuild/External_nlohmann_json.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj nlohmann_json) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS "") 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED nlohmann_json_DIR AND NOT EXISTS ${nlohmann_json_DIR}) 16 | message(FATAL_ERROR "nlohmann_json_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/nlohmann/json.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | # version < v3.8.0 has build error in xeus-python on Windows (ambiguous conversion) 30 | # version == v3.9.1 has build error on linux (nlohmann_json not found) 31 | "v3.8.0" 32 | QUIET 33 | ) 34 | 35 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 36 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 37 | 38 | ExternalProject_Add(${proj} 39 | ${${proj}_EP_ARGS} 40 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 41 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 42 | SOURCE_DIR ${EP_SOURCE_DIR} 43 | BINARY_DIR ${EP_BINARY_DIR} 44 | CMAKE_CACHE_ARGS 45 | # Compiler settings 46 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 47 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 48 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 49 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 50 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 51 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 52 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 53 | # Output directories 54 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 55 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 56 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 57 | # Install directories 58 | -Dnlohmann_json_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 59 | -Dnlohmann_json_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 60 | -DCMAKE_INSTALL_LIBDIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} # Skip default initialization by GNUInstallDirs CMake module 61 | # Options 62 | -DBUILD_TESTING:BOOL=OFF 63 | INSTALL_COMMAND "" 64 | DEPENDS 65 | ${${proj}_DEPENDS} 66 | ) 67 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 68 | 69 | else() 70 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 71 | endif() 72 | 73 | mark_as_superbuild(${proj}_DIR:PATH) 74 | 75 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 76 | -------------------------------------------------------------------------------- /SuperBuild/External_pybind11.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj pybind11) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS "") 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED pybind11_DIR AND NOT EXISTS ${pybind11_DIR}) 16 | message(FATAL_ERROR "pybind11_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/pybind/pybind11.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "v2.8.1" # v2.9 did not generate any .sln file, which made the build fail 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | set(EP_INSTALL_DIR ${CMAKE_BINARY_DIR}/${proj}-install) 36 | 37 | ExternalProject_Add(${proj} 38 | ${${proj}_EP_ARGS} 39 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 40 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 41 | SOURCE_DIR ${EP_SOURCE_DIR} 42 | BINARY_DIR ${EP_BINARY_DIR} 43 | CMAKE_CACHE_ARGS 44 | # Compiler settings 45 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 46 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 47 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 48 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 49 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 50 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 51 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 52 | # Output directories 53 | #-DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 54 | #-DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 55 | #-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 56 | # Install directories 57 | #-Dpybind11_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 58 | #-Dpybind11_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 59 | # Options 60 | -DPYBIND11_MASTER_PROJECT:BOOL=ON 61 | -DPYBIND11_TEST:BOOL=OFF 62 | -DPYBIND11_INSTALL:BOOL=ON 63 | #-DPYBIND11_CMAKECONFIG_INSTALL_DIR:STRING="." 64 | -DCMAKE_INSTALL_PREFIX:PATH=${EP_INSTALL_DIR} 65 | 66 | -DPYTHON_EXECUTABLE:PATH=${PYTHON_EXECUTABLE} 67 | -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY} 68 | -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR} 69 | 70 | #INSTALL_COMMAND "" 71 | DEPENDS 72 | ${${proj}_DEPENDS} 73 | ) 74 | set(${proj}_DIR "${EP_INSTALL_DIR}/share/cmake/pybind11") 75 | 76 | else() 77 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 78 | endif() 79 | 80 | mark_as_superbuild(${proj}_DIR:PATH) 81 | 82 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 83 | -------------------------------------------------------------------------------- /SuperBuild/External_pybind11_json.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj pybind11_json) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS pybind11 nlohmann_json) 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED pybind11_json_DIR AND NOT EXISTS ${pybind11_json_DIR}) 16 | message(FATAL_ERROR "pybind11_json_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/pybind/pybind11_json.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "0.2.12" 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | 36 | ExternalProject_Add(${proj} 37 | ${${proj}_EP_ARGS} 38 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 39 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 40 | SOURCE_DIR ${EP_SOURCE_DIR} 41 | BINARY_DIR ${EP_BINARY_DIR} 42 | CMAKE_CACHE_ARGS 43 | # Compiler settings 44 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 45 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 46 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 47 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 48 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 49 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 50 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 51 | # Output directories 52 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 53 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 54 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 55 | # Install directories 56 | -Dpybind11_json_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 57 | -Dpybind11_json_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 58 | # Options 59 | -DBUILD_TESTS:BOOL=OFF 60 | # Dependencies 61 | -Dpybind11_DIR:PATH=${pybind11_DIR} 62 | -Dnlohmann_json_DIR:PATH=${nlohmann_json_DIR} 63 | 64 | -DPYTHON_EXECUTABLE:PATH=${PYTHON_EXECUTABLE} 65 | -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY} 66 | -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR} 67 | 68 | INSTALL_COMMAND "" 69 | DEPENDS 70 | ${${proj}_DEPENDS} 71 | ) 72 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 73 | 74 | else() 75 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 76 | endif() 77 | 78 | mark_as_superbuild(${proj}_DIR:PATH) 79 | 80 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 81 | -------------------------------------------------------------------------------- /SuperBuild/External_python-packages.cmake: -------------------------------------------------------------------------------- 1 | set(proj python-packages) 2 | 3 | # Set dependency list 4 | set(${proj}_DEPENDENCIES "") 5 | 6 | # Include dependent projects if any 7 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj DEPENDS_VAR ${proj}_DEPENDENCIES) 8 | 9 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 10 | ExternalProject_FindPythonPackage( 11 | MODULE_NAME "jedi" 12 | REQUIRED 13 | ) 14 | endif() 15 | 16 | if(NOT DEFINED ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 17 | set(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} ${Slicer_USE_SYSTEM_python}) 18 | endif() 19 | 20 | if(NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 21 | 22 | ExternalProject_SetIfNotDefined( 23 | ${CMAKE_PROJECT_NAME}_jedi_VERSION 24 | "0.18.0" 25 | QUIET 26 | ) 27 | 28 | # argon2-cffi package is needed by JupyterLab. 29 | # It does not have binary wheels, therefore we need build it manually. 30 | ExternalProject_SetIfNotDefined( 31 | ${CMAKE_PROJECT_NAME}_argon2_cffi_VERSION 32 | "20.1.0" 33 | QUIET 34 | ) 35 | 36 | ExternalProject_SetIfNotDefined( 37 | ${CMAKE_PROJECT_NAME}_xeus_python_shell_VERSION 38 | "0.5.0" 39 | QUIET 40 | ) 41 | 42 | # Alternative python prefix for installing extension python packages 43 | set(python_packages_DIR "${CMAKE_BINARY_DIR}/python-packages-install") 44 | file(TO_NATIVE_PATH ${python_packages_DIR} python_packages_DIR_NATIVE_DIR) 45 | 46 | set(python_sitepackages_DIR "${CMAKE_BINARY_DIR}/python-packages-install/${PYTHON_SITE_PACKAGES_SUBDIR}") 47 | file(TO_NATIVE_PATH ${python_sitepackages_DIR} python_sitepackages_DIR_NATIVE_DIR) 48 | 49 | 50 | set(_no_binary "") 51 | 52 | # Install jedi and requirements 53 | set(_install_jedi COMMAND ${CMAKE_COMMAND} 54 | -E env 55 | PYTHONNOUSERSITE=1 56 | CC=${CMAKE_C_COMPILER} 57 | PYTHONPATH=${python_sitepackages_DIR} 58 | ${wrapper_script} ${PYTHON_EXECUTABLE} -m pip install 59 | jedi==${${CMAKE_PROJECT_NAME}_jedi_VERSION} 60 | argon2-cffi==${${CMAKE_PROJECT_NAME}_argon2_cffi_VERSION} 61 | xeus-python-shell==${${CMAKE_PROJECT_NAME}_xeus_python_shell_VERSION} 62 | ${_no_binary} 63 | --prefix ${python_packages_DIR_NATIVE_DIR} 64 | --no-warn-script-location 65 | ) 66 | 67 | ExternalProject_Add(${proj} 68 | ${${proj}_EP_ARGS} 69 | SOURCE_DIR ${proj} 70 | BUILD_IN_SOURCE 1 71 | CONFIGURE_COMMAND "" 72 | BUILD_COMMAND "" 73 | DOWNLOAD_COMMAND "" 74 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo_append "" 75 | ${_install_jedi} 76 | DEPENDS 77 | ${${proj}_DEPENDENCIES} 78 | ) 79 | 80 | ExternalProject_GenerateProjectDescription_Step(${proj} 81 | VERSION ${${CMAKE_PROJECT_NAME}_${proj}_VERSION} 82 | LICENSE_FILES 83 | "https://raw.githubusercontent.com/davidhalter/jedi/master/LICENSE.txt" 84 | ) 85 | 86 | #----------------------------------------------------------------------------- 87 | # Launcher setting specific to build tree 88 | set(${proj}_PYTHONPATH_LAUNCHER_BUILD 89 | ${python_packages_DIR}/${PYTHON_STDLIB_SUBDIR} 90 | ${python_packages_DIR}/${PYTHON_STDLIB_SUBDIR}/lib-dynload 91 | ${python_packages_DIR}/${PYTHON_SITE_PACKAGES_SUBDIR} 92 | ) 93 | mark_as_superbuild( 94 | VARS ${proj}_PYTHONPATH_LAUNCHER_BUILD 95 | LABELS "PYTHONPATH_LAUNCHER_BUILD" 96 | ) 97 | 98 | mark_as_superbuild(python_packages_DIR:PATH) 99 | 100 | else() 101 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDENCIES}) 102 | endif() 103 | -------------------------------------------------------------------------------- /SuperBuild/External_xeus-python.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj xeus-python) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS nlohmann_json xtl ZeroMQ cppzmq pybind11 pybind11_json xeus) 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED xeus-python_DIR AND NOT EXISTS ${xeus-python_DIR}) 16 | message(FATAL_ERROR "xeus-python_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/jupyter-xeus/xeus-python.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "0.14.3" 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | 36 | set(EXTERNAL_PROJECT_CMAKE_CACHE_ARGS) 37 | if(UNIX) 38 | list(APPEND EXTERNAL_PROJECT_CMAKE_CACHE_ARGS 39 | -DOPENSSL_SSL_LIBRARY:FILEPATH=${OPENSSL_SSL_LIBRARY} 40 | -DOPENSSL_CRYPTO_LIBRARY:FILEPATH=${OPENSSL_CRYPTO_LIBRARY} 41 | ) 42 | else() 43 | list(APPEND EXTERNAL_PROJECT_CMAKE_CACHE_ARGS 44 | -DLIB_EAY_DEBUG:FILEPATH=${LIB_EAY_DEBUG} 45 | -DLIB_EAY_RELEASE:FILEPATH=${LIB_EAY_RELEASE} 46 | -DSSL_EAY_DEBUG:FILEPATH=${SSL_EAY_DEBUG} 47 | -DSSL_EAY_RELEASE:FILEPATH=${SSL_EAY_RELEASE} 48 | ) 49 | endif() 50 | 51 | ExternalProject_Add(${proj} 52 | ${${proj}_EP_ARGS} 53 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 54 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 55 | SOURCE_DIR ${EP_SOURCE_DIR} 56 | BINARY_DIR ${EP_BINARY_DIR} 57 | CMAKE_CACHE_ARGS 58 | # Compiler settings 59 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 60 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 61 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 62 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 63 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 64 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 65 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 66 | # Output directories 67 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 68 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 69 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 70 | # Install directories 71 | -Dxeus-python_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 72 | -Dxeus-python_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 73 | -DCMAKE_INSTALL_LIBDIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} # Skip default initialization by GNUInstallDirs CMake module 74 | # Options 75 | -DBUILD_TESTING:BOOL=OFF 76 | # Dependencies 77 | -Dxeus_DIR:PATH=${xeus_DIR} 78 | -Dpybind11_DIR:PATH=${pybind11_DIR} 79 | -Dpybind11_json_DIR:PATH=${pybind11_json_DIR} 80 | -Dnlohmann_json_DIR:PATH=${nlohmann_json_DIR} 81 | -Dxtl_DIR:PATH=${xtl_DIR} 82 | -DZeroMQ_DIR:PATH=${ZeroMQ_DIR} 83 | -Dcppzmq_DIR:PATH=${cppzmq_DIR} 84 | -DOPENSSL_INCLUDE_DIR:PATH=${OPENSSL_INCLUDE_DIR} 85 | 86 | -DPYTHON_EXECUTABLE:PATH=${PYTHON_EXECUTABLE} 87 | -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY} 88 | -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR} 89 | 90 | ${EXTERNAL_PROJECT_CMAKE_CACHE_ARGS} 91 | INSTALL_COMMAND "" 92 | DEPENDS 93 | ${${proj}_DEPENDS} 94 | ) 95 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 96 | 97 | else() 98 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 99 | endif() 100 | 101 | mark_as_superbuild(${proj}_DIR:PATH) 102 | 103 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 104 | -------------------------------------------------------------------------------- /SuperBuild/External_xeus.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj xeus) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS nlohmann_json xtl ZeroMQ cppzmq) 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED xeus_DIR AND NOT EXISTS ${xeus_DIR}) 16 | message(FATAL_ERROR "xeus_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/jupyter-xeus/xeus.git" 24 | QUIET 25 | ) 26 | 27 | # Important: When updating the version of xeus consider also updating 28 | # the "fix_rpath" step below. 29 | ExternalProject_SetIfNotDefined( 30 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 31 | "2.4.1" 32 | QUIET 33 | ) 34 | 35 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 36 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 37 | 38 | set(EXTERNAL_PROJECT_CMAKE_CACHE_ARGS) 39 | if(UNIX) 40 | list(APPEND EXTERNAL_PROJECT_CMAKE_CACHE_ARGS 41 | -DOPENSSL_SSL_LIBRARY:FILEPATH=${OPENSSL_SSL_LIBRARY} 42 | -DOPENSSL_CRYPTO_LIBRARY:FILEPATH=${OPENSSL_CRYPTO_LIBRARY} 43 | ) 44 | else() 45 | list(APPEND EXTERNAL_PROJECT_CMAKE_CACHE_ARGS 46 | -DLIB_EAY_DEBUG:FILEPATH=${LIB_EAY_DEBUG} 47 | -DLIB_EAY_RELEASE:FILEPATH=${LIB_EAY_RELEASE} 48 | -DSSL_EAY_DEBUG:FILEPATH=${SSL_EAY_DEBUG} 49 | -DSSL_EAY_RELEASE:FILEPATH=${SSL_EAY_RELEASE} 50 | ) 51 | endif() 52 | 53 | ExternalProject_Add(${proj} 54 | ${${proj}_EP_ARGS} 55 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 56 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 57 | SOURCE_DIR ${EP_SOURCE_DIR} 58 | BINARY_DIR ${EP_BINARY_DIR} 59 | CMAKE_CACHE_ARGS 60 | # Compiler settings 61 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 62 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 63 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 64 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 65 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 66 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 67 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 68 | # Output directories 69 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 70 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 71 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 72 | # Install directories 73 | -Dxeus_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 74 | -Dxeus_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 75 | -DCMAKE_INSTALL_LIBDIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} # Skip default initialization by GNUInstallDirs CMake module 76 | # Options 77 | -DBUILD_TESTING:BOOL=OFF 78 | # Depdendencies 79 | -Dnlohmann_json_DIR:PATH=${nlohmann_json_DIR} 80 | -Dxtl_DIR:PATH=${xtl_DIR} 81 | -DZeroMQ_DIR:PATH=${ZeroMQ_DIR} 82 | -Dcppzmq_DIR:PATH=${cppzmq_DIR} 83 | -DOPENSSL_INCLUDE_DIR:PATH=${OPENSSL_INCLUDE_DIR} 84 | ${EXTERNAL_PROJECT_CMAKE_CACHE_ARGS} 85 | INSTALL_COMMAND "" 86 | DEPENDS 87 | ${${proj}_DEPENDS} 88 | ) 89 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 90 | 91 | if(APPLE) 92 | # This corresponds to the XEUS_BINARY_CURRENT value found in the "xeus.hpp" header. 93 | # See https://github.com/jupyter-xeus/xeus/blob/master/include/xeus/xeus.hpp 94 | ExternalProject_Add_Step(${proj} fix_rpath 95 | COMMAND install_name_tool -id 96 | ${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR}/libxeus.6.dylib 97 | ${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR}/libxeus.6.dylib 98 | DEPENDEES install 99 | ) 100 | endif() 101 | 102 | else() 103 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 104 | endif() 105 | 106 | mark_as_superbuild(${proj}_DIR:PATH) 107 | 108 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 109 | -------------------------------------------------------------------------------- /SuperBuild/External_xtl.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(proj xtl) 3 | 4 | # Set dependency list 5 | set(${proj}_DEPENDS "") 6 | 7 | # Include dependent projects if any 8 | ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) 9 | 10 | if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 11 | message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") 12 | endif() 13 | 14 | # Sanity checks 15 | if(DEFINED xtl_DIR AND NOT EXISTS ${xtl_DIR}) 16 | message(FATAL_ERROR "xtl_DIR variable is defined but corresponds to nonexistent directory") 17 | endif() 18 | 19 | if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) 20 | 21 | ExternalProject_SetIfNotDefined( 22 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY 23 | "${EP_GIT_PROTOCOL}://github.com/xtensor-stack/xtl.git" 24 | QUIET 25 | ) 26 | 27 | ExternalProject_SetIfNotDefined( 28 | ${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG 29 | "0.7.4" 30 | QUIET 31 | ) 32 | 33 | set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) 34 | set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 35 | 36 | ExternalProject_Add(${proj} 37 | ${${proj}_EP_ARGS} 38 | GIT_REPOSITORY "${${CMAKE_PROJECT_NAME}_${proj}_GIT_REPOSITORY}" 39 | GIT_TAG "${${CMAKE_PROJECT_NAME}_${proj}_GIT_TAG}" 40 | SOURCE_DIR ${EP_SOURCE_DIR} 41 | BINARY_DIR ${EP_BINARY_DIR} 42 | CMAKE_CACHE_ARGS 43 | # Compiler settings 44 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 45 | -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} 46 | -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} 47 | -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} 48 | -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} 49 | -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} 50 | -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} 51 | # Output directories 52 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} 53 | -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} 54 | -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} 55 | # Install directories 56 | -Dxtl_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 57 | -Dxtl_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} 58 | # Options 59 | -DBUILD_TESTING:BOOL=OFF 60 | INSTALL_COMMAND "" 61 | DEPENDS 62 | ${${proj}_DEPENDS} 63 | ) 64 | set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-build) 65 | 66 | else() 67 | ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) 68 | endif() 69 | 70 | mark_as_superbuild(${proj}_DIR:PATH) 71 | 72 | ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") 73 | -------------------------------------------------------------------------------- /doc/AutoComplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/doc/AutoComplete.png -------------------------------------------------------------------------------- /doc/Inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/doc/Inspect.png -------------------------------------------------------------------------------- /doc/InstallVideoThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/doc/InstallVideoThumbnail.png -------------------------------------------------------------------------------- /doc/StartKernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slicer/SlicerJupyter/724809ab27667793a0438af6e087ff7decd7d1fe/doc/StartKernel.png --------------------------------------------------------------------------------