diff --git a/.gitignore b/.gitignore
index 25d39f193b6..c4c37ee9497 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,4 @@ tags
# Documentation
Docs/_build
+Docs/_moduledescriptions
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 00000000000..820a025e81f
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,3 @@
+# Credits
+
+Please see the GitHub project page at .
diff --git a/AUTHORS.rst b/AUTHORS.rst
deleted file mode 100644
index 2dc7287948e..00000000000
--- a/AUTHORS.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-=======
-Credits
-=======
-
-Please see the GitHub project page at https://github.com/Slicer/Slicer/graphs/contributors
diff --git a/Docs/_extracli/BRAINSDWICleanup.xml b/Docs/_extracli/BRAINSDWICleanup.xml
new file mode 100644
index 00000000000..8583f779612
--- /dev/null
+++ b/Docs/_extracli/BRAINSDWICleanup.xml
@@ -0,0 +1,44 @@
+
+
+ Diffusion.Utilities
+ BRAINSDWICleanup
+
+ Remove bad gradients/volumes from DWI NRRD file.
+
+ 5.4.0
+
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ This tool was developed by Kent Williams.
+
+
+
+
+
+
+
+ inputVolume
+ i
+ inputVolume
+ Required: input image is a 4D NRRD image.
+
+ input
+
+
+ outputVolume
+ o
+ outputVolume
+ given a list of
+
+ output
+
+
+
+ badGradients
+ badGradients
+ b
+
+
+
+
+
+
diff --git a/Docs/_extracli/BRAINSDeface.xml b/Docs/_extracli/BRAINSDeface.xml
new file mode 100644
index 00000000000..bd4901c76fe
--- /dev/null
+++ b/Docs/_extracli/BRAINSDeface.xml
@@ -0,0 +1,188 @@
+
+
+ Utilities.BRAINS
+ Brain Deface from T1/T2 image (BRAINS)
+
+ This program: 1) will deface images from a set of images. Inputs must be ACPC aligned, and AC, PC, LE, RE provided.
+
+ 5.4.0
+
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+
+ This tool is created by Hans J. Johnson.
+
+
+ This work was developed by the University of Iowa Department of Electrical and Computer Engineering.
+
+
+
+
+ First Image, Second Image and Mask Image
+
+
+ inputLandmarks
+ l
+ inputLandmarks
+ Input Landmark File with LE, and RE points defined in physical locations
+
+
+ input
+
+
+ >
+ inputVolume
+ i
+ inputVolume
+ Input images, all images must be in the exact same physical space, ACPC aligned and consistent with landmarks.
+
+ input
+
+ >
+ passiveVolume
+ p
+ passiveVolume
+ Input images not used in generating masks, all images must be in the exact same physical space as inputVolumes, ACPC aligned and consistent with landmarks.
+
+ input
+
+
+
+
+ inputMask
+ inputMask
+ Optional pre-generated mask to use.
+
+ input
+
+
+
+ defaceMode
+ defaceMode
+ zero
+ blur
+ blur
+
+
+
+
+
+ Outputs from both MUSH generation and brain volume mask creation
+
+
+ outputMask
+ o
+ outputMask
+ The brain volume mask generated from the MUSH image
+
+ output
+ output.nii.gz
+
+
+ outputDirectory
+ d
+ outputDirectory
+ The output directory to writing the defaced input files
+
+ output
+ /tmp
+
+
+
+
+
+ Modify the program to only generate a mask
+
+
+ noMaskApplication
+ noMaskApplication
+ Do not apply the mask to the input images used to generate the mask
+
+
+
+
+
+
+
+ Parameters for normalizing the output images.
+
+ upperPercentile
+ upperPercentile
+ Upper Intensity Percentile (0.99 default)
+
+ 0.99
+
+ 0.0
+ 1.0
+ .001
+
+
+
+
+ lowerPercentile
+ lowerPercentile
+ Lower Intensity Percentile (0.01 default)
+
+ 0.01
+
+ 0.0
+ 1.0
+ .001
+
+
+
+
+ upperOutputIntensity
+ upperOutputIntensity
+ Upper Output Intensity
+
+ 255
+
+ 0
+ 255
+ .1
+
+
+
+
+ lowerOutputIntensity
+ lowerOutputIntensity
+ Lower Output Intensity
+
+ 0
+
+ 0
+ 255
+ .1
+
+
+
+
+ no_clip
+ no_clip
+ Do not clip Values outside of this range to be the "Outside Value"
+
+
+
+
+
+ no_relative
+ no_relative
+ Do not scale to the relative percentiles of the output scale
+
+
+
+
+
+ debugLevel
+ debugLevel
+ Level of Debugging (0=None)
+
+ 0
+
+ 0
+ 10
+ 1
+
+
+
+
diff --git a/Docs/_extracli/BRAINSFit.xml b/Docs/_extracli/BRAINSFit.xml
new file mode 100644
index 00000000000..4b13d4becdd
--- /dev/null
+++ b/Docs/_extracli/BRAINSFit.xml
@@ -0,0 +1,558 @@
+
+
+ Registration
+ General Registration (BRAINS)
+ Register a three-dimensional volume to a reference volume (Mattes Mutual Information by default). Full documentation avalable here: http://wiki.slicer.org/slicerWiki/index.php/Documentation/4.1/Modules/BRAINSFit. Method described in BRAINSFit: Mutual Information Registrations of Whole-Brain 3D Images, Using the Insight Toolkit, Johnson H.J., Harris G., Williams K., The Insight Journal, 2007. http://hdl.handle.net/1926/1291
+ http://www.slicer.org/slicerWiki/index.php/Documentation/4.1/Modules/BRAINSFit
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Hans J. Johnson (hans-johnson -at- uiowa.edu, http://www.psychiatry.uiowa.edu), Ali Ghayoor
+
+ 5.4.0
+
+
+
+
+ fixedVolume
+ fixedVolume
+
+ Input fixed image (the moving image will be transformed into this image space).
+ input
+
+
+
+ movingVolume
+ movingVolume
+
+ Input moving image (this image will be transformed into the fixed image space).
+
+ input
+
+
+
+
+
+
+ samplingPercentage
+ samplingPercentage
+
+ Fraction of voxels of the fixed image that will be used for registration. The number has to be larger than zero and less or equal to one. Higher values increase the computation time but may give more accurate results. You can also limit the sampling focus with ROI masks and ROIAUTO mask generation. The default is 0.002 (use approximately 0.2% of voxels, resulting in 100000 samples in a 512x512x192 volume) to provide a very fast registration in most cases. Typical values range from 0.01 (1%) for low detail images to 0.2 (20%) for high detail images.
+ 0.002
+
+
+ splineGridSize
+ splineGridSize
+
+ Number of BSpline grid subdivisions along each axis of the fixed image, centered on the image space. Values must be 3 or higher for the BSpline to be correctly computed.
+ 14,10,12
+
+
+
+
+
+
+ linearTransform
+ linearTransform
+
+ (optional) Output estimated transform - in case the computed transform is not BSpline. NOTE: You must set at least one output object (transform and/or output volume).
+ output
+
+
+
+
+ bsplineTransform
+ bsplineTransform
+
+ (optional) Output estimated transform - in case the computed transform is BSpline. NOTE: You must set at least one output object (transform and/or output volume).
+ output
+
+
+
+
+ outputVolume
+ outputVolume
+
+ (optional) Output image: the moving image warped to the fixed image space. NOTE: You must set at least one output object (transform and/or output volume).
+ output
+
+
+
+
+
+
+ Options for initializing transform parameters.
+
+ initialTransform
+ initialTransform
+
+ Transform to be applied to the moving image to initialize the registration. This can only be used if Initialize Transform Mode is Off.
+ input
+
+
+ initializeTransformMode
+ initializeTransformMode
+
+ Determine how to initialize the transform center. useMomentsAlign assumes that the center of mass of the images represent similar structures. useCenterOfHeadAlign attempts to use the top of head and shape of neck to drive a center of mass estimate. useGeometryAlign on assumes that the center of the voxel lattice of the images represent similar structures. Off assumes that the physical space of the images are close. This flag is mutually exclusive with the Initialization transform.
+ Off
+ Off
+ useMomentsAlign
+ useCenterOfHeadAlign
+ useGeometryAlign
+ useCenterOfROIAlign
+
+
+
+
+
+ Each of the registration phases will be used to initialize the next phase
+
+ useRigid
+ useRigid
+
+ Perform a rigid registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useScaleVersor3D
+ useScaleVersor3D
+
+ Perform a ScaleVersor3D registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useScaleSkewVersor3D
+ useScaleSkewVersor3D
+
+ Perform a ScaleSkewVersor3D registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useAffine
+ useAffine
+
+ Perform an Affine registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useBSpline
+ useBSpline
+
+ Perform a BSpline registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useSyN
+ useSyN
+
+ Perform a SyN registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+ useComposite
+ useComposite
+
+ Perform a Composite registration as part of the sequential registration steps. This family of options overrides the use of transformType if any of them are set.
+ false
+
+
+
+
+
+
+ maskProcessingMode
+ maskProcessingMode
+ Specifies a mask to only consider a certain image region for the registration. If ROIAUTO is chosen, then the mask is computed using Otsu thresholding and hole filling. If ROI is chosen then the mask has to be specified as in input.
+
+ NOMASK
+ NOMASK
+ ROIAUTO
+ ROI
+
+
+ fixedBinaryVolume
+ fixedBinaryVolume
+
+ Fixed Image binary mask volume, required if Masking Option is ROI. Image areas where the mask volume has zero value are ignored during the registration.
+ input
+
+
+ movingBinaryVolume
+ movingBinaryVolume
+
+ Moving Image binary mask volume, required if Masking Option is ROI. Image areas where the mask volume has zero value are ignored during the registration.
+ input
+
+
+ outputFixedVolumeROI
+ outputFixedVolumeROI
+
+ ROI that is automatically computed from the fixed image. Only available if Masking Option is ROIAUTO. Image areas where the mask volume has zero value are ignored during the registration.
+ output
+
+
+ outputMovingVolumeROI
+ outputMovingVolumeROI
+
+ ROI that is automatically computed from the moving image. Only available if Masking Option is ROIAUTO. Image areas where the mask volume has zero value are ignored during the registration.
+ output
+
+
+ useROIBSpline
+ useROIBSpline
+
+ If enabled then the bounding box of the input ROIs defines the BSpline grid support region. Otherwise the BSpline grid support region is the whole fixed image.
+ false
+
+
+ histogramMatch
+ e
+ histogramMatch
+ Apply histogram matching operation for the input images to make them more similar. This is suitable for images of the same modality that may have different brightness or contrast, but the same overall intensity profile. Do NOT use if registering images from different modalities.
+
+ false
+
+
+ medianFilterSize
+ medianFilterSize
+
+ Apply median filtering to reduce noise in the input volumes. The 3 values specify the radius for the optional MedianImageFilter preprocessing in all 3 directions (in voxels).
+ 0,0,0
+
+
+ removeIntensityOutliers
+ removeIntensityOutliers
+
+ Remove very high and very low intensity voxels from the input volumes. The parameter specifies the half percentage to decide outliers of image intensities. The default value is zero, which means no outlier removal. If the value of 0.005 is given, the 0.005% of both tails will be thrown away, so 0.01% of intensities in total would be ignored in the statistic calculation.
+ 0.0
+
+
+
+
+
+
+ fixedVolume2
+ fixedVolume2
+
+ Input fixed image that will be used for multimodal registration. (the moving image will be transformed into this image space).
+ input
+
+
+
+ movingVolume2
+ movingVolume2
+
+ Input moving image that will be used for multimodal registration(this image will be transformed into the fixed image space).
+
+ input
+
+
+ outputVolumePixelType
+ outputVolumePixelType
+
+ Data type for representing a voxel of the Output Volume.
+ float
+ float
+ short
+ ushort
+ int
+ uint
+ uchar
+
+
+ backgroundFillValue
+ backgroundFillValue
+
+ This value will be used for filling those areas of the output image that have no corresponding voxels in the input moving image.
+ 0.0
+
+
+ scaleOutputValues
+ scaleOutputValues
+
+ If true, and the voxel values do not fit within the minimum and maximum values of the desired outputVolumePixelType, then linearly scale the min/max output image voxel values to fit within the min/max range of the outputVolumePixelType.
+ false
+
+
+ interpolationMode
+ interpolationMode
+
+ Type of interpolation to be used when applying transform to moving volume. Options are Linear, NearestNeighbor, BSpline, WindowedSinc, Hamming, Cosine, Welch, Lanczos, or ResampleInPlace. The ResampleInPlace option will create an image with the same discrete voxel values and will adjust the origin and direction of the physical space interpretation.
+ Linear
+ NearestNeighbor
+ Linear
+ ResampleInPlace
+ BSpline
+ WindowedSinc
+ Hamming
+ Cosine
+ Welch
+ Lanczos
+ Blackman
+
+
+
+
+
+
+ numberOfIterations
+ numberOfIterations
+
+ The maximum number of iterations to try before stopping the optimization. When using a lower value (500-1000) then the registration is forced to terminate earlier but there is a higher risk of stopping before an optimal solution is reached.
+ 1500
+
+
+ maximumStepLength
+ maximumStepLength
+
+ Starting step length of the optimizer. In general, higher values allow for recovering larger initial misalignments but there is an increased chance that the registration will not converge.
+ 0.05
+
+
+ minimumStepLength
+ minimumStepLength
+
+ Each step in the optimization takes steps at least this big. When none are possible, registration is complete. Smaller values allows the optimizer to make smaller adjustments, but the registration time may increase.
+ 0.001
+
+
+ relaxationFactor
+ relaxationFactor
+
+ Specifies how quickly the optimization step length is decreased during registration. The value must be larger than 0 and smaller than 1. Larger values result in slower step size decrease, which allow for recovering larger initial misalignments but it increases the registration time and the chance that the registration will not converge.
+ 0.5
+
+
+ translationScale
+ translationScale
+
+ How much to scale up changes in position (in mm) compared to unit rotational changes (in radians) -- decrease this to allow for more rotation in the search pattern.
+ 1000.0
+
+
+ reproportionScale
+ reproportionScale
+
+ ScaleVersor3D 'Scale' compensation factor. Increase this to allow for more rescaling in a ScaleVersor3D or ScaleSkewVersor3D search pattern. 1.0 works well with a translationScale of 1000.0
+ 1.0
+
+
+ skewScale
+ skewScale
+
+ ScaleSkewVersor3D Skew compensation factor. Increase this to allow for more skew in a ScaleSkewVersor3D search pattern. 1.0 works well with a translationScale of 1000.0
+ 1.0
+
+
+ maxBSplineDisplacement
+ maxBSplineDisplacement
+
+ Maximum allowed displacements in image physical coordinates (mm) for BSpline control grid along each axis. A value of 0.0 indicates that the problem should be unbounded. NOTE: This only constrains the BSpline portion, and does not limit the displacement from the associated bulk transform. This can lead to a substantial reduction in computation time in the BSpline optimizer.
+
+ 0.0
+
+
+
+
+
+
+ fixedVolumeTimeIndex
+ fixedVolumeTimeIndex
+
+ The index in the time series for the 3D fixed image to fit. Only allowed if the fixed input volume is 4-dimensional.
+ 0
+
+
+ movingVolumeTimeIndex
+ movingVolumeTimeIndex
+
+ The index in the time series for the 3D moving image to fit. Only allowed if the moving input volume is 4-dimensional
+ 0
+
+
+
+ numberOfHistogramBins
+ numberOfHistogramBins
+ The number of histogram levels used for mutual information metric estimation.
+
+ 50
+
+
+ numberOfMatchPoints
+ numberOfMatchPoints
+ Number of histogram match points used for mutual information metric estimation.
+
+ 10
+
+
+ costMetric
+ costMetric
+
+ The cost metric to be used during fitting. Defaults to MMI. Options are MMI (Mattes Mutual Information), MSE (Mean Square Error), NC (Normalized Correlation), MC (Match Cardinality for binary images)
+ MMI
+ MMI
+ MSE
+ NC
+ MIH
+
+
+ maskInferiorCutOffFromCenter
+ maskInferiorCutOffFromCenter
+
+ If Initialize Transform Mode is set to useCenterOfHeadAlign or Masking Option is ROIAUTO then this value defines the how much is cut of from the inferior part of the image. The cut-off distance is specified in millimeters, relative to the image center. If the value is 1000 or larger then no cut-off performed.
+ 1000.0
+
+
+ ROIAutoDilateSize
+ ROIAutoDilateSize
+
+ This flag is only relevant when using ROIAUTO mode for initializing masks. It defines the final dilation size to capture a bit of background outside the tissue region. A setting of 10mm has been shown to help regularize a BSpline registration type so that there is some background constraints to match the edges of the head better.
+ 0.0
+
+
+ ROIAutoClosingSize
+ ROIAutoClosingSize
+
+ This flag is only relevant when using ROIAUTO mode for initializing masks. It defines the hole closing size in mm. It is rounded up to the nearest whole pixel size in each direction. The default is to use a closing size of 9mm. For mouse data this value may need to be reset to 0.9 or smaller.
+ 9.0
+
+
+ numberOfSamples
+ numberOfSamples
+
+ The number of voxels sampled for mutual information computation. Increase this for higher accuracy, at the cost of longer computation time.\nNOTE that it is suggested to use samplingPercentage instead of this option. However, if set to non-zero, numberOfSamples overwrites the samplingPercentage option.
+ 0
+
+
+ strippedOutputTransform
+ strippedOutputTransform
+
+ Rigid component of the estimated affine transform. Can be used to rigidly register the moving image to the fixed image. NOTE: This value is overridden if either bsplineTransform or linearTransform is set.
+ output
+
+
+
+
+ transformType
+ transformType
+
+ Specifies a list of registration types to be used. The valid types are, Rigid, ScaleVersor3D, ScaleSkewVersor3D, Affine, BSpline and SyN. Specifying more than one in a comma separated list will initialize the next stage with the previous results. If registrationClass flag is used, it overrides this parameter setting.
+
+
+
+
+ outputTransform
+ outputTransform
+
+ (optional) Filename to which save the (optional) estimated transform. NOTE: You must select either the outputTransform or the outputVolume option.
+ output
+
+
+
+
+ initializeRegistrationByCurrentGenericTransform
+ initializeRegistrationByCurrentGenericTransform
+
+ If this flag is ON, the current generic composite transform, resulted from the linear registration stages, is set to initialize the follow nonlinear registration process. However, by the default behaviour, the moving image is first warped based on the existant transform before it is passed to the BSpline registration filter. It is done to speed up the BSpline registration by reducing the computations of composite transform Jacobian.
+ 0
+
+
+ writeOutputTransformInFloat
+ writeOutputTransformInFloat
+
+ By default, the output registration transforms (either the output composite transform or each transform component) are written to the disk in double precision. If this flag is ON, the output transforms will be written in single (float) precision. It is especially important if the output transform is a displacement field transform, or it is a composite transform that includes several displacement fields.
+ false
+
+
+
+
+
+
+ failureExitCode
+ failureExitCode
+
+ If the fit fails, exit with this status code. (It can be used to force a successfult exit status of (0) if the registration fails due to reaching the maximum number of iterations.
+ -1
+
+
+ writeTransformOnFailure
+ writeTransformOnFailure
+
+ Flag to save the final transform even if the numberOfIterations are reached without convergence. (Intended for use when --failureExitCode 0 )
+ 0
+
+
+ numberOfThreads
+ numberOfThreads
+
+ Explicitly specify the maximum number of threads to use. (default is auto-detected)
+ -1
+
+
+ debugLevel
+
+ Display debug messages, and produce debug intermediate results. 0=OFF, 1=Minimal, 10=Maximum debugging.
+ debugLevel
+ 0
+
+
+ costFunctionConvergenceFactor
+ costFunctionConvergenceFactor
+ From itkLBFGSBOptimizer.h: Set/Get the CostFunctionConvergenceFactor. Algorithm terminates when the reduction in cost function is less than (factor * epsmcj) where epsmch is the machine precision. Typical values for factor: 1e+12 for low accuracy; 1e+7 for moderate accuracy and 1e+1 for extremely high accuracy. 1e+9 seems to work well.
+
+ 2e+13
+
+
+ projectedGradientTolerance
+ projectedGradientTolerance
+ From itkLBFGSBOptimizer.h: Set/Get the ProjectedGradientTolerance. Algorithm terminates when the project gradient is below the tolerance. Default lbfgsb value is 1e-5, but 1e-4 seems to work well.
+
+ 1e-5
+
+
+ maximumNumberOfEvaluations
+ maximumNumberOfEvaluations
+ Maximum number of evaluations for line search in lbfgsb optimizer.
+ 900
+
+
+ maximumNumberOfCorrections
+ maximumNumberOfCorrections
+ Maximum number of corrections in lbfgsb optimizer.
+ 25
+
+
+ UseDebugImageViewer
+ G
+ gui
+ Display intermediate image volumes for debugging. NOTE: This is not part of the standard build sytem, and probably does nothing on your installation.
+ false
+
+
+ PromptAfterImageSend
+ p
+ promptUser
+ Prompt the user to hit enter each time an image is sent to the DebugImageViewer
+ false
+
+
+ metricSamplingStrategy
+ metricSamplingStrategy
+
+ It defines the method that registration filter uses to sample the input fixed image. Only Random is supported for now.
+ Random
+ Random
+
+
+ logFileReport
+ logFileReport
+
+ A file to write out final information report in CSV file: MetricName,MetricValue,FixedImageName,FixedMaskName,MovingImageName,MovingMaskName
+ output
+
+
+ printVersionInfo
+ v
+ false
+
+
+
+
diff --git a/Docs/_extracli/BRAINSLabelStats.xml b/Docs/_extracli/BRAINSLabelStats.xml
new file mode 100644
index 00000000000..499e99e35bf
--- /dev/null
+++ b/Docs/_extracli/BRAINSLabelStats.xml
@@ -0,0 +1,115 @@
+
+
+ Quantification
+ Label Statistics (BRAINS)
+ Compute image statistics within each label of a label map.
+ 5.4.0
+ http://www.nitrc.org/plugins/mwiki/index.php/brains:BRAINSClassify
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Vincent A. Magnotta
+ Funding for this work was provided by the Dana Foundation
+
+
+
+
+
+ imageVolume
+ --imageVolume
+
+ Image Volume
+ input
+
+
+
+
+ labelVolume
+ --labelVolume
+
+ Label Volume
+
+ input
+
+
+
+ labelNameFile
+ --labelNameFile
+
+ Label Name File
+
+ input
+
+
+
+
+
+
+
+
+ outputPrefixColumnNames
+ --outputPrefixColumnNames
+ Prefix Column Name(s)
+
+
+
+
+
+ outputPrefixColumnValues
+ --outputPrefixColumnValues
+ Prefix Column Value(s)
+
+
+
+
+
+ labelFileType
+ --labelFileType
+
+ Label File Type
+ unknown
+ unknown
+ fslxml
+ ants
+ csv
+
+
+
+
+
+
+
+ numberOfHistogramBins
+ --numberOfHistogramBins
+ Number Of Histogram Bins
+
+ 100000
+
+
+
+ minMaxType
+ --minMaxType
+
+ Define minimim and maximum values based upon the image, label, or via command line
+ image
+ image
+ label
+ manual
+
+
+
+ userDefineMinimum
+ --userDefineMinimum
+ User define minimum value
+
+ 0.0
+
+
+
+ userDefineMaximum
+ --userDefineMaximum
+ User define maximum value
+
+ 4095.0
+
+
+
+
diff --git a/Docs/_extracli/BRAINSROIAuto.xml b/Docs/_extracli/BRAINSROIAuto.xml
new file mode 100644
index 00000000000..598d6bd1cb8
--- /dev/null
+++ b/Docs/_extracli/BRAINSROIAuto.xml
@@ -0,0 +1,108 @@
+
+
+ Segmentation.Specialized
+ Foreground masking (BRAINS)
+ This program is used to create a mask over the most prominant forground region in an image. This is accomplished via a combination of otsu thresholding and a closing operation. More documentation is available here: http://wiki.slicer.org/slicerWiki/index.php/Documentation/4.1/Modules/ForegroundMasking.
+
+ 5.4.0
+
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Hans J. Johnson, hans-johnson -at- uiowa.edu, http://www.psychiatry.uiowa.edu
+ Hans Johnson(1,3,4); Kent Williams(1); Gregory Harris(1), Vincent Magnotta(1,2,3); Andriy Fedorov(5), fedorov -at- bwh.harvard.edu (Slicer integration); (1=University of Iowa Department of Psychiatry, 2=University of Iowa Department of Radiology, 3=University of Iowa Department of Biomedical Engineering, 4=University of Iowa Department of Electrical and Computer Engineering, 5=Surgical Planning Lab, Harvard)
+
+
+ Input/output parameters
+
+ inputVolume
+ inputVolume
+
+ The input image for finding the largest region filled mask.
+ input
+
+
+ outputROIMaskVolume
+ outputROIMaskVolume
+
+ The ROI automatically found from the input image.
+ output
+
+
+ outputVolume
+ outputVolume
+
+ The inputVolume with optional [maskOutput|cropOutput] to the region of the brain mask.
+ output
+
+
+ maskOutput
+ maskOutput
+
+ The inputVolume multiplied by the ROI mask.
+ true
+
+
+ cropOutput
+ cropOutput
+
+ The inputVolume cropped to the region of the ROI mask.
+ false
+
+
+
+
+
+
+
+ otsuPercentileThreshold
+ otsuPercentileThreshold
+
+ Parameter to the Otsu threshold algorithm.
+ 0.01
+
+
+
+ thresholdCorrectionFactor
+ thresholdCorrectionFactor
+
+ A factor to scale the Otsu algorithm's result threshold, in case clipping mangles the image.
+ 1.0
+
+
+
+ closingSize
+ closingSize
+
+ The Closing Size (in millimeters) for largest connected filled mask. This value is divided by image spacing and rounded to the next largest voxel number.
+ 9.0
+
+
+
+ ROIAutoDilateSize
+ ROIAutoDilateSize
+
+ This flag is only relavent when using ROIAUTO mode for initializing masks. It defines the final dilation size to capture a bit of background outside the tissue region. At setting of 10mm has been shown to help regularize a BSpline registration type so that there is some background constraints to match the edges of the head better.
+ 0.0
+
+
+
+ outputVolumePixelType
+ outputVolumePixelType
+
+ The output image Pixel Type is the scalar datatype for representation of the Output Volume.
+ short
+ float
+ short
+ ushort
+ int
+ uint
+ uchar
+
+
+ numberOfThreads
+ numberOfThreads
+
+ Explicitly specify the maximum number of threads to use.
+ -1
+
+
+
diff --git a/Docs/_extracli/BRAINSResample.xml b/Docs/_extracli/BRAINSResample.xml
new file mode 100644
index 00000000000..658eb3b8228
--- /dev/null
+++ b/Docs/_extracli/BRAINSResample.xml
@@ -0,0 +1,155 @@
+
+
+ Registration
+ Resample Image (BRAINS)
+
+
+ This program collects together three common image processing tasks that all involve resampling an image volume: Resampling to a new resolution and spacing, applying a transformation (using an ITK transform IO mechanisms) and Warping (using a vector image deformation field). Full documentation available here: http://wiki.slicer.org/slicerWiki/index.php/Documentation/4.1/Modules/BRAINSResample.
+
+ 5.4.0
+ http://www.slicer.org/slicerWiki/index.php/Documentation/4.1/Modules/BRAINSResample
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ This tool was developed by Vincent Magnotta, Greg Harris, and Hans Johnson.
+ The development of this tool was supported by funding from grants NS050568 and NS40068 from the National Institute of Neurological Disorders and Stroke and grants MH31593, MH40856, from the National Institute of Mental Health.
+
+
+
+ Parameters for specifying the image to warp and resulting image space
+
+
+ inputVolume
+ inputVolume
+ Image To Warp
+
+ input
+
+
+
+ referenceVolume
+ referenceVolume
+ Reference image used only to define the output space. If not specified, the warping is done in the same space as the image to warp.
+
+ input
+
+
+
+
+
+ Resulting deformed image parameters
+
+
+ outputVolume
+ outputVolume
+ Resulting deformed image
+
+ output
+
+
+
+
+ pixelType
+ pixelType
+
+ Specifies the pixel type for the input/output images. If the type is "input", then infer from the input image. The "binary" pixel type uses a modified algorithm whereby the image is read in as unsigned char, a signed distance map is created, signed distance map is resampled, and then a thresholded image of type unsigned char is written to disk.
+ float
+ float
+ short
+ ushort
+ int
+ uint
+ uchar
+ binary
+ input
+
+
+
+
+
+
+ Parameters used to define home the image is warped
+
+
+ deformationVolume
+ deformationVolume
+ Displacement Field to be used to warp the image (ITKv3 or earlier)
+
+ input
+
+
+
+
+ warpTransform
+ warpTransform
+
+ Filename for the BRAINSFit transform (ITKv3 or earlier) or composite transform file (ITKv4)
+ input
+
+
+
+
+ interpolationMode
+ interpolationMode
+
+ Type of interpolation to be used when applying transform to moving volume. Options are Linear, ResampleInPlace, NearestNeighbor, BSpline, or WindowedSinc
+ Linear
+ NearestNeighbor
+ Linear
+ ResampleInPlace
+ BSpline
+ WindowedSinc
+ Hamming
+ Cosine
+ Welch
+ Lanczos
+ Blackman
+
+
+
+ inverseTransform
+ inverseTransform
+
+ True/False is to compute inverse of given transformation. Default is false
+ false
+
+
+ defaultValue
+ defaultValue
+
+ Default voxel value
+ 0.0
+
+
+
+
+
+
+
+
+
+ gridSpacing
+ gridSpacing
+
+ Add warped grid to output image to help show the deformation that occured with specified spacing. A spacing of 0 in a dimension indicates that grid lines should be rendered to fall exactly (i.e. do not allow displacements off that plane). This is useful for makeing a 2D image of grid lines from the 3D space
+
+
+
+
+
+
+
+ numberOfThreads
+ numberOfThreads
+
+ Explicitly specify the maximum number of threads to use.
+ -1
+
+
+
diff --git a/Docs/_extracli/BRAINSResize.xml b/Docs/_extracli/BRAINSResize.xml
new file mode 100644
index 00000000000..344010ca942
--- /dev/null
+++ b/Docs/_extracli/BRAINSResize.xml
@@ -0,0 +1,68 @@
+
+
+ Registration
+ Resize Image (BRAINS)
+
+
+This program is useful for downsampling an image by a constant scale factor.
+
+ 5.4.0
+
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ This tool was developed by Hans Johnson.
+ The development of this tool was supported by funding from grants NS050568 and NS40068 from the National Institute of Neurological Disorders and Stroke and grants MH31593, MH40856, from the National Institute of Mental Health.
+
+
+
+ Parameters for specifying the image to warp and resulting image space
+
+
+ inputVolume
+ inputVolume
+ Image To Scale
+
+ input
+
+
+
+
+
+ Resulting scaled image parameters
+
+
+ outputVolume
+ outputVolume
+ Resulting scaled image
+
+ output
+
+
+
+ pixelType
+ pixelType
+
+ Specifies the pixel type for the input/output images. The "binary" pixel type uses a modified algorithm whereby the image is read in as unsigned char, a signed distance map is created, signed distance map is resampled, and then a thresholded image of type unsigned char is written to disk.
+ float
+ float
+ short
+ ushort
+ int
+ uint
+ uchar
+ binary
+
+
+
+
+
+ Parameters used to define the scaling of the output image
+
+
+ scaleFactor
+ scaleFactor
+
+ The scale factor for the image spacing.
+ 2.0
+
+
+
diff --git a/Docs/_extracli/BRAINSStripRotation.xml b/Docs/_extracli/BRAINSStripRotation.xml
new file mode 100644
index 00000000000..d701a3e2c73
--- /dev/null
+++ b/Docs/_extracli/BRAINSStripRotation.xml
@@ -0,0 +1,35 @@
+
+
+ Utilities
+ BRAINS Strip Rotation
+ Read an Image, write out same image with identity rotation matrix plus an ITK transform file
+ 5.4.0
+
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Kent WIlliams
+
+
+
+ inputVolume
+ inputVolume
+ Image To Warp
+
+ input
+
+
+ outputVolume
+ outputVolume
+ Resulting deformed image
+
+ output
+
+
+ transform
+ transform
+
+ Filename for the transform file
+ output
+
+
+
+
diff --git a/Docs/_extracli/BRAINSTransformConvert.xml b/Docs/_extracli/BRAINSTransformConvert.xml
new file mode 100644
index 00000000000..b687ae97734
--- /dev/null
+++ b/Docs/_extracli/BRAINSTransformConvert.xml
@@ -0,0 +1,68 @@
+
+
+ Utilities.BRAINS
+ BRAINS Transform Convert
+ Convert ITK transforms to higher order transforms
+ 5.4.0
+ A utility to convert between transform file formats.
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Hans J. Johnson,Kent Williams, Ali Ghayoor
+
+
+
+
+
+ Input/output parameters
+
+ input
+ inputTransform
+
+
+ --inputTransform
+
+
+ input
+ referenceVolume
+
+ --referenceVolume
+
+
+
+ outputTransformType
+ --outputTransformType
+ The target transformation type. Must be conversion-compatible with the input transform type
+ Affine
+ Affine
+ VersorRigid
+ ScaleVersor
+ ScaleSkewVersor
+ DisplacementField
+ Same
+
+
+
+ outputPrecisionType
+ --outputPrecisionType
+ Precision type of the output transform. It can be either single precision or double precision
+ double
+ double
+ float
+
+
+
+ output
+ displacementVolume
+
+ --displacementVolume
+
+
+ output
+ outputTransform
+
+
+ --outputTransform
+
+
+
+
+
diff --git a/Docs/_extracli/DWIConvert.xml b/Docs/_extracli/DWIConvert.xml
new file mode 100644
index 00000000000..372343bd348
--- /dev/null
+++ b/Docs/_extracli/DWIConvert.xml
@@ -0,0 +1,184 @@
+
+
+ Diffusion.Import and Export
+ Diffusion-weighted DICOM Import (DWIConvert)
+
+ 5.4.0
+ http://wiki.slicer.org/slicerWiki/index.php/Documentation/4.5/Modules/DWIConverter
+ Apache License Version 2.0
+ Hans Johnson (UIowa), Vince Magnotta (UIowa) Joy Matsui (UIowa), Kent Williams (UIowa), Mark Scully (Uiowa), Xiaodong Tao (GE)
+
+
+
+
+
+
+ conversionMode
+ --conversionMode
+
+ DicomToNrrd
+ DicomToNrrd
+ DicomToFSL
+ NrrdToFSL
+ FSLToNrrd
+
+
+ inputVolume
+ --inputVolume
+
+ input
+
+
+
+
+ outputVolume
+ -o
+ --outputVolume
+
+ output
+
+
+
+
+
+
+
+
+ inputDicomDirectory
+ -i
+ --inputDicomDirectory
+
+ input
+
+
+
+
+
+
+
+
+
+ fslNIFTIFile
+ --fslNIFTIFile
+
+ input
+
+
+
+ inputBValues
+ --inputBValues
+
+ input
+
+
+
+ inputBVectors
+ --inputBVectors
+
+ input
+
+
+
+
+
+
+
+
+ outputNiftiFile
+ --outputNiftiFile
+
+ output
+
+
+
+ outputBValues
+ --outputBValues
+
+ output
+
+ .bval)]]>
+
+
+
+ outputBVectors
+ --outputBVectors
+
+ output
+
+ .bvec)]]>
+
+
+
+
+
+
+
+
+ writeProtocolGradientsFile
+ --writeProtocolGradientsFile
+
+
+ false
+
+
+ useIdentityMeaseurementFrame
+ --useIdentityMeaseurementFrame
+
+
+ false
+
+
+ useBMatrixGradientDirections
+ --useBMatrixGradientDirections
+
+
+ false
+
+
+ outputDirectory
+ --outputDirectory
+
+ output
+
+ .
+
+
+ smallGradientThreshold
+ --smallGradientThreshold
+
+
+ 0.2
+
+
+ transpose
+ transposeInputBVectors
+
+ true
+
+
+
+ allowLossyConversion
+ allowLossyConversion
+
+ false
+
+
+
+
+
+
+ gradientVectorFile
+ --gradientVectorFile
+
+ output
+
+
+
+ fMRIOutput
+ --fMRI
+
+ DEPRECATED: No support or testing. Output a NRRD file, but without gradients
+
+
+
+
diff --git a/Docs/_extracli/PerformMetricTest.xml b/Docs/_extracli/PerformMetricTest.xml
new file mode 100644
index 00000000000..cc5ae98d925
--- /dev/null
+++ b/Docs/_extracli/PerformMetricTest.xml
@@ -0,0 +1,71 @@
+
+
+ Registration
+ Metric Test
+ Compare Mattes/MSQ metric value for two input images and a possible input BSpline transform.
+ 5.4.0
+ A utility to compare metric value between two input images.
+ https://www.nitrc.org/svn/brains/BuildScripts/trunk/License.txt
+ Ali Ghayoor
+
+
+
+
+
+ Input parameters
+
+
+ input
+ inputBSplineTransform
+
+ Input transform that is use to warp moving image before metric comparison.
+ inputBSplineTransform
+
+
+
+ input
+ inputFixedImage
+
+ inputFixedImage
+
+
+
+ input
+ inputMovingImage
+
+ inputMovingImage
+
+
+
+
+
+ Metric type and input parameters.
+
+
+ metricType
+ metricType
+
+ Comparison metric type
+ MMI
+ MMI
+ MSE
+
+
+
+ numberOfSamples
+ numberOfSamples
+
+ The number of voxels sampled for metric evaluation.
+ 0
+
+
+
+ numberOfHistogramBins
+ numberOfHistogramBins
+
+ The number of historgram bins when MMI (Mattes) is metric type.
+ 50
+
+
+
+
diff --git a/Docs/cli_module_overview_to_md.xsl b/Docs/cli_module_overview_to_md.xsl
new file mode 100644
index 00000000000..82577f7e8d3
--- /dev/null
+++ b/Docs/cli_module_overview_to_md.xsl
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+ #
+
+
+
+ ## Overview
+
+
+
+
+
+
diff --git a/Docs/cli_module_parameters_to_md.xsl b/Docs/cli_module_parameters_to_md.xsl
new file mode 100644
index 00000000000..93ce7c30aeb
--- /dev/null
+++ b/Docs/cli_module_parameters_to_md.xsl
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+ (*
+
+ *)
+
+
+
+ ## Panels and their use
+
+
+
+ ###
+
+
+ :
+
+
+
+ - **
+
+ **
+
+ :
+
+
+
+ - **
+
+ **
+
+ :
+
+
+
+
+
+
+ ## Contributors
+
+
+
+
+
+ ## Acknowledgements
+
+
+
+
+
+
diff --git a/Docs/conf.py b/Docs/conf.py
index 0c17c88fd10..269af1f6088 100644
--- a/Docs/conf.py
+++ b/Docs/conf.py
@@ -16,11 +16,10 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
+import lxml.etree as ET
import os
import sys
-from recommonmark.parser import CommonMarkParser
-
sys.path.insert(0, os.path.abspath('../Base/Python'))
sys.path.append(os.path.abspath("./_sphinxext"))
@@ -36,12 +35,20 @@
# ones.
extensions = [
'sphinx.ext.autodoc',
- 'workaround_recommonmark_issue_191',
- 'recommonmark',
+ 'myst_parser',
'sphinx_markdown_tables',
- 'notfound.extension', # Show a better 404 page when an invlid address is entered
+ 'notfound.extension', # Show a better 404 page when an invalid address is entered
+]
+
+myst_enable_extensions = [
+ "colon_fence", # Allow code fence using ::: (see https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html#syntax-colon-fence)
+ "linkify", # Allow automatic creation of links from URLs (it is sufficient to write https://google.com instead of )
]
+# Auto-generate header anchors up to level 6, so that it can be referenced like [](file.md#header-anchor).
+# (see https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html#auto-generated-header-anchors)
+myst_heading_anchors = 6
+
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -77,14 +84,13 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_moduledescriptions']
# Set EXCLUDE_DEVELOPER_GUIDE=True environment variable to exclude developer guide.
# It is useful for quicker documentation generation while eiditing user manual.
if os.environ.get('EXCLUDE_API_REFERENCE', False) == 'True':
print("API reference is excluded from documentation.")
- exclude_patterns.append('developer_guide/saferef.rst')
- exclude_patterns.append('developer_guide/teem.rst')
+ exclude_patterns.append('developer_guide/vtkTeem.rst')
exclude_patterns.append('developer_guide/vtkAddon.rst')
exclude_patterns.append('developer_guide/vtkITK.rst')
exclude_patterns.append('developer_guide/slicer.rst')
@@ -207,3 +213,68 @@
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# -- Convert CLI module desriptions into markdown files ----------------
+
+# Each CLI module descriptor XML file is converted to two markdown files
+# in _moduledescriptions subfolder: *Overview.md and *Parameters.md.
+# Overview file contains the module title and description.
+# Parameters file contains detailed description of module inputs and
+# outputs, contributors, and acknowledgements.
+#
+# These md files are included at the top and bottom of each CLI module
+# documentation file (user_guide\modules\*.md). Custom content, such as
+# tutorials, screenshots, etc. can be added in between these includes.
+#
+# A copy of all CLI module descriptor XML files for modules that are
+# not part of the Slicer repository (e.g., BRAINS toolkit) are stored in
+# _extracli subfolder. Content of this folder must be updated manually
+# whenever they are updated in the original location. The process could
+# be automated with some effort, but since these bundled libraries do
+# not change frequently, manual update takes less work overall.
+
+
+# Documentation root folder (folder of this script).
+docsfolder = os.path.dirname(__file__)
+
+# List of folders that contain CLI module descriptor XML files.
+inputpaths = [
+ os.path.join(docsfolder, "../Modules/CLI"),
+ os.path.join(docsfolder, "_extracli"),
+ ]
+
+# List of modules to be excluded from documentation generation
+# (for example, testing modules only).
+excludenames = [
+ 'CLIROITest.xml',
+ 'TestGridTransformRegistration.xml',
+ 'DiffusionTensorTest.xml',
+ ]
+
+# Output folder that contains all generated markdown files.
+outpath = os.path.join(docsfolder, "_moduledescriptions")
+os.makedirs(outpath, exist_ok=True)
+with open(os.path.join(outpath, '_readme_.txt'), 'w') as descriptionfile:
+ descriptionfile.write("Content of this folder is automatically generated by Docs/conf.py from CLI module descriptor XML files\n")
+ descriptionfile.write("during documentation build. The folder can be deleted because it is automatically regenerated when needed.")
+
+
+def _generatemd(dom, docsfolder, outpath, xslt, suffix):
+ """Helper function to create markdown file from CLI module description XML file using XSLT"""
+ xsltpath = os.path.join(docsfolder, xslt)
+ transform = ET.XSLT(ET.parse(xsltpath))
+ content = str(transform(dom))
+ with open(os.path.join(outpath, os.path.splitext(name)[0]+suffix+'.md'), 'w', encoding='utf8') as outfile:
+ outfile.write(content)
+
+
+for inputpath in inputpaths:
+ for root, dirs, files in os.walk(inputpath):
+ for name in files:
+ if name in excludenames:
+ continue
+ if name.endswith((".xml")):
+ print(f"Generating CLI module documentation from {name}")
+ dom = ET.parse(os.path.join(root, name))
+ _generatemd(dom, docsfolder, outpath, "cli_module_overview_to_md.xsl", "Overview")
+ _generatemd(dom, docsfolder, outpath, "cli_module_parameters_to_md.xsl", "Parameters")
diff --git a/Docs/developer_guide/api.md b/Docs/developer_guide/api.md
new file mode 100644
index 00000000000..58c0364b6df
--- /dev/null
+++ b/Docs/developer_guide/api.md
@@ -0,0 +1,80 @@
+# Slicer API
+
+## Tutorials
+
+Check out these [developer tutorials](https://www.slicer.org/wiki/Documentation/Nightly/Training#PerkLab.27s_Slicer_bootcamp_training_materials) to get started with customizing and extending 3D Slicer using Python scripting or C++.
+
+## C++
+
+Majority of Slicer core modules and all basic infrastructure are implemented in C++.
+Documentation of these classes are available at: http://apidocs.slicer.org/master
+
+## Python
+
+### Native Python documentation
+
+Python-style documentation is available for the following packages:
+
+```{toctree}
+:maxdepth: 4
+
+mrml
+slicer
+vtkTeem
+vtkAddon
+vtkITK
+```
+
+% TODO: Add VTK module documentation. Currently disabled because the API file size is too big and it breaks the documentation build.
+
+### Doxygen-style documentation
+
+Slicer core infrastructure is mostly implemented in C++ and it is made available in Python in the `slicer` namespace.
+Documentation of these classes is available at: http://apidocs.slicer.org/master/
+
+This documentation is generated using the Doxygen tool, which uses C++ syntax. The following rules can help in interpreting this documentation for Python:
+
+- Qt classes (class name starts with `q`): for example, [qSlicerMarkupsPlaceWidget](http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html)
+- VTK classes VTK classes (class name starts with `vtk`): for example, [vtkMRMLModelDisplayNode](http://apidocs.slicer.org/master/classvtkMRMLModelDisplayNode.html)
+- Public Types: most commonly used for specifying enumerated values (indicated by `enum` type). These values can be accessed as `slicer.className.typeName`, for example `slicer.qSlicerMarkupsPlaceWidget.HidePlaceMultipleMarkupsOption`
+- Properties: these are values that are accessible as object attributes in Python and can be read and written as `objectName.propertyName`. For example:
+
+ ```python
+ >>> w = slicer.qSlicerMarkupsPlaceWidget()
+ >>> w.deleteAllMarkupsOptionVisible
+ True
+ >>> w.deleteAllMarkupsOptionVisible=False
+ >>> w.deleteAllMarkupsOptionVisible
+ False
+ ```
+
+- Public slots: publicly available methods. Note that `setSomeProperty` methods show up in the documentation but in Python these methods are not available and instead property values can be set using `someProperty = ...`.
+- Signals: signals that can be connected to Python methods
+
+ ```python
+ def someFunction():
+ print("clicked!")
+
+ b = qt.QPushButton("MyButton")
+ b.connect("clicked()", someFunction) # someFunction will be called when the button is clicked
+ b.show()
+ ```
+
+- Public member functions: methods that have `Q_INVOKABLE` keyword next to them are available from Python. `virtual` and `override` specifiers can be ignored.
+
+ - `className` (for Qt classes): constructor, shows the arguments that can be passed when an object is created
+ - `New` (for VTK classes): constructor, never needs an argument
+ - `~className`: destructor, can be ignored, Python calls it automatically when needed
+ - `SafeDownCast` (for VTK classes): not needed for Python, as type conversions are automatic
+
+- Static Public Member Functions: can be accessed as `slicer.className.memberFunctionName(arguments)` for example: `slicer.vtkMRMLModelDisplayNode.GetSliceDisplayModeAsString(0)`
+- Protected Slots, Member Functions, Attributes: for internal use only, not accessible in Python
+- Mapping commonly used data types from C++ documentation to Python:
+
+ - `void` -> Python: if the return value of a method is this type then it means that no value is returned
+ - `someClass*` (object pointer) -> Python: since Python takes care of reference counting, it can be simply interpreted in Python as `someClass`. The called method can modify the object.
+ - `int`, `char`, `short` (with optional `signed` or `unsigned` prefix) -> Python: `int`
+ - `float`, `double` -> Python: `float`
+ - `double[3]` -> Python: initialize a variable before the method call as `point = np.zeros(3)` (or `point = [0.0, 0.0, 0.0]`) and use it as argument in the function
+ - `const char *`, `std::string`, `QString`, `const QString&` -> Python: `str`
+ - `bool` -> Python: `bool`
diff --git a/Docs/developer_guide/api.rst b/Docs/developer_guide/api.rst
deleted file mode 100644
index 0620c2412c1..00000000000
--- a/Docs/developer_guide/api.rst
+++ /dev/null
@@ -1,88 +0,0 @@
-==========
-Slicer API
-==========
-
-Tutorials
----------
-
-Check out these `developer tutorials `_ to get started with customizing and extending 3D Slicer using Python scripting or C++.
-
-C++
----
-
-Majority of Slicer core modules and all basic infrastructure are implemented in C++.
-Documentation of these classes are available at: http://apidocs.slicer.org/master/
-
-Python
-------
-
-Native Python documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Python-style documentation is available for the following packages:
-
-.. toctree::
- :maxdepth: 4
-
- mrml
- saferef
- slicer
- teem
- vtkAddon
- vtkITK
-
-Doxygen-style documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Slicer core infrastructure is mostly implemented in C++ and it is made available in Python in the ``slicer`` namespace.
-Documentation of these classes is available at: http://apidocs.slicer.org/master/
-
-This documentation is generated using the Doxygen tool, which uses C++ syntax. The following rules can help in interpreting this documentation for Python:
-
-- Qt classes (class name starts with ``q``): for example, `qSlicerMarkupsPlaceWidget `_
-- VTK classes VTK classes (class name starts with ``vtk``): for example, `vtkMRMLModelDisplayNode `_
-- Public Types: most commonly used for specifying enumerated values (indicated by ``enum`` type).
- These values can be accessed as ``slicer.className.typeName``, for example ``slicer.qSlicerMarkupsPlaceWidget.HidePlaceMultipleMarkupsOption``
-- Properties: these are values that are accessible as object attributes in Python and can be read and written as ``objectName.propertyName``. For example:
- ::
-
- >>> w = slicer.qSlicerMarkupsPlaceWidget()
- >>> w.deleteAllMarkupsOptionVisible
- True
- >>> w.deleteAllMarkupsOptionVisible=False
- >>> w.deleteAllMarkupsOptionVisible
- False
-
-- Public slots: publicly available methods. Note that ``setSomeProperty`` methods show up in the documentation but in Python these methods
- are not available and instead property values can be set using ``someProperty = ...``.
-
-- Signals: signals that can be connected to Python methods
- ::
-
- def someFunction():
- print("clicked!")
-
- b = qt.QPushButton("MyButton")
- b.connect("clicked()", someFunction) # someFunction will be called when the button is clicked
- b.show()
-
-- Public member functions: methods that have ``Q_INVOKABLE`` keyword next to them are available from Python. ``virtual`` and ``override`` specifiers can be ignored.
-
- - ``className`` (for Qt classes): constructor, shows the arguments that can be passed when an object is created
- - ``New`` (for VTK classes): constructor, never needs an argument
- - ``~className``: destructor, can be ignored, Python calls it automatically when needed
- - ``SafeDownCast`` (for VTK classes): not needed for Python, as type conversions are automatic
-
-- Static Public Member Functions: can be accessed as ``slicer.className.memberFunctionName(arguments)`` for example: ``slicer.vtkMRMLModelDisplayNode.GetSliceDisplayModeAsString(0)``
-- Protected Slots, Member Functions, Attributes: for internal use only, not accessible in Python
-- Mapping commonly used data types from C++ documentation to Python:
-
- - ``void`` -> Python: if the return value of a method is this type then it means that no value is returned
- - ``someClass*`` (object pointer) -> Python: since Python takes care of reference counting, it can be simply interpreted in Python as ``someClass``.
- The called method can modify the object.
- - ``int``, ``char``, ``short`` (with optional ``signed`` or ``unsigned`` prefix) -> Python: ``int``
- - ``float``, ``double`` -> Python: ``float``
- - ``double[3]`` -> Python: initialize a variable before the method call as ``point = np.zeros(3)`` (or ``point = [0.0, 0.0, 0.0]``) and use it as argument in the function
-- ``const char *``, ``std::string``, ``QString``, ``const QString&`` -> Python: ``str``
- - ``bool`` -> Python: ``bool``
-
diff --git a/Docs/developer_guide/authors.md b/Docs/developer_guide/authors.md
new file mode 100644
index 00000000000..7c33aee46ee
--- /dev/null
+++ b/Docs/developer_guide/authors.md
@@ -0,0 +1,2 @@
+```{include} ../../AUTHORS.md
+```
diff --git a/Docs/developer_guide/authors.rst b/Docs/developer_guide/authors.rst
deleted file mode 100644
index 7739272f9d3..00000000000
--- a/Docs/developer_guide/authors.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. include:: ../../AUTHORS.rst
diff --git a/Docs/developer_guide/build_instructions/index.md b/Docs/developer_guide/build_instructions/index.md
new file mode 100644
index 00000000000..925e8dff4c3
--- /dev/null
+++ b/Docs/developer_guide/build_instructions/index.md
@@ -0,0 +1,11 @@
+# Build Instructions
+
+```{toctree}
+:maxdepth: 2
+
+overview.md
+windows.md
+macos.md
+linux.md
+common_errors.md
+```
diff --git a/Docs/developer_guide/build_instructions/index.rst b/Docs/developer_guide/build_instructions/index.rst
deleted file mode 100644
index cd981630ca1..00000000000
--- a/Docs/developer_guide/build_instructions/index.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-
-.. _build_instructions_index:
-
-#####################
-Build Instructions
-#####################
-.. toctree::
-
- overview.md
- windows.md
- macos.md
- linux.md
- common_errors.md
diff --git a/Docs/developer_guide/build_instructions/linux.md b/Docs/developer_guide/build_instructions/linux.md
index 79ed0264387..15eeba299aa 100644
--- a/Docs/developer_guide/build_instructions/linux.md
+++ b/Docs/developer_guide/build_instructions/linux.md
@@ -38,9 +38,11 @@ part of the *superbuild*:
### Debian 10 Stable (Buster)
Install the development tools and the support libraries:
-```
-sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui qt5-default qtmultimedia5-dev
- qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev qtbase5-private-dev libqt5x11extras5-dev libxt-dev libssl-dev
+
+```console
+sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui \
+ qt5-default qtmultimedia5-dev qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev \
+ qtbase5-private-dev libqt5x11extras5-dev libxt-dev libssl-dev
```
### Debian Testing (Bullseye) and Debian 9
@@ -48,54 +50,59 @@ sudo apt update && sudo apt install git subversion build-essential cmake cmake-c
*This option is not suggested since it does not work with standard packages. Debian 9 Qt 5.7 packages will not work with current Slicer 4.11. Checked 2020-08-19. May be possible to build from source or install other packages. In addition, for Debian 9 you also need to build cmake from source as [described here](https://cmake.org/install/) or otherwise get a newer version than is supplied by the distribution.*
Install the development tools and the support libraries:
-```
-sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui qt5-default qtmultimedia5-dev qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev qtbase5-private-dev libqt5x11extras5-dev libxt-dev libssl-dev
+
+```console
+sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui \
+ qt5-default qtmultimedia5-dev qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev \
+ qtbase5-private-dev libqt5x11extras5-dev libxt-dev libssl-dev
```
### Ubuntu 20.04 (Focal Fossa)
Install the development tools and the support libraries:
+
+```console
+sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui \
+ qt5-default qtmultimedia5-dev qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev \
+ qtbase5-private-dev libqt5x11extras5-dev libxt-dev
```
-sudo apt update && sudo apt install git subversion build-essential cmake cmake-curses-gui cmake-qt-gui qt5-default qtmultimedia5-dev qttools5-dev libqt5xmlpatterns5-dev libqt5svg5-dev qtwebengine5-dev qtscript5-dev qtbase5-private-dev libqt5x11extras5-dev libxt-dev
-```
+
### ArchLinux
Install the development tools and the support libraries:
-```
-sudo pacman -S git make patch subversion gcc cmake qt5-base qt5-multimedia qt5-tools qt5-xmlpatterns qt5-svg qt5-webengine qt5-script qt5-x11extras libxt
+```console
+sudo pacman -S git make patch subversion gcc cmake \
+ qt5-base qt5-multimedia qt5-tools qt5-xmlpatterns qt5-svg qt5-webengine qt5-script qt5-x11extras libxt
```
## Checkout Slicer source files
The recommended way to obtain the source code of SLicer is cloning the repository using `git`:
-```
+```console
git clone git://github.com/Slicer/Slicer.git
```
This will create a `Slicer` directory contaning the source code of Slicer.
Hereafter we will call this directory the `source directory`.
-
-
-
Warning
+:::{note} Warning
+:class: warning
-
It is highly recommended to avoid the use of the space
- character in the name of the source directory or any of its parent
- directories.
+It is highly recommended to **avoid** the use of the **space** character in the name of the `source directory` or any of its parent directories.
+:::
After obtaining the source code, we need to set up the development environment:
-```
+```console
cd Slicer
./Utilities/SetupForDevelopment.sh
cd ..
```
-[comment]: <> (TODO: Link to the readthedocs equivalent of https://www.slicer.org/wiki/Documentation/Nightly/Developers/DevelopmentWithGit)
+% TODO: Link to the readthedocs equivalent of https://www.slicer.org/wiki/Documentation/Nightly/Developers/DevelopmentWithGit
## Configure and generate the Slicer build project files
@@ -117,84 +124,51 @@ The following folders will be used in the instructions below:
To obtain a default configuration of the Slicer build project, create the **build** folder and use `cmake`:
-```
+```console
mkdir Slicer-SuperBuild-Debug
cd Slicer-SuperBuild-Debug
cmake ../Slicer
```
+
It is possible to change variables with `cmake`. In the following example we
change the built type (Debug as default) to Release:
-```
+```console
cmake -DCMAKE_BUILD_TYPE:STRING=Release ../Slicer
```
-
-
-
Tip
+:::{admonition} Tip
-
Instead of cmake, one can use ccmake, which provides a
- text-based interface or cmake-gui, which provides a graphical user interface.
- These applications will also provide a list of variables that can be changed.
-
+Instead of `cmake`, one can use `ccmake`, which provides a text-based interface or `cmake-gui`, which provides a graphical user interface. These applications will also provide a list of variables that can be changed.
-
+:::
## Build Slicer
Once the Slicer build project files have been generated, the Slicer project can
be built by running this command in the **build** folder
-```
+```console
make
```
-
-
-
Tip
-
-
Building Slicer will generally take long
- time, particularly on the first build or upon code/configuration changes. To
- help speeding up the process one can use make -j<N>, where N is the
- number of parallel builds. As a rule of thumb, many uses the number of CPU
- threads + 1 as the number of parallel builds.
-
+:::{admonition} Tip
-
+Building Slicer will generally take long time, particularly on the first build or upon code/configuration changes. To help speeding up the process one can use `make -j`, where `` is the number of parallel builds. As a rule of thumb, many uses the `number of CPU threads + 1` as the number of parallel builds.
+:::
-
-
-
Warning
+:::{warning}
-
Increasing the number of parallel builds generally
- increases the memory required for the build process. In the event that the
- required memory exceeds the available memory, the process will either fail or
- start using swap memory, which will make in practice the system to freeze.
+Increasing the number of parallel builds generally increases the memory required for the build process. In the event that the required memory exceeds the available memory, the process will either fail or start using swap memory, which will make in practice the system to freeze.
-
+:::
-
-
-
Tip
-
-
Using parallel builds makes finding compilation errors difficult due to the
- fact that all parallel build processes use the same screen otput, as opposed
- to sequential builds, where the compilation process will stop at the error. A
- common technique to have parallel builds and easily find errors is launch a
- parallel build followed by a sequential build. For the parallel build, it is adviced to run make
- -j<N> -k to have the parallel build keep going as far as
- possible before doing the sequential build withmake
+:::{admonition} Tip
-
+Using parallel builds makes finding compilation errors difficult due to the fact that all parallel build processes use the same screen otput, as opposed to sequential builds, where the compilation process will stop at the error. A common technique to have parallel builds and easily find errors is launch a parallel build followed by a sequential build. For the parallel build, it is adviced to run `make -j -k` to have the parallel build keep going as far as possible before doing the sequential build with `make`.
+:::
## Run Slicer
@@ -202,7 +176,8 @@ After the building process has successfully completed, the executable file to
run Slicer will be located in the **inner-build** folder.
The application can be launched by these commands:
-```
+
+```console
cd Slicer-build
./Slicer`
```
@@ -212,14 +187,16 @@ cd Slicer-build
After building, run the tests in the **inner-build** folder.
Type the following (you can replace 4 by the number of processor cores in the computer):
-```
+
+```console
ctest -j4
```
## Package Slicer
Start a terminal and type the following in the **inner-build** folder:
-```
+
+```console
make package
```
diff --git a/Docs/developer_guide/build_instructions/macos.md b/Docs/developer_guide/build_instructions/macos.md
index 3e227e88ce4..cca771bd2e0 100644
--- a/Docs/developer_guide/build_instructions/macos.md
+++ b/Docs/developer_guide/build_instructions/macos.md
@@ -6,7 +6,7 @@ The prerequisites listed below are required to be able to configure/build/packag
- XCode command line tools must be installed:
-```bash
+```console
xcode-select --install
```
@@ -27,7 +27,7 @@ Check out the code using `git`:
- Clone the github repository
-```bash
+```console
git clone git://github.com/Slicer/Slicer.git
```
@@ -35,7 +35,7 @@ The `Slicer` directory is automatically created after cloning Slicer.
- Setup the development environment:
-```bash
+```console
cd Slicer
./Utilities/SetupForDevelopment.sh
```
@@ -44,7 +44,7 @@ cd Slicer
- Configure using the following commands. By default `CMAKE_BUILD_TYPE` is set to `Debug` (replace `/path/to/Qt` with the real path on your machine where QtSDK is located):
-```bash
+```console
mkdir /opt/s
cd /opt/s
cmake \
@@ -60,7 +60,7 @@ cmake \
- Instead of `cmake`, you can use `ccmake` or `cmake-gui` to visually inspect and edit configure options.
- Using top-level directory name like `/opt/sr` for Release or `/opt/s` for Debug is recommended. If `/opt` does not exist on your machine you need to use sudo for `mkdir` and `chown` in `/opt`.
- [Step-by-step debug instuctions](https://www.slicer.org/wiki/Documentation/Nightly/Developers/Tutorials/Debug_Instructions)
- - Additional configuration options to customize the application are described [here](overview.html#Custom_builds).
+ - Additional configuration options to customize the application are described [here](overview.md#custom-builds).
### General information
@@ -71,7 +71,7 @@ Two projects are generated by either `cmake`, `ccmake` or `cmake-gui`. One of th
*Warning:* An significant amount of disk space is required to compile Slicer in Debug mode (>20GB)
-*Warning:* Some firewalls will block the git protocol. See more information and solution [here](../overview.html#firewall-is-blocking-git-protocol).
+*Warning:* Some firewalls will block the git protocol. See more information and solution [here](common_errors.md#firewall-is-blocking-git-protocol).
## Build Slicer
@@ -79,7 +79,7 @@ After configuration, start the build process in the `/opt/s` directory
- Start a terminal and type the following (you can replace 4 by the number of processor cores in the computer. You can find out the number of available cores by running `sysctl -n hw.ncpu`):
-```bash
+```console
cd ~/opt/s
make -j4
```
@@ -88,7 +88,7 @@ make -j4
Start a terminal and type the following:
-```bash
+```console
/opt/s/Slicer-build/Slicer
```
@@ -98,7 +98,7 @@ After building, run the tests in the `/opt/s/Slicer-build` directory.
Start a terminal and type the following (you can replace 4 by the number of processor cores in the computer):
-```bash
+```console
cd /opt/s/Slicer-build
ctest -j4
```
@@ -109,7 +109,7 @@ ctest -j4
Start a terminal and type the following:
-```bash
+```console
cd /opt/s
cd Slicer-build
make package
@@ -119,13 +119,13 @@ make package
When using the `-j` option, the build will continue past the source of the first error. If the build fails and you don't see what failed, rebuild without the `-j` option. Or, to speed up this process build first with the `-j` and `-k` options and then run plain make. The `-k` option will make the build keep going so that any code that can be compiled independent of the error will be completed and the second make will reach the error condition more efficiently. To debug the error you can pipe the output of the make command to an external log file like this:
-```bash
+```console
make -j10 -k; make 2>&1 | tee /tmp/build.log
```
In some cases when the build fails without explicitly stating what went wrong it's useful to look at error logs created during building of individual packages bundled with Slicer. Running the following command in the `/opt/s` folder
-```bash
+```console
find . -name "*rr*.log" | xargs ls -ltur
```
@@ -136,14 +136,14 @@ will list such error logs in ordered by the time of latest access. The log that
If the XCode command line tools are not properly set up on macOS, PCRE could fail to build in the Superbuild process with the errors like below:
-```bash
+```console
configure: error: in `/Users/fedorov/local/Slicer4-Debug/PCRE-build':
configure: error: cannot run C compiled programs.
```
To install XCode command line tools, use the following command from the terminal:
-```bash
+```console
xcode-select --install
```
@@ -178,7 +178,7 @@ See [Relevant issue that's tracking this error](https://github.com/Slicer/Slicer
## Update Homebrew packages
-Slicer can be installed with a single command using [Homebrew](https://brew.sh/), as described in the [installation documentation](../../user_guide/getting_started.html#installing-using-homebrew).
+Slicer can be installed with a single command using [Homebrew](https://brew.sh/), as described in the [installation documentation](../../user_guide/getting_started.md#installing-using-homebrew).
Specifically, the `cask` extension is used, which allows management of graphical applications from the command line.
Casks are Ruby files (`.rb`) describing the metadata necessary to download, check and install the package.
diff --git a/Docs/developer_guide/build_instructions/overview.md b/Docs/developer_guide/build_instructions/overview.md
index 9c4805f1e2c..5564e222d5d 100644
--- a/Docs/developer_guide/build_instructions/overview.md
+++ b/Docs/developer_guide/build_instructions/overview.md
@@ -2,7 +2,7 @@
Building Slicer is the process of obtaining a copy of the source code of the project and use tools, such as compilers, project generators and build systems, to create binary libraries and executables. Slicer documentation is also generated in this process.
-Users of Slicer application and extensions do not need to build the application and they can download and install pre-built packages instead. Python scripting and development of new Slicer modules in Python does not require building the application either. Only software developers interested in developing Slicer [modules](../user_guide/modules/index.html) in C++ language or contributing to the development of Slicer core must build the application.
+Users of Slicer application and extensions do not need to build the application and they can download and install pre-built packages instead. Python scripting and development of new Slicer modules in Python does not require building the application either. Only software developers interested in developing Slicer [modules](../../user_guide/modules/index.md) in C++ language or contributing to the development of Slicer core must build the application.
Slicer is based on a *superbuild* architecture. This means that the in the building process, most of the dependencies of Slicer will be downloaded in local directories (within the Slicer build directory) and will be configured, built and installed locally, before Slicer itself is built. This helps reducing the complexity for developers.
diff --git a/Docs/developer_guide/contributing.md b/Docs/developer_guide/contributing.md
new file mode 100644
index 00000000000..ef6daa82aaf
--- /dev/null
+++ b/Docs/developer_guide/contributing.md
@@ -0,0 +1,2 @@
+```{include} ../../CONTRIBUTING.md
+```
diff --git a/Docs/developer_guide/contributing.rst b/Docs/developer_guide/contributing.rst
deleted file mode 100644
index f2c987c718a..00000000000
--- a/Docs/developer_guide/contributing.rst
+++ /dev/null
@@ -1 +0,0 @@
-.. mdinclude:: ../../CONTRIBUTING.md
diff --git a/Docs/developer_guide/index.md b/Docs/developer_guide/index.md
new file mode 100644
index 00000000000..2b7290b1aa6
--- /dev/null
+++ b/Docs/developer_guide/index.md
@@ -0,0 +1,15 @@
+# Developer Guide
+
+```{toctree}
+:maxdepth: 3
+
+api
+mrml_overview
+modules/index
+extensions
+python_faq
+script_repository
+build_instructions/index
+contributing
+authors
+```
diff --git a/Docs/developer_guide/index.rst b/Docs/developer_guide/index.rst
deleted file mode 100644
index 195e2b5f30c..00000000000
--- a/Docs/developer_guide/index.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-.. _developer_guide_index:
-
-###############
-Developer Guide
-###############
-
-.. toctree::
- :maxdepth: 2
-
- api
- mrml_overview
- modules/index.rst
- extensions
- python_faq
- script_repository
- build_instructions/index.rst
- contributing
- authors
diff --git a/Docs/developer_guide/modules/dicom.md b/Docs/developer_guide/modules/dicom.md
index 4540ce1203b..8cc17f426ba 100644
--- a/Docs/developer_guide/modules/dicom.md
+++ b/Docs/developer_guide/modules/dicom.md
@@ -20,7 +20,7 @@ The existing two rules can be used as examples: the [default](https://github.com
## Plugin architecture
-There are many different kind of DICOM information objects and import/export of all of them would not be feasible to implement in the Slicer core (see more information in the [DICOM module user manual](../../user_guide/modules/dicom.html#plugin-architecture)). Therefore, extensions can implement custom importer/exporter clases based on DICOMLib.DICOMPlugin class and add them to slicer.modules.dicomPlugins dictionary object. The DICOM module uses these plugins to determine list of loadable and exportable items.
+There are many different kind of DICOM information objects and import/export of all of them would not be feasible to implement in the Slicer core (see more information in the [DICOM module user manual](../../user_guide/modules/dicom.md#dicom-plugins)). Therefore, extensions can implement custom importer/exporter clases based on DICOMLib.DICOMPlugin class and add them to slicer.modules.dicomPlugins dictionary object. The DICOM module uses these plugins to determine list of loadable and exportable items.
## References
@@ -28,4 +28,4 @@ See the [CTK web site](http://commontk.org) for more information on the internal
## Examples
-Examples for common DICOM operations are provided in the [script repository](../script_repository.html#dicom).
+Examples for common DICOM operations are provided in the [script repository](../script_repository.md#dicom).
diff --git a/Docs/developer_guide/modules/index.rst b/Docs/developer_guide/modules/index.md
similarity index 61%
rename from Docs/developer_guide/modules/index.rst
rename to Docs/developer_guide/modules/index.md
index 339bf94b151..2c4674bfe98 100644
--- a/Docs/developer_guide/modules/index.rst
+++ b/Docs/developer_guide/modules/index.md
@@ -1,17 +1,14 @@
-.. _developers_modules_index:
-
-#########
-Modules API
-#########
+# Modules API
Modules usually interact with each other only indirectly, by making changes and observing changes in the MRML scene. However, modules can also directly use functions offered by other modules, by calling its logic functions. Examples and notes for using specific modules are provided below.
-.. toctree::
- :maxdepth: 2
+```{toctree}
+:maxdepth: 2
- dicom
- markups
- segmenteditor
- transforms
- volumerendering
- volumes
+dicom
+markups
+segmenteditor
+transforms
+volumerendering
+volumes
+```
diff --git a/Docs/developer_guide/modules/markups.md b/Docs/developer_guide/modules/markups.md
index 499f6ffa9cf..0063f6a98e3 100644
--- a/Docs/developer_guide/modules/markups.md
+++ b/Docs/developer_guide/modules/markups.md
@@ -54,4 +54,4 @@ Then comes the fiducials, one per line, for example:
## Examples
-Examples for common operations on transform are provided in the [script repository](../script_repository.html#markups).
+Examples for common operations on transform are provided in the [script repository](../script_repository.md#markups).
diff --git a/Docs/developer_guide/modules/segmenteditor.md b/Docs/developer_guide/modules/segmenteditor.md
index a08b24b6202..f70beff3544 100644
--- a/Docs/developer_guide/modules/segmenteditor.md
+++ b/Docs/developer_guide/modules/segmenteditor.md
@@ -98,4 +98,4 @@ Common parameters must be set using `setCommonParameter` method (others can be s
## Examples
-Examples for common operations on segmentations are provided in the [script repository](../script_repository.html#segmentations).
+Examples for common operations on segmentations are provided in the [script repository](../script_repository.md#segmentations).
diff --git a/Docs/developer_guide/modules/transforms.md b/Docs/developer_guide/modules/transforms.md
index 0a26ad6d7c9..f9b8a247e6b 100644
--- a/Docs/developer_guide/modules/transforms.md
+++ b/Docs/developer_guide/modules/transforms.md
@@ -23,4 +23,4 @@ When a transform node is observed by a transformable node, [vtkMRMLTransformable
## Examples
-Examples for common operations on transform are provided in the [script repository](../script_repository.html#transforms).
+Examples for common operations on transform are provided in the [script repository](../script_repository.md#transforms).
diff --git a/Docs/developer_guide/modules/volumerendering.md b/Docs/developer_guide/modules/volumerendering.md
index c0d5b9819c8..f667a112d27 100644
--- a/Docs/developer_guide/modules/volumerendering.md
+++ b/Docs/developer_guide/modules/volumerendering.md
@@ -28,4 +28,4 @@ Example:
## Examples
-Examples for common operations on transform are provided in the [script repository](../script_repository.html#volumes).
+Examples for common operations on transform are provided in the [script repository](../script_repository.md#volumes).
diff --git a/Docs/developer_guide/modules/volumes.md b/Docs/developer_guide/modules/volumes.md
index c5798669196..a02fd72c128 100644
--- a/Docs/developer_guide/modules/volumes.md
+++ b/Docs/developer_guide/modules/volumes.md
@@ -2,4 +2,4 @@
## Examples
-Examples for common operations on volumes are provided in the [script repository](../script_repository.html#volumes).
+Examples for common operations on volumes are provided in the [script repository](../script_repository.md#volumes).
diff --git a/Docs/developer_guide/mrml.rst b/Docs/developer_guide/mrml.md
similarity index 76%
rename from Docs/developer_guide/mrml.rst
rename to Docs/developer_guide/mrml.md
index 6e25d93acda..4694ac74bd3 100644
--- a/Docs/developer_guide/mrml.rst
+++ b/Docs/developer_guide/mrml.md
@@ -1,8 +1,9 @@
-mrml module
-===========
+# mrml module
+```{eval-rst}
.. automodule:: mrml
:members:
:undoc-members:
:show-inheritance:
:imported-members:
+```
diff --git a/Docs/developer_guide/saferef.rst b/Docs/developer_guide/saferef.rst
deleted file mode 100644
index 983ad897d5e..00000000000
--- a/Docs/developer_guide/saferef.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-saferef module
-==============
-
-.. automodule:: saferef
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/Docs/developer_guide/script_repository.md b/Docs/developer_guide/script_repository.md
new file mode 100644
index 00000000000..1f26bcd723a
--- /dev/null
+++ b/Docs/developer_guide/script_repository.md
@@ -0,0 +1,45 @@
+# Script repository
+
+:::{note}
+
+**Usage**: Copy-paste the code lines displayed below or the linked `.py` file contents into Python console in Slicer. Or save them to a `.py` file and run them using `execfile`.
+
+To run a Python code snippet automatically at each application startup, add it to the [.slicerrc.py file](../user_guide/settings.md#application-startup-file).
+
+:::
+
+```{include} script_repository/gui.md
+```
+
+```{include} script_repository/dicom.md
+```
+
+```{include} script_repository/markups.md
+```
+
+```{include} script_repository/models.md
+```
+
+```{include} script_repository/plots.md
+```
+
+```{include} script_repository/screencapture.md
+```
+
+```{include} script_repository/segmentations.md
+```
+
+```{include} script_repository/sequences.md
+```
+
+```{include} script_repository/subjecthierarchy.md
+```
+
+```{include} script_repository/tractography.md
+```
+
+```{include} script_repository/transforms.md
+```
+
+```{include} script_repository/volumes.md
+```
diff --git a/Docs/developer_guide/script_repository.rst b/Docs/developer_guide/script_repository.rst
deleted file mode 100644
index f5fe9b08077..00000000000
--- a/Docs/developer_guide/script_repository.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-Script repository
-=================
-
-.. note::
-
- **Usage**: Copy-paste the code lines displayed below or the linked ``.py`` file contents
- into Python console in Slicer. Or save them to a ``.py`` file and run them using ``execfile``.
-
-.. include:: script_repository/gui.rst
-
-.. include:: script_repository/dicom.rst
-
-.. include:: script_repository/markups.rst
-
-.. include:: script_repository/models.rst
-
-.. include:: script_repository/plots.rst
-
-.. include:: script_repository/screencapture.rst
-
-.. include:: script_repository/segmentations.rst
-
-.. include:: script_repository/sequences.rst
-
-.. include:: script_repository/subjecthierarchy.rst
-
-.. include:: script_repository/tractography.rst
-
-.. include:: script_repository/transforms.rst
-
-.. include:: script_repository/volumes.rst
diff --git a/Docs/developer_guide/script_repository/dicom.md b/Docs/developer_guide/script_repository/dicom.md
new file mode 100644
index 00000000000..2b327d16626
--- /dev/null
+++ b/Docs/developer_guide/script_repository/dicom.md
@@ -0,0 +1,255 @@
+## DICOM
+
+### Load DICOM files into the scene from a folder
+
+This code loads all DICOM objects into the scene from a file folder. All the registered plugins are evaluated and the one with the highest confidence will be used to load the data. Files are imported into a temporary DICOM database, so the current Slicer DICOM database is not impacted.
+
+```python
+dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files
+loadedNodeIDs = [] # this list will contain the list of all loaded node IDs
+
+from DICOMLib import DICOMUtils
+with DICOMUtils.TemporaryDICOMDatabase() as db:
+ DICOMUtils.importDicom(dicomDataDir, db)
+ patientUIDs = db.patients()
+ for patientUID in patientUIDs:
+ loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))
+```
+
+### Import DICOM files into the application's DICOM database
+
+This code snippet uses Slicer DICOM browser built-in indexer to import DICOM files into the database. Images are not loaded into the scene, but they show up in the DICOM browser. After import, data sets can be loaded using DICOMUtils functions (e.g., loadPatientByUID) - see above for an example.
+
+```python
+# instantiate a new DICOM browser
+slicer.util.selectModule("DICOM")
+dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser
+# use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)
+dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)
+# wait for import to finish before proceeding (optional, if removed then import runs in the background)
+dicomBrowser.waitForImportFinished()
+```
+
+### Import DICOM files using DICOMweb
+
+Download and import DICOM data set using DICOMweb from [Kheops](https://kheops.online/), Google Health API, etc.
+
+How to obtain accessToken:
+
+- Google Cloud: Execute `gcloud auth print-access-token` once you have logged in
+- Kheops: create an album, create a sharing link (something like `https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf`), the token is the string after the last slash (`TfYXwbKAW7JYbAgZ7MyISf`).
+
+```python
+slicer.util.selectModule("DICOM") # ensure DICOM database is initialized and
+slicer.app.processEvents()
+from DICOMLib import DICOMUtils
+DICOMUtils.importFromDICOMWeb(
+ dicomWebEndpoint="http://demo.kheops.online/api",
+ studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",
+ accessToken="TfYXwbKAW7JYbAgZ7MyISf")
+```
+
+### Access top level tags of DICOM images imported into Slicer
+
+For example, to print the first patient's first study's first series' "0020,0032" field:
+
+```python
+db = slicer.dicomDatabase
+patientList = db.patients()
+studyList = db.studiesForPatient(patientList[0])
+seriesList = db.seriesForStudy(studyList[0])
+fileList = db.filesForSeries(seriesList[0])
+# Note, fileValue accesses the database of cached top level tags
+# (nested tags are not included)
+print(db.fileValue(fileList[0], "0020,0032"))
+# Get tag group,number from dicom dictionary
+import pydicom as dicom
+tagName = "StudyDate"
+tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(" ","")
+print(db.fileValue(fileList[0], tagStr))
+```
+
+### Access DICOM tags nested in a sequence
+
+```python
+db = slicer.dicomDatabase
+patientList = db.patients()
+studyList = db.studiesForPatient(patientList[0])
+seriesList = db.seriesForStudy(studyList[0])
+fileList = db.filesForSeries(seriesList[0])
+# Use pydicom to access the full header, which requires
+# re-reading the dataset instead of using the database cache
+import pydicom
+pydicom.dcmread(fileList[0])
+ds.CTExposureSequence[0].ExposureModulationType
+```
+
+### Access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume
+
+```python
+volumeName = "2: ENT IMRT"
+n = slicer.util.getNode(volumeName)
+instUids = n.GetAttribute("DICOM.instanceUIDs").split()
+filename = slicer.dicomDatabase.fileForInstance(instUids[0])
+print(slicer.dicomDatabase.fileValue(filename, "0018,5100"))
+```
+
+### Access tag of an item in the Subject Hierachy tree
+
+For example, get the content time tag of a structure set:
+
+```python
+rtStructName = "3: RTSTRUCT: PROS"
+rtStructNode = slicer.util.getNode(rtStructName)
+shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)
+ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, "DICOM.ReferencedInstanceUIDs").split()
+filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])
+print(slicer.dicomDatabase.fileValue(filename, "0008,0033"))
+```
+
+### Get path and filename of a loaded DICOM volume
+
+```python
+def pathFromNode(node):
+ storageNode = node.GetStorageNode()
+ if storageNode is not None: # loaded via drag-drop
+ filepath = storageNode.GetFullNameFromFileName()
+ else: # Loaded via DICOM browser
+ instanceUIDs = node.GetAttribute("DICOM.instanceUIDs").split()
+ filepath = slicer.dicomDatabase.fileForInstance(instUids[0])
+ return filepath
+
+# Example:
+node = slicer.util.getNode("volume1")
+path = self.pathFromNode(node)
+print("DICOM path=%s" % path)
+```
+
+### Convert DICOM to NRRD on the command line
+
+```console
+/Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm'); slicer.util.saveNode(node, "/tmp/output.nrrd"); exit()"
+```
+
+The same can be done on windows by using the top level Slicer.exe. Be sure to use forward slashes in the pathnames within quotes on the command line.
+
+### Export a volume to DICOM file format
+
+```python
+volumeNode = getNode("CTChest")
+outputFolder = "c:/tmp/dicom-output"
+
+# Create patient and study and put the volume under the study
+shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")
+studyItemID = shNode.CreateStudyItem(patientItemID, "test study")
+volumeShItemID = shNode.GetItemByDataNode(volumeNode)
+shNode.SetItemParent(volumeShItemID, studyItemID)
+
+import DICOMScalarVolumePlugin
+exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
+exportables = exporter.examineForExport(volumeShItemID)
+for exp in exportables:
+ exp.directory = outputFolder
+
+exporter.export(exportables)
+```
+
+### Export a segmentation to DICOM segmentation object
+
+```python
+segmentationNode = ...
+referenceVolumeNode = ...
+outputFolder = "c:/tmp/dicom-output"
+
+# Associate segmentation node with a reference volume node
+shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)
+studyShItem = shNode.GetItemParent(referenceVolumeShItem)
+segmentationShItem = shNode.GetItemByDataNode(segmentationNode)
+shNode.SetItemParent(segmentationShItem, studyShItem)
+
+# Export to DICOM
+import DICOMSegmentationPlugin
+exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()
+exportables = exporter.examineForExport(segmentationShItem)
+for exp in exportables:
+ exp.directory = outputFolder
+
+exporter.export(exportables)
+```
+
+### Customize table columns in DICOM browser
+
+Documentation of methods for changing DICOM browser columns: https://github.com/commontk/CTK/blob/master/Libs/DICOM/Core/ctkDICOMDatabase.h#L354-L375
+
+```python
+# Get browser and database
+dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser
+dicomDatabase = dicomBrowser.database()
+
+# Print list of available columns
+print(dicomDatabase.patientFieldNames)
+print(dicomDatabase.studyFieldNames)
+print(dicomDatabase.seriesFieldNames)
+
+# Change column order
+dicomDatabase.setWeightForField("Series", "SeriesDescription", 7)
+dicomDatabase.setWeightForField("Studies", "StudyDescription", 6)
+# Change column visibility
+dicomDatabase.setVisibilityForField("Patients", "PatientsBirthDate", False)
+dicomDatabase.setVisibilityForField("Patients", "PatientsComments", True)
+dicomDatabase.setWeightForField("Patients", "PatientsComments", 8)
+# Change column name
+dicomDatabase.setDisplayedNameForField("Series", "DisplayedCount", "Number of images")
+# Change column width to manual
+dicomDatabase.setFormatForField("Series", "SeriesDescription", '{"resizeMode":"interactive"}')
+# Customize table manager in DICOM browser
+dicomTableManager = dicomBrowser.dicomTableManager()
+dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection
+dicomTableManager.autoSelectSeries = False
+
+# Force database views update
+dicomDatabase.closeDatabase()
+dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)
+```
+
+### Query and retrieve data from a PACS using classic DIMSE DICOM networking
+
+```python
+# Query
+dicomQuery = ctk.ctkDICOMQuery()
+dicomQuery.callingAETitle = "SLICER"
+dicomQuery.calledAETitle = "ANYAE"
+dicomQuery.host = "dicomserver.co.uk"
+dicomQuery.port = 11112
+dicomQuery.preferCGET = True
+dicomQuery.filters = {"Name":"Anon", "Modalities":"MR"}
+# temporary in-memory database for storing query results
+tempDb = ctk.ctkDICOMDatabase()
+tempDb.openDatabase("")
+dicomQuery.query(tempDb)
+
+# Retrieve
+dicomRetrieve = ctk.ctkDICOMRetrieve()
+dicomRetrieve.callingAETitle = dicomQuery.callingAETitle
+dicomRetrieve.calledAETitle = dicomQuery.calledAETitle
+dicomRetrieve.host = dicomQuery.host
+dicomRetrieve.port = dicomQuery.port
+dicomRetrieve.setMoveDestinationAETitle("SLICER");
+dicomRetrieve.setDatabase(slicer.dicomDatabase)
+for study in dicomQuery.studyInstanceUIDQueried:
+ print(f"ctkDICOMRetrieveTest2: Retrieving {study}")
+ slicer.app.processEvents()
+ if dicomQuery.preferCGET:
+ success = dicomRetrieve.getStudy(study)
+ else:
+ success = dicomRetrieve.moveStudy(study)
+ print(f" - {'success' if success else 'failed'}")
+slicer.dicomDatabase.updateDisplayedFields()
+```
+
+### Convert RT structure set to labelmap NRRD files
+
+[SlicerRT batch processing](https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing) to batch convert RT structure sets to labelmap NRRD files.
diff --git a/Docs/developer_guide/script_repository/dicom.rst b/Docs/developer_guide/script_repository/dicom.rst
deleted file mode 100644
index f5ad45e293a..00000000000
--- a/Docs/developer_guide/script_repository/dicom.rst
+++ /dev/null
@@ -1,270 +0,0 @@
-DICOM
-~~~~~
-
-Load DICOM files into the scene from a folder
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code loads all DICOM objects into the scene from a file folder. All the registered plugins are evaluated and the one with the highest confidence will be used to load the data. Files are imported into a temporary DICOM database, so the current Slicer DICOM database is not impacted.
-
-.. code-block:: python
-
- dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files
- loadedNodeIDs = [] # this list will contain the list of all loaded node IDs
-
- from DICOMLib import DICOMUtils
- with DICOMUtils.TemporaryDICOMDatabase() as db:
- DICOMUtils.importDicom(dicomDataDir, db)
- patientUIDs = db.patients()
- for patientUID in patientUIDs:
- loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))
-
-Import DICOM files into the application's DICOM database
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code snippet uses Slicer DICOM browser built-in indexer to import DICOM files into the database. Images are not loaded into the scene, but they show up in the DICOM browser. After import, data sets can be loaded using DICOMUtils functions (e.g., loadPatientByUID) - see above for an example.
-
-.. code-block:: python
-
- # instantiate a new DICOM browser
- slicer.util.selectModule("DICOM")
- dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser
- # use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)
- dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)
- # wait for import to finish before proceeding (optional, if removed then import runs in the background)
- dicomBrowser.waitForImportFinished()
-
-Import DICOM files using DICOMweb
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Download and import DICOM data set using DICOMweb from `Kheops `__, Google Health API, etc.
-
-How to obtain accessToken:
-
-- Google Cloud: Execute ``gcloud auth print-access-token`` once you have logged in
-- Kheops: create an album, create a sharing link (somethin like ```https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf`` `__), the token is the string after the last slash
-
-.. code-block:: python
-
- slicer.util.selectModule("DICOM") # ensure DICOM database is initialized and
- slicer.app.processEvents()
- from DICOMLib import DICOMUtils
- DICOMUtils.importFromDICOMWeb(
- dicomWebEndpoint="http://demo.kheops.online/api",
- studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",
- accessToken="TfYXwbKAW7JYbAgZ7MyISf")
-
-Access top level tags of DICOM images imported into Slicer
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For example, to print the first patient's first study's first series' "0020,0032" field:
-
-.. code-block:: python
-
- db = slicer.dicomDatabase
- patientList = db.patients()
- studyList = db.studiesForPatient(patientList[0])
- seriesList = db.seriesForStudy(studyList[0])
- fileList = db.filesForSeries(seriesList[0])
- # Note, fileValue accesses the database of cached top level tags
- # (nested tags are not included)
- print(db.fileValue(fileList[0], "0020,0032"))
- # Get tag group,number from dicom dictionary
- import pydicom as dicom
- tagName = "StudyDate"
- tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(" ","")
- print(db.fileValue(fileList[0], tagStr))
-
-Access DICOM tags nested in a sequence
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- db = slicer.dicomDatabase
- patientList = db.patients()
- studyList = db.studiesForPatient(patientList[0])
- seriesList = db.seriesForStudy(studyList[0])
- fileList = db.filesForSeries(seriesList[0])
- # Use pydicom to access the full header, which requires
- # re-reading the dataset instead of using the database cache
- import pydicom
- pydicom.dcmread(fileList[0])
- ds.CTExposureSequence[0].ExposureModulationType
-
-Access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- volumeName = "2: ENT IMRT"
- n = slicer.util.getNode(volumeName)
- instUids = n.GetAttribute("DICOM.instanceUIDs").split()
- filename = slicer.dicomDatabase.fileForInstance(instUids[0])
- print(slicer.dicomDatabase.fileValue(filename, "0018,5100"))
-
-Access tag of an item in the Subject Hierachy tree
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For example, get the content time tag of a structure set:
-
-.. code-block:: python
-
- rtStructName = "3: RTSTRUCT: PROS"
- rtStructNode = slicer.util.getNode(rtStructName)
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)
- ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, "DICOM.ReferencedInstanceUIDs").split()
- filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])
- print(slicer.dicomDatabase.fileValue(filename, "0008,0033"))
-
-Get path and filename of a loaded DICOM volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- def pathFromNode(node):
- storageNode = node.GetStorageNode()
- if storageNode is not None: # loaded via drag-drop
- filepath = storageNode.GetFullNameFromFileName()
- else: # Loaded via DICOM browser
- instanceUIDs = node.GetAttribute("DICOM.instanceUIDs").split()
- filepath = slicer.dicomDatabase.fileForInstance(instUids[0])
- return filepath
-
- # Example:
- node = slicer.util.getNode("volume1")
- path = self.pathFromNode(node)
- print("DICOM path=%s" % path)
-
-Convert DICOM to NRRD on the command line
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-::
-
- /Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm'); slicer.util.saveNode(node, "/tmp/output.nrrd"); exit()"
-
-The same can be done on windows by using the top level Slicer.exe. Be sure to use forward slashes in the pathnames within quotes on the command line.
-
-Export a volume to DICOM file format
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- volumeNode = getNode("CTChest")
- outputFolder = "c:/tmp/dicom-output"
-
- # Create patient and study and put the volume under the study
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")
- studyItemID = shNode.CreateStudyItem(patientItemID, "test study")
- volumeShItemID = shNode.GetItemByDataNode(volumeNode)
- shNode.SetItemParent(volumeShItemID, studyItemID)
-
- import DICOMScalarVolumePlugin
- exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()
- exportables = exporter.examineForExport(volumeShItemID)
- for exp in exportables:
- exp.directory = outputFolder
-
- exporter.export(exportables)
-
-Export a segmentation to DICOM segmentation object
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- segmentationNode = ...
- referenceVolumeNode = ...
- outputFolder = "c:/tmp/dicom-output"
-
- # Associate segmentation node with a reference volume node
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)
- studyShItem = shNode.GetItemParent(referenceVolumeShItem)
- segmentationShItem = shNode.GetItemByDataNode(segmentationNode)
- shNode.SetItemParent(segmentationShItem, studyShItem)
-
- # Export to DICOM
- import DICOMSegmentationPlugin
- exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()
- exportables = exporter.examineForExport(segmentationShItem)
- for exp in exportables:
- exp.directory = outputFolder
-
- exporter.export(exportables)
-
-Customize table columns in DICOM browser
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Documentation of methods for changing DICOM browser columns: https://github.com/commontk/CTK/blob/master/Libs/DICOM/Core/ctkDICOMDatabase.h#L354-L375
-
-.. code-block:: python
-
- # Get browser and database
- dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser
- dicomDatabase = dicomBrowser.database()
-
- # Print list of available columns
- print(dicomDatabase.patientFieldNames)
- print(dicomDatabase.studyFieldNames)
- print(dicomDatabase.seriesFieldNames)
-
- # Change column order
- dicomDatabase.setWeightForField("Series", "SeriesDescription", 7)
- dicomDatabase.setWeightForField("Studies", "StudyDescription", 6)
- # Change column visibility
- dicomDatabase.setVisibilityForField("Patients", "PatientsBirthDate", False)
- dicomDatabase.setVisibilityForField("Patients", "PatientsComments", True)
- dicomDatabase.setWeightForField("Patients", "PatientsComments", 8)
- # Change column name
- dicomDatabase.setDisplayedNameForField("Series", "DisplayedCount", "Number of images")
- # Change column width to manual
- dicomDatabase.setFormatForField("Series", "SeriesDescription", '{"resizeMode":"interactive"}')
- # Customize table manager in DICOM browser
- dicomTableManager = dicomBrowser.dicomTableManager()
- dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection
- dicomTableManager.autoSelectSeries = False
-
- # Force database views update
- dicomDatabase.closeDatabase()
- dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)
-
-Query and retrieve data from a PACS using classic DIMSE DICOM networking
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Query
- dicomQuery = ctk.ctkDICOMQuery()
- dicomQuery.callingAETitle = "SLICER"
- dicomQuery.calledAETitle = "ANYAE"
- dicomQuery.host = "dicomserver.co.uk"
- dicomQuery.port = 11112
- dicomQuery.preferCGET = True
- dicomQuery.filters = {"Name":"Anon", "Modalities":"MR"}
- # temporary in-memory database for storing query results
- tempDb = ctk.ctkDICOMDatabase()
- tempDb.openDatabase("")
- dicomQuery.query(tempDb)
-
- # Retrieve
- dicomRetrieve = ctk.ctkDICOMRetrieve()
- dicomRetrieve.callingAETitle = dicomQuery.callingAETitle
- dicomRetrieve.calledAETitle = dicomQuery.calledAETitle
- dicomRetrieve.host = dicomQuery.host
- dicomRetrieve.port = dicomQuery.port
- dicomRetrieve.setMoveDestinationAETitle("SLICER");
- dicomRetrieve.setDatabase(slicer.dicomDatabase)
- for study in dicomQuery.studyInstanceUIDQueried:
- print(f"ctkDICOMRetrieveTest2: Retrieving {study}")
- slicer.app.processEvents()
- if dicomQuery.preferCGET:
- success = dicomRetrieve.getStudy(study)
- else:
- success = dicomRetrieve.moveStudy(study)
- print(f" - {'success' if success else 'failed'}")
- slicer.dicomDatabase.updateDisplayedFields()
-
-Convert RT structure set to labelmap NRRD files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-`SlicerRT batch processing `__ to batch convert RT structure sets to labelmap NRRD files.
diff --git a/Docs/developer_guide/script_repository/gui.md b/Docs/developer_guide/script_repository/gui.md
new file mode 100644
index 00000000000..8802ab7f791
--- /dev/null
+++ b/Docs/developer_guide/script_repository/gui.md
@@ -0,0 +1,1064 @@
+## Launch Slicer
+
+### Open a file with Slicer at the command line
+
+Open `imagefile.nrrd` file in Slicer:
+
+```console
+Slicer.exe /full/path/to/imagefile.nrrd
+```
+
+:::{note}
+
+It may be necessary to specify full path to the Slicer executable and to the file that needs to be loaded.
+
+:::
+
+To load a file with non-default options, you can use `--python-code` option to run `slicer.util.load...` commands.
+
+### Open an .mrb file with Slicer at the command line
+
+```console
+Slicer.exe --python-code "slicer.util.loadScene('f:/2013-08-23-Scene.mrb')"
+```
+
+### Run Python commands in the Slicer environment
+
+Run Python commands, without showing any graphical user interface:
+
+```console
+Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window
+```
+
+Slicer exits when the commands are completed because `--testing` options is specified.
+
+### Run a Python script file in the Slicer environment
+
+Run a Python script (stored in script file), without showing any graphical user interface:
+
+```console
+Slicer.exe --python-script "/full/path/to/myscript.py" --no-splash --no-main-window
+```
+
+To make Slicer exit when the script execution is completed, call `sys.exit(errorCode)` (where `errorCode` is set 0 for success and other value to indicate error).
+
+#### MRML scene
+
+### Get MRML node from the scene
+
+Get markups fiducial node named `F` (useful for quickly getting access to a MRML node in the Python console):
+
+```python
+fidsNode = getNode('F')
+# do something with the node... let's remove the first control point in it
+fidsNode.RemoveNthControlPoint(0)
+```
+
+Getting the first volume node without knowing its name (useful if there is only one volume loaded):
+
+```python
+volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
+# do something with the node... let's change its display window/level
+volumeNode.GetDisplayNode().SetAutoWindowLevel(False)
+volumeNode.GetDisplayNode().SetWindowLevelMinMax(100, 200)
+```
+
+:::{note}
+
+- {func}`slicer.util.getNode()` is recommended **only for interactive debugging** in the Python console/Jupyter notebook
+ - its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as `*`), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module
+ - throws an exception so that the developer knows immediately that there was a typo or other unexpected error
+- `slicer.mrmlScene.GetNodeByID()` is more appropriate when a module needs to access a MRML node:
+ - its behavior is more predictable: it only accepts node ID as input. `slicer.mrmlScene.GetFirstNodeByName()` can be used to get a node by its name, but since multiple nodes in the scene can have the same name, it is not recommended to keep reference to a node by its name. Since node IDs may change when a scene is saved and reloaded, node ID should not be stored persistently, but [node references](mrml_overview.md#mrml-node-references) must be used instead
+ - if node is not found it returns `None` (instead of throwing an exception), because this is often not considered an error in module code (it is just used to check existence of a node) and using return value for not-found nodes allows simpler syntax
+
+:::
+
+### Clone a node
+
+This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
+
+```python
+# Get a node from SampleData that we will clone
+import SampleData
+nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
+
+# Clone the node
+shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
+clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
+clonedNode = shNode.GetItemDataNode(clonedItemID)
+```
+
+### Save a node to file
+
+Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
+
+```python
+myNode = getNode("LinearTransform_3")
+
+myStorageNode = myNode.CreateDefaultStorageNode()
+myStorageNode.SetFileName("c:/tmp/something.tfm")
+myStorageNode.WriteData(myNode)
+```
+
+### Save the scene into a single MRB file
+
+```python
+# Generate file name
+import time
+sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"
+
+# Save scene
+if slicer.util.saveScene(sceneSaveFilename):
+ logging.info("Scene saved to: {0}".format(sceneSaveFilename))
+else:
+ logging.error("Scene saving failed")
+```
+
+### Save the scene into a new directory
+
+```python
+# Create a new directory where the scene will be saved into
+import time
+sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
+if not os.access(sceneSaveDirectory, os.F_OK):
+ os.makedirs(sceneSaveDirectory)
+
+# Save the scene
+if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
+ logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
+else:
+ logging.error("Scene saving failed")
+```
+
+### Override default scene save dialog
+
+Place this class in the scripted module file to override
+
+```python
+class MyModuleFileDialog ():
+ """This specially named class is detected by the scripted loadable
+ module and is the target for optional drag and drop operations.
+ See: Base/QTGUI/qSlicerScriptedFileDialog.h.
+
+ This class is used for overriding default scene save dialog
+ with simple saving the scene without asking anything.
+ """
+
+ def __init__(self,qSlicerFileDialog ):
+ self.qSlicerFileDialog = qSlicerFileDialog
+ qSlicerFileDialog.fileType = "NoFile"
+ qSlicerFileDialog.description = "Save scene"
+ qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write
+
+ def execDialog(self):
+ # Implement custom scene save operation here.
+ # Return True if saving completed successfully,
+ # return False if saving was cancelled.
+ ...
+ return saved
+```
+
+### Override application close behavior
+
+When application close is requested then by default confirmation popup is displayed. To customize this behavior (for example, allow application closing without displaying default confirmation popup) an event filter can be installed for the close event on the main window:
+
+```python
+class CloseApplicationEventFilter(qt.QWidget):
+ def eventFilter(self, object, event):
+ if event.type() == qt.QEvent.Close:
+ event.accept()
+ return True
+ return False
+
+filter = CloseApplicationEventFilter()
+slicer.util.mainWindow().installEventFilter(filter)
+```
+
+### Change default output file type for new nodes
+
+This script changes default output file format for nodes that have not been saved yet (do not have storage node yet).
+
+Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to PLY for model nodes:
+
+```python
+defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
+defaultModelStorageNode.SetDefaultWriteFileExtension("ply")
+slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
+```
+
+To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).
+
+### Change file type for saving for existing nodes
+
+This script changes output file types for nodes that have been already saved (they already have storage node).
+
+If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.
+
+```python
+# Delete existing model storage nodes so that they will be recreated with default settings
+existingModelStorageNodes = slicer.util.getNodesByClass("vtkMRMLModelStorageNode")
+for modelStorageNode in existingModelStorageNodes:
+ slicer.mrmlScene.RemoveNode(modelStorageNode)
+```
+
+To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):
+
+```python
+requiredFileExtension = ".nia"
+originalFileExtension = ".nrrd"
+volumeNodes = slicer.util.getNodesByClass("vtkMRMLScalarVolumeNode")
+for volumeNode in volumeNodes:
+ volumeStorageNode = volumeNode.GetStorageNode()
+ if not volumeStorageNode:
+ volumeNode.AddDefaultStorageNode()
+ volumeStorageNode = volumeNode.GetStorageNode()
+ volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
+ else:
+ volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
+```
+
+To set all volume nodes to save uncompressed by default (add this to [.slicerrc.py file ](../user_guide/settings.md#application-startup-file) so it takes effect for the whole session):
+
+```python
+#set the default volume storage to not compress by default
+defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
+defaultVolumeStorageNode.SetUseCompression(0)
+slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+logging.info("Volume nodes will be stored uncompressed by default")
+```
+
+Same thing as above, but applied to all segmentations instead of volumes:
+
+```python
+#set the default volume storage to not compress by default
+defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
+defaultVolumeStorageNode.SetUseCompression(0)
+slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
+logging.info("Segmentation nodes will be stored uncompressed
+```
+
+#### Module selection
+
+### Switch to a different module
+
+This utility function can be used to open a different module:
+
+```python
+slicer.util.selectModule("DICOM")
+```
+
+### Set a new default module at startup
+
+Instead of the default Welcome module:
+
+```python
+qt.QSettings().setValue("Modules/HomeModule", "Data")
+```
+
+#### Views
+
+### Display text in a 3D view or slice view
+
+The easiest way to show information overlaid on a viewer is to use corner annotations.
+
+```python
+view=slicer.app.layoutManager().threeDWidget(0).threeDView()
+# Set text to "Something"
+view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
+# Set color to red
+view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
+# Update the view
+view.forceRender()
+```
+
+To display text in slice views, replace the first line by this line (and consider hiding slice view annotations, to prevent them from overwriting the text you place there):
+
+```python
+view=slicer.app.layoutManager().sliceWidget("Red").sliceView()
+```
+
+### Show orientation marker in all views
+
+```python
+viewNodes = slicer.util.getNodesByClass("vtkMRMLAbstractViewNode")
+for viewNode in viewNodes:
+ viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)
+```
+
+### Change view axis labels
+
+```python
+labels = ["x", "X", "y", "Y", "z", "Z"]
+viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
+# for slice view:
+# viewNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
+for index, label in enumerate(labels):
+ viewNode.SetAxisLabel(index, label)
+```
+
+### Hide view controller bars
+
+```python
+slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)
+slicer.app.layoutManager().sliceWidget("Red").sliceController().setVisible(False)
+slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)
+slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)
+```
+
+### Hide Slicer logo from main window
+
+This script increases vertical space available in the module panel by hiding the Slicer application logo.
+
+```python
+slicer.util.findChild(slicer.util.mainWindow(), "LogoLabel").visible = False
+```
+
+### Customize widgets in view controller bars
+
+```python
+sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()
+
+# hide what is not needed
+sliceController.pinButton().hide()
+#sliceController.viewLabel().hide()
+sliceController.fitToWindowToolButton().hide()
+sliceController.sliceOffsetSlider().hide()
+
+# add custom widgets
+myButton = qt.QPushButton("My custom button")
+sliceController.barLayout().addWidget(myButton)
+```
+
+### Get current mouse coordinates in a slice view
+
+You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
+
+```python
+def onMouseMoved(observer,eventid):
+ ras=[0,0,0]
+ crosshairNode.GetCursorPositionRAS(ras)
+ print(ras)
+
+crosshairNode=slicer.util.getNode("Crosshair")
+crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
+```
+
+### Display crosshair at a 3D position
+
+```python
+position_RAS = [23.4, 5.6, 78.9]
+crosshairNode = slicer.util.getNode("Crosshair")
+# Set crosshair position
+crosshairNode.SetCrosshairRAS(position_RAS)
+# Center the position in all slice views
+slicer.vtkMRMLSliceNode.JumpAllSlices(slicer.mrmlScene, *position_RAS, slicer.vtkMRMLSliceNode.CenteredJumpSlice)
+```
+
+:::{note}
+
+Crosshair node stores two positions: Cursor position is the current position of the mouse pointer in a slice or 3D view (modules should only read this position). Crosshair position is the location of the visible crosshair in views (modules can read or write this position).
+
+:::
+
+### Display mouse pointer coordinates in alternative coordinate system
+
+The Data probe only shows coordinate values in the world coordinate system. You can make the world coordinate system mean anything you want (e.g., MNI) by applying a transform to the volume that transforms it into that space. See more details in [here ](https://discourse.slicer.org/t/setting-an-mni-origo-to-a-volume/16164/4).
+
+```python
+def onMouseMoved(observer,eventid):
+ mniToWorldTransformNode = getNode("LinearTransform_3") # replace this by the name of your actual MNI to world transform
+ worldToMniTransform = vtk.vtkGeneralTransform()
+ mniToWorldTransformNode.GetTransformToWorld(worldToMniTransform)
+ ras=[0,0,0]
+ mni=[0,0,0]
+ crosshairNode.GetCursorPositionRAS(ras)
+ worldToMniTransform.TransformPoint(ras, mni)
+ _ras = "; ".join([str(k) for k in ras])
+ _mni = "; ".join([str(k) for k in mni])
+ slicer.util.showStatusMessage(f"RAS={_ras} MNI={_mni}")
+
+crosshairNode=slicer.util.getNode("Crosshair")
+observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
+
+# Run this to stop displaying values:
+# crosshairNode.RemoveObserver(observationId)
+```
+
+### Get DataProbe text
+
+You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the `.` (period) key in a slice view after pasting in the following code.
+
+```python
+def printDataProbe():
+ infoWidget = slicer.modules.DataProbeInstance.infoWidget
+ for layer in ("B", "F", "L"):
+ print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)
+
+s = qt.QShortcut(qt.QKeySequence("."), mainWindow())
+s.connect("activated()", printDataProbe)
+```
+
+### Create custom color table
+
+This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
+
+```python
+invertedocean = slicer.vtkMRMLColorTableNode()
+invertedocean.SetTypeToUser()
+invertedocean.SetNumberOfColors(256)
+invertedocean.SetName("InvertedOcean")
+
+for i in range(0,255):
+ invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)
+
+slicer.mrmlScene.AddNode(invertedocean)
+```
+
+### Show color scalar bar in slice views
+
+Display color bar for background volume in slice views (managed by DataProbe):
+
+```python
+sliceAnnotations = slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations
+sliceAnnotations.sliceViewAnnotationsEnabled = True
+sliceAnnotations.scalarBarEnabled = 1
+sliceAnnotations.scalarBarSelectedLayer = "background" # alternative is "foreground"
+sliceAnnotations.rangeLabelFormat = "test %G"
+sliceAnnotations.updateSliceViewFromGUI()
+```
+
+### Display color scalar bar in 3D views
+
+```python
+colorTableRangeMm = 40
+title ="Radial\nCompression\n"
+labelsFormat = "%4.1f mm"
+
+# Create color node
+colorNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLProceduralColorNode")
+colorNode.UnRegister(None) # to prevent memory leaks
+colorNode.SetName(slicer.mrmlScene.GenerateUniqueName("MyColormap"))
+colorNode.SetAttribute("Category", "MyModule")
+# The color node is a procedural color node, which is saved using a storage node.
+# Hidden nodes are not saved if they use a storage node, therefore
+# the color node must be visible.
+colorNode.SetHideFromEditors(False)
+slicer.mrmlScene.AddNode(colorNode)
+
+# Specify colormap
+colorMap = colorNode.GetColorTransferFunction()
+colorMap.RemoveAllPoints()
+colorMap.AddRGBPoint(colorTableRangeMm * 0.0, 0.0, 0.0, 1.0)
+colorMap.AddRGBPoint(colorTableRangeMm * 0.2, 0.0, 1.0, 1.0)
+colorMap.AddRGBPoint(colorTableRangeMm * 0.5, 1.0, 1.0, 0.0)
+colorMap.AddRGBPoint(colorTableRangeMm * 1.0, 1.0, 0.0, 0.0)
+
+# Display color scalar bar
+colorWidget = slicer.modules.colors.widgetRepresentation()
+colorWidget.setCurrentColorNode(colorNode)
+ctkScalarBarWidget = slicer.util.findChildren(colorWidget, name="VTKScalarBar")[0]
+ctkScalarBarWidget.setDisplay(1)
+ctkScalarBarWidget.setTitle(title)
+ctkScalarBarWidget.setMaxNumberOfColors(256)
+ctkScalarBarWidget.setLabelsFormat(labelsFormat)
+```
+
+### Customize view layout
+
+Show a custom layout of a 3D view on top of the red slice view:
+
+```python
+customLayout = """
+
+
+
+ 1
+
+
+
+
+ Axial
+ R
+ #F34A33
+
+
+
+"""
+
+# Built-in layout IDs are all below 100, so you can choose any large random number
+# for your custom layout ID.
+customLayoutId=501
+
+layoutManager = slicer.app.layoutManager()
+layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)
+
+# Switch to the new custom layout
+layoutManager.setLayout(customLayoutId)
+```
+
+See description of standard layouts (that can be used as examples) here: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
+
+You can use this code snippet to add a button to the layout selector toolbar:
+
+```python
+# Add button to layout selector toolbar for this custom layout
+viewToolBar = mainWindow().findChild("QToolBar", "ViewToolBar")
+layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()
+layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list
+layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list
+layoutSwitchAction.setData(layoutId)
+layoutSwitchAction.setIcon(qt.QIcon(":Icons/Go.png"))
+layoutSwitchAction.setToolTip("3D and slice view")
+```
+
+### Turn on slice intersections
+
+```python
+viewNodes = slicer.util.getNodesByClass("vtkMRMLSliceCompositeNode")
+for viewNode in viewNodes:
+ viewNode.SetSliceIntersectionVisibility(1)
+```
+
+:::{note}
+
+How to find code corresponding to a user interface widget?
+
+For this one I searched for "slice intersections" text in the whole slicer source code, found that the function is implemented in `Base\QTGUI\qSlicerViewersToolBar.cxx`, then translated the `qSlicerViewersToolBarPrivate::setSliceIntersectionVisible(bool visible)` method to Python.
+
+:::
+
+### Hide slice view annotations
+
+This script can hide node name, patient information displayed in corners of slice views (managed by DataProbe module).
+
+```python
+# Disable slice annotations immediately
+sliceAnnotations = slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations
+sliceAnnotations.sliceViewAnnotationsEnabled=False
+sliceAnnotations.updateSliceViewFromGUI()
+# Disable slice annotations persistently (after Slicer restarts)
+settings = qt.QSettings()
+settings.setValue("DataProbe/sliceViewAnnotations.enabled", 0)
+```
+
+### Change slice offset
+
+Equivalent to moving the slider in slice view controller.
+
+```python
+layoutManager = slicer.app.layoutManager()
+red = layoutManager.sliceWidget("Red")
+redLogic = red.sliceLogic()
+# Print current slice offset position
+print(redLogic.GetSliceOffset())
+# Change slice position
+redLogic.SetSliceOffset(20)
+```
+
+### Change slice orientation
+
+Get `Red` slice node and rotate around `X` and `Y` axes.
+
+```python
+sliceNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
+sliceToRas = sliceNode.GetSliceToRAS()
+transform=vtk.vtkTransform()
+transform.SetMatrix(SliceToRAS)
+transform.RotateX(20)
+transform.RotateY(15)
+sliceToRas.DeepCopy(transform.GetMatrix())
+sliceNode.UpdateMatrices()
+```
+
+### Measure angle between two slice planes
+
+Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.
+
+```python
+sliceNodeIds = ["vtkMRMLSliceNodeRed", "vtkMRMLSliceNodeYellow"]
+
+# Print angles between slice nodes
+def ShowAngle(unused1=None, unused2=None):
+ sliceNormalVector = []
+ for sliceNodeId in sliceNodeIds:
+ sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()
+ sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])
+ angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])
+ angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
+ print("Angle between slice planes = {0:0.3f}".format(angleDeg))
+
+# Observe slice node changes
+for sliceNodeId in sliceNodeIds:
+ slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)
+
+# Print current angle
+ShowAngle()
+```
+
+### Set slice position and orientation from a normal vector and position
+
+This code snippet shows how to display a slice view defined by a normal vector and position in an anatomically sensible way: rotating slice view so that "up" direction (or "right" direction) is towards an anatomical axis.
+
+```python
+def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
+ """
+ Set slice pose from the provided plane normal and position. View up direction is determined automatically,
+ to make view up point towards defaultViewUpDirection.
+ :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
+ :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
+ if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
+ """
+ # Fix up input directions
+ if defaultViewUpDirection is None:
+ defaultViewUpDirection = [0,0,1]
+ if backupViewRightDirection is None:
+ backupViewRightDirection = [-1,0,0]
+ if sliceNormal[1]>=0:
+ sliceNormalStandardized = sliceNormal
+ else:
+ sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
+ # Compute slice axes
+ sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
+ angleTooSmallThresholdRad = 0.25 # about 15 degrees
+ if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
+ viewUpDirection = defaultViewUpDirection
+ sliceAxisY = viewUpDirection
+ sliceAxisX = [0, 0, 0]
+ vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
+ else:
+ sliceAxisX = backupViewRightDirection
+ # Set slice axes
+ sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],
+ sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],
+ slicePosition[0], slicePosition[1], slicePosition[2], 0)
+
+# Example usage:
+sliceNode = getNode("vtkMRMLSliceNodeRed")
+transformNode = getNode("Transform_3")
+transformMatrix = vtk.vtkMatrix4x4()
+transformNode.GetMatrixTransformToParent(transformMatrix)
+sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
+slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
+setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
+```
+
+### Show slice views in 3D window
+
+Equivalent to clicking 'eye' icon in the slice view controller.
+
+```python
+layoutManager = slicer.app.layoutManager()
+for sliceViewName in layoutManager.sliceViewNames():
+ controller = layoutManager.sliceWidget(sliceViewName).sliceController()
+ controller.setSliceVisible(True)
+```
+
+### Change default slice view orientation
+
+You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your [.slicerrc.py file](../user_guide/settings.md#application-startup-file).
+
+```python
+# Axial slice axes:
+# 1 0 0
+# 0 1 0
+# 0 0 1
+axialSliceToRas=vtk.vtkMatrix3x3()
+
+# Coronal slice axes:
+# 1 0 0
+# 0 0 -1
+# 0 1 0
+coronalSliceToRas=vtk.vtkMatrix3x3()
+coronalSliceToRas.SetElement(1,1, 0)
+coronalSliceToRas.SetElement(1,2, -1)
+coronalSliceToRas.SetElement(2,1, 1)
+coronalSliceToRas.SetElement(2,2, 0)
+
+# Replace orientation presets in all existing slice nodes and in the default slice node
+sliceNodes = slicer.util.getNodesByClass("vtkMRMLSliceNode")
+sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass("vtkMRMLSliceNode"))
+for sliceNode in sliceNodes:
+ orientationPresetName = sliceNode.GetOrientation()
+ sliceNode.RemoveSliceOrientationPreset("Axial")
+ sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)
+ sliceNode.RemoveSliceOrientationPreset("Coronal")
+ sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)
+ sliceNode.SetOrientation(orientationPresetName)
+```
+
+### Set all slice views linked by default
+
+You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [.slicerrc.py file ](../user_guide/settings.md#application-startup-file).
+
+```python
+# Set linked slice views in all existing slice composite nodes and in the default node
+sliceCompositeNodes = slicer.util.getNodesByClass("vtkMRMLSliceCompositeNode")
+defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass("vtkMRMLSliceCompositeNode")
+if not defaultSliceCompositeNode:
+ defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLSliceCompositeNode")
+ defaultSliceCompositeNode.UnRegister(None) # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks
+ slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
+sliceCompositeNodes.append(defaultSliceCompositeNode)
+for sliceCompositeNode in sliceCompositeNodes:
+ sliceCompositeNode.SetLinkedControl(True)
+```
+
+### Set crosshair jump mode to centered by default
+
+You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [.slicerrc.py file ](../user_guide/settings.md#application-startup-file).
+
+```python
+crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
+crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
+```
+
+### Set up custom units in slice view ruler
+
+For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):
+
+```python
+lm = slicer.app.layoutManager()
+for sliceViewName in lm.sliceViewNames():
+ sliceView = lm.sliceWidget(sliceViewName).sliceView()
+ displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")
+ displayableManager.RemoveAllRulerScalePresets()
+ displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)
+ displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)
+ displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)
+ displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)
+ displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)
+ displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)
+ displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)
+ displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)
+ displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)
+ displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)
+ displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)
+```
+
+### Center the 3D view on the scene
+
+```python
+layoutManager = slicer.app.layoutManager()
+threeDWidget = layoutManager.threeDWidget(0)
+threeDView = threeDWidget.threeDView()
+threeDView.resetFocalPoint()
+```
+
+### Rotate the 3D View
+
+```python
+layoutManager = slicer.app.layoutManager()
+threeDWidget = layoutManager.threeDWidget(0)
+threeDView = threeDWidget.threeDView()
+threeDView.yaw()
+```
+
+### Change 3D view background color
+
+```python
+viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
+viewNode.SetBackgroundColor(1,0,0)
+viewNode.SetBackgroundColor2(1,0,0)
+```
+
+### Show a slice view outside the view layout
+
+```python
+# layout name is used to create and identify the underlying slice node and should be set to a value that is not used in any of the layouts owned by the layout manager
+layoutName = "TestSlice1"
+layoutLabel = "TS1"
+layoutColor = [1.0, 1.0, 0.0]
+# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
+viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
+
+# Create MRML nodes
+viewLogic = slicer.vtkMRMLSliceLogic()
+viewLogic.SetMRMLScene(slicer.mrmlScene)
+viewNode = viewLogic.AddSliceNode(layoutName)
+viewNode.SetLayoutLabel(layoutLabel)
+viewNode.SetLayoutColor(layoutColor)
+viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
+
+# Create widget
+viewWidget = slicer.qMRMLSliceWidget()
+viewWidget.setMRMLScene(slicer.mrmlScene)
+viewWidget.setMRMLSliceNode(viewNode)
+sliceLogics = slicer.app.applicationLogic().GetSliceLogics()
+viewWidget.setSliceLogics(sliceLogics)
+sliceLogics.AddItem(viewWidget.sliceLogic())
+viewWidget.show()
+```
+
+### Show a 3D view outside the view layout
+
+```python
+# layout name is used to create and identify the underlying view node and should be set to a value that is not used in any of the layouts owned by the layout manager
+layoutName = "Test3DView"
+layoutLabel = "T3"
+layoutColor = [1.0, 1.0, 0.0]
+# ownerNode manages this view instead of the layout manager (it can be any node in the scene)
+viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
+
+# Create MRML node
+viewLogic = slicer.vtkMRMLViewLogic()
+viewLogic.SetMRMLScene(slicer.mrmlScene)
+viewNode = viewLogic.AddViewNode(layoutName)
+viewNode.SetLayoutLabel(layoutLabel)
+viewNode.SetLayoutColor(layoutColor)
+viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
+
+# Create widget
+viewWidget = slicer.qMRMLThreeDWidget()
+viewWidget.setMRMLScene(slicer.mrmlScene)
+viewWidget.setMRMLViewNode(viewNode)
+viewWidget.show()
+```
+
+#### Access VTK rendering classes
+
+### Accesss VTK views, renderers, and cameras
+
+Iterate through all 3D views in current layout:
+
+```python
+layoutManager = slicer.app.layoutManager()
+for threeDViewIndex in range(layoutManager.threeDViewCount) :
+ view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
+ threeDViewNode = view.mrmlViewNode()
+ cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
+ print("View node for 3D widget " + str(threeDViewIndex))
+ print(" Name: " + threeDViewNode .GetName())
+ print(" ID: " + threeDViewNode .GetID())
+ print(" Camera ID: " + cameraNode.GetID())
+```
+
+Iterate through all slice views in current layout:
+
+```python
+layoutManager = slicer.app.layoutManager()
+for sliceViewName in layoutManager.sliceViewNames():
+ view = layoutManager.sliceWidget(sliceViewName).sliceView()
+ sliceNode = view.mrmlSliceNode()
+ sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
+ compositeNode = sliceLogic.GetSliceCompositeNode()
+ print("Slice view " + str(sliceViewName))
+ print(" Name: " + sliceNode.GetName())
+ print(" ID: " + sliceNode.GetID())
+ print(" Background volume: {0}".format(compositeNode.GetBackgroundVolumeID()))
+ print(" Foreground volume: {0} (opacity: {1})".format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))
+ print(" Label volume: {0} (opacity: {1})".format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))
+```
+
+For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.
+
+```python
+renderWindow = view.renderWindow()
+renderers = renderWindow.GetRenderers()
+renderer = renderers.GetItemAsObject(0)
+camera = cameraNode.GetCamera()
+```
+
+### Get displayable manager of a certain type for a certain view
+
+Displayable managers are responsible for creating VTK filters, mappers, and actors to display MRML nodes in renderers. Input to filters and mappers are VTK objects stored in MRML data nodes. Filter and actor properties are set based on display options specified in MRML display nodes.
+
+Accessing displayable managers is useful for troubleshooting or for testing new features that are not exposed via MRML classes yet, as they provide usually allow low-level access to VTK actors.
+
+```python
+threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
+modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLModelDisplayableManager")
+if modelDisplayableManager is None:
+ logging.error("Failed to find the model displayable manager")
+```
+
+### Access VTK actor properties
+
+This example shows how to access and modify VTK actor properties to experiment with physically-based rendering.
+
+```python
+modelNode = slicer.util.getNode("MyModel")
+
+threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
+modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLModelDisplayableManager")
+actor=modelDisplayableManager.GetActorByID(modelNode.GetDisplayNode().GetID())
+property=actor.GetProperty()
+property.SetInterpolationToPBR()
+property.SetMetallic(0.5)
+property.SetRoughness(0.5)
+property.SetColor(0.5,0.5,0.9)
+slicer.util.forceRenderAllViews()
+```
+
+See more information on physically based rendering in VTK here: https://blog.kitware.com/vtk-pbr/
+
+#### Keyboard shortcuts and mouse gestures
+
+### Customize keyboard shortcuts
+
+Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your [.slicerrc.py file](../user_guide/settings.md#application-startup-file).
+
+For example, this script registers Ctrl+b, Ctrl+n, Ctrl+m, Ctrl+, keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.
+
+```python
+shortcuts = [
+ ("Ctrl+b", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
+ ("Ctrl+n", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
+ ("Ctrl+m", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
+ ("Ctrl+,", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
+ ]
+
+for (shortcutKey, callback) in shortcuts:
+ shortcut = qt.QShortcut(slicer.util.mainWindow())
+ shortcut.setKey(qt.QKeySequence(shortcutKey))
+ shortcut.connect( "activated()", callback)
+```
+
+Here's an example for cycling through Segment Editor effects (requested [on the Slicer forum](https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2) for the [SlicerMorph](http://slicermorph.org) project).
+
+```python
+def cycleEffect(delta=1):
+ try:
+ orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())
+ allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()
+ for name in allNames:
+ try:
+ orderedNames.index(name)
+ except ValueError:
+ orderedNames.append(name)
+ orderedNames.insert(0, None)
+ activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()
+ if activeEffect:
+ activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name
+ else:
+ activeName = None
+ newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)
+ slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])
+ except AttributeError:
+ # module not active
+ pass
+
+shortcuts = [
+ ("`", lambda: cycleEffect(-1)),
+ ("~", lambda: cycleEffect(1)),
+ ]
+
+for (shortcutKey, callback) in shortcuts:
+ shortcut = qt.QShortcut(slicer.util.mainWindow())
+ shortcut.setKey(qt.QKeySequence(shortcutKey))
+ shortcut.connect( "activated()", callback)
+```
+
+### Customize keyboard/mouse gestures in viewers
+
+Example for making the 3D view rotate using right-click-and-drag:
+
+```python
+threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
+cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLCameraDisplayableManager")
+cameraWidget = cameraDisplayableManager.GetCameraWidget()
+
+# Remove old mapping from right-click-and-drag
+cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
+ cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)
+
+# Make right-click-and-drag rotate the view
+cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
+ cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)
+```
+
+### Disable certain user interactions in slice views
+
+For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
+
+```python
+interactorStyle = slicer.app.layoutManager().sliceWidget("Red").sliceView().sliceViewInteractorStyle()
+interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
+```
+
+Hide all slice view controllers:
+
+```python
+lm = slicer.app.layoutManager()
+for sliceViewName in lm.sliceViewNames():
+ lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
+```
+
+Hide all 3D view controllers:
+
+```python
+lm = slicer.app.layoutManager()
+for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
+ lm.threeDWidget(0).threeDController().setVisible(False)
+```
+
+### Add keyboard shortcut to jump to center or world coordinate system
+
+You can copy-paste this into the Python console to jump slice views to (0,0,0) position on (Ctrl+e):
+
+```python
+shortcut = qt.QShortcut(qt.QKeySequence("Ctrl+e"), slicer.util.mainWindow())
+shortcut.connect("activated()",
+ lambda: slicer.modules.markups.logic().JumpSlicesToLocation(0,0,0, True))
+```
+
+#### Launch external applications
+
+How to run external applications from Slicer.
+
+### Launch external process in startup environment
+
+When a process is launched from Slicer then by default Slicer"s ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using {func}`slicer.util.startupEnvironment()`.
+
+Example: run Python3 script from Slicer:
+
+```python
+command_to_execute = ["/usr/bin/python3", "-c", "print("hola")"]
+from subprocess import check_output
+check_output(
+ command_to_execute,
+ env=slicer.util.startupEnvironment()
+ )
+```
+
+will output:
+
+```python
+"hola\n"
+```
+
+On some systems, *shell=True* must be specified as well.
+
+#### Manage extensions
+
+### Download and install extension
+
+```python
+extensionName = 'SlicerIGT'
+em = slicer.app.extensionsManagerModel()
+if not em.isExtensionInstalled(extensionName):
+ extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)
+ url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']
+ extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']
+ slicer.util.downloadFile(url, extensionPackageFilename)
+ em.installExtension(extensionPackageFilename)
+ slicer.util.restart()
+```
+
+### Install a module directly from a git repository
+
+This [code snippet](https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597) can be useful for sharing code in development without requiring a restart of Slicer.
+
+### Install a Python package
+
+Python packages that are optional or would be impractical to bundle into the extension can be installed at runtime. It is recommended to only install a package when it is actually needed (not at startup, not even when the user opens a module, but just before that Python package is used the first time), and ask the user about it (if it is more than just a few megabytes).
+
+```python
+try:
+ import flywheel
+except ModuleNotFoundError as e:
+ if slicer.util.confirmOkCancelDisplay("This module requires 'flywheel-sdk' Python package. Click OK to install it now."):
+ slicer.util.pip_install("flywheel-sdk")
+ import flywheel
+```
diff --git a/Docs/developer_guide/script_repository/gui.rst b/Docs/developer_guide/script_repository/gui.rst
deleted file mode 100644
index 3b87be01fa2..00000000000
--- a/Docs/developer_guide/script_repository/gui.rst
+++ /dev/null
@@ -1,1124 +0,0 @@
-Launch Slicer
-~~~~~~~~~~~~~
-
-Open a file with Slicer at the command line
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Open ``imagefile.nrrd`` file in Slicer:
-
-::
-
- Slicer.exe /full/path/to/imagefile.nrrd
-
-.. note::
-
- It may be necessary to specify full path to the Slicer executable and to the file that needs to be loaded.
-
-To load a file with non-default options, you can use ``--python-code`` option to run ``slicer.util.load...`` commands.
-
-Open an .mrb file with Slicer at the command line
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-::
-
- Slicer.exe --python-code "slicer.util.loadScene('f:/2013-08-23-Scene.mrb')"
-
-Run Python commands in the Slicer environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Run Python commands, without showing any graphical user interface:
-
-::
-
- Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window
-
-Slicer exits when the commands are completed because ``--testing`` options is specified.
-
-Run a Python script file in the Slicer environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Run a Python script (stored in script file), without showing any graphical user interface:
-
-::
-
- Slicer.exe --python-script "/full/path/to/myscript.py" --no-splash --no-main-window
-
-To make Slicer exit when the script execution is completed, call ``sys.exit(errorCode)`` (where ``errorCode`` is set 0 for success and other value to indicate error).
-
-MRML scene
-~~~~~~~~~~
-
-Get MRML node from the scene
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Get markups fiducial node named ``F`` (useful for quickly getting access to a MRML node in the Python console):
-
-.. code-block:: python
-
- fidsNode = getNode('F')
- # do something with the node... let's remove the first control point in it
- fidsNode.RemoveNthControlPoint(0)
-
-Getting the first volume node without knowing its name (useful if there is only one volume loaded):
-
-.. code-block:: python
-
- volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
- # do something with the node... let's change its display window/level
- volumeNode.GetDisplayNode().SetAutoWindowLevel(False)
- volumeNode.GetDisplayNode().SetWindowLevelMinMax(100, 200)
-
-.. note::
-
- - :func:`slicer.util.getNode()` is recommended **only for interactive debugging** in the Python console/Jupyter notebook
-
- - its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as ``*``), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module
- - throws an exception so that the developer knows immediately that there was a typo or other unexpected error
-
- - ``slicer.mrmlScene.GetNodeByID()`` is more appropriate when a module needs to access a MRML node:
-
- - its behavior is more predictable: it only accepts node ID as input. ``slicer.mrmlScene.GetFirstNodeByName()`` can be used to get a node by its name, but since multiple nodes in the scene can have the same name, it is not recommended to keep reference to a node by its name. Since node IDs may change when a scene is saved and reloaded, node ID should not be stored persistently, but `node references `__ must be used instead
- - if node is not found it returns ``None`` (instead of throwing an exception), because this is often not considered an error in module code (it is just used to check existence of a node) and using return value for not-found nodes allows simpler syntax
-
-Clone a node
-^^^^^^^^^^^^
-
-This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).
-
-.. code-block:: python
-
- # Get a node from SampleData that we will clone
- import SampleData
- nodeToClone = SampleData.SampleDataLogic().downloadMRHead()
-
- # Clone the node
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- itemIDToClone = shNode.GetItemByDataNode(nodeToClone)
- clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)
- clonedNode = shNode.GetItemDataNode(clonedItemID)
-
-Save a node to file
-^^^^^^^^^^^^^^^^^^^
-
-Save a transform node to file (should work with any other node type, if file extension is set to a supported one):
-
-.. code-block:: python
-
- myNode = getNode("LinearTransform_3")
-
- myStorageNode = myNode.CreateDefaultStorageNode()
- myStorageNode.SetFileName("c:/tmp/something.tfm")
- myStorageNode.WriteData(myNode)
-
-Save the scene into a single MRB file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Generate file name
- import time
- sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"
-
- # Save scene
- if slicer.util.saveScene(sceneSaveFilename):
- logging.info("Scene saved to: {0}".format(sceneSaveFilename))
- else:
- logging.error("Scene saving failed")
-
-Save the scene into a new directory
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Create a new directory where the scene will be saved into
- import time
- sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")
- if not os.access(sceneSaveDirectory, os.F_OK):
- os.makedirs(sceneSaveDirectory)
-
- # Save the scene
- if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):
- logging.info("Scene saved to: {0}".format(sceneSaveDirectory))
- else:
- logging.error("Scene saving failed")
-
-Override default scene save dialog
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Place this class in the scripted module file to override
-
-.. code-block:: python
-
- class MyModuleFileDialog ():
- """This specially named class is detected by the scripted loadable
- module and is the target for optional drag and drop operations.
- See: Base/QTGUI/qSlicerScriptedFileDialog.h.
-
- This class is used for overriding default scene save dialog
- with simple saving the scene without asking anything.
- """
-
- def __init__(self,qSlicerFileDialog ):
- self.qSlicerFileDialog = qSlicerFileDialog
- qSlicerFileDialog.fileType = "NoFile"
- qSlicerFileDialog.description = "Save scene"
- qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write
-
- def execDialog(self):
- # Implement custom scene save operation here.
- # Return True if saving completed successfully,
- # return False if saving was cancelled.
- ...
- return saved
-
-Override application close behavior
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When application close is requested then by default confirmation popup is displayed. To customize this behavior (for example, allow application closing without displaying default confirmation popup) an event filter can be installed for the close event on the main window:
-
-.. code-block:: python
-
- class CloseApplicationEventFilter(qt.QWidget):
- def eventFilter(self, object, event):
- if event.type() == qt.QEvent.Close:
- event.accept()
- return True
- return False
-
- filter = CloseApplicationEventFilter()
- slicer.util.mainWindow().installEventFilter(filter)
-
-Change default output file type for new nodes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This script changes default output file format for nodes that have not been saved yet (do not have storage node yet).
-
-Default node can be specified that will be used as a basis of all new storage nodes. This can be used for setting default file extension. For example, change file format to PLY for model nodes:
-
-.. code-block:: python
-
- defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()
- defaultModelStorageNode.SetDefaultWriteFileExtension("ply")
- slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)
-
-To permanently change default file extension on your computer, copy-paste the code above into your application startup script (you can find its location in menu: Edit / Application settings / General / Application startup script).
-
-Change file type for saving for existing nodes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This script changes output file types for nodes that have been already saved (they already have storage node).
-
-If it is not necessary to preserve file paths then the simplest is to configure default storage node (as shown in the example above), then delete all existing storage nodes. When save dialog is opened, default storage nodes will be recreated.
-
-.. code-block:: python
-
- # Delete existing model storage nodes so that they will be recreated with default settings
- existingModelStorageNodes = slicer.util.getNodesByClass("vtkMRMLModelStorageNode")
- for modelStorageNode in existingModelStorageNodes:
- slicer.mrmlScene.RemoveNode(modelStorageNode)
-
-To update existing storage nodes to use new file extension (but keep all other parameters unchanged) you can use this approach (example is for volume storage):
-
-.. code-block:: python
-
- requiredFileExtension = ".nia"
- originalFileExtension = ".nrrd"
- volumeNodes = slicer.util.getNodesByClass("vtkMRMLScalarVolumeNode")
- for volumeNode in volumeNodes:
- volumeStorageNode = volumeNode.GetStorageNode()
- if not volumeStorageNode:
- volumeNode.AddDefaultStorageNode()
- volumeStorageNode = volumeNode.GetStorageNode()
- volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)
- else:
- volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))
-
-To set all volume nodes to save uncompressed by default (add this to `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__ so it takes effect for the whole session):
-
-.. code-block:: python
-
- #set the default volume storage to not compress by default
- defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()
- defaultVolumeStorageNode.SetUseCompression(0)
- slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
- logging.info("Volume nodes will be stored uncompressed by default")
-
-Same thing as above, but applied to all segmentations instead of volumes:
-
-.. code-block:: python
-
- #set the default volume storage to not compress by default
- defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()
- defaultVolumeStorageNode.SetUseCompression(0)
- slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)
- logging.info("Segmentation nodes will be stored uncompressed
-
-Module selection
-~~~~~~~~~~~~~~~~
-
-Switch to a different module
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This utility function can be used to open a different module:
-
-.. code-block:: python
-
- slicer.util.selectModule("DICOM")
-
-Set a new default module at startup
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Instead of the default Welcome module:
-
-.. code-block:: python
-
- qt.QSettings().setValue("Modules/HomeModule", "Data")
-
-Views
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Display text in a 3D view or slice view
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The easiest way to show information overlaid on a viewer is to use corner annotations.
-
-.. code-block:: python
-
- view=slicer.app.layoutManager().threeDWidget(0).threeDView()
- # Set text to "Something"
- view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")
- # Set color to red
- view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)
- # Update the view
- view.forceRender()
-
-To display text in slice views, replace the first line by this line (and consider hiding slice view annotations, to prevent them from overwriting the text you place there):
-
-.. code-block:: python
-
- view=slicer.app.layoutManager().sliceWidget("Red").sliceView()
-
-Show orientation marker in all views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- viewNodes = slicer.util.getNodesByClass("vtkMRMLAbstractViewNode")
- for viewNode in viewNodes:
- viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)
-
-Change view axis labels
-^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- labels = ["x", "X", "y", "Y", "z", "Z"]
- viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
- # for slice view:
- # viewNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
- for index, label in enumerate(labels):
- viewNode.SetAxisLabel(index, label)
-
-Hide view controller bars
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)
- slicer.app.layoutManager().sliceWidget("Red").sliceController().setVisible(False)
- slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)
- slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)
-
-Hide Slicer logo from main window
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This script increases vertical space available in the module panel by hiding the Slicer application logo.
-
-.. code-block:: python
-
- slicer.util.findChild(slicer.util.mainWindow(), "LogoLabel").visible = False
-
-Customize widgets in view controller bars
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()
-
- # hide what is not needed
- sliceController.pinButton().hide()
- #sliceController.viewLabel().hide()
- sliceController.fitToWindowToolButton().hide()
- sliceController.sliceOffsetSlider().hide()
-
- # add custom widgets
- myButton = qt.QPushButton("My custom button")
- sliceController.barLayout().addWidget(myButton)
-
-Get current mouse coordinates in a slice view
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:
-
-.. code-block:: python
-
- def onMouseMoved(observer,eventid):
- ras=[0,0,0]
- crosshairNode.GetCursorPositionRAS(ras)
- print(ras)
-
- crosshairNode=slicer.util.getNode("Crosshair")
- crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
-
-Display crosshair at a 3D position
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- position_RAS = [23.4, 5.6, 78.9]
- crosshairNode = slicer.util.getNode("Crosshair")
- # Set crosshair position
- crosshairNode.SetCrosshairRAS(position_RAS)
- # Center the position in all slice views
- slicer.vtkMRMLSliceNode.JumpAllSlices(slicer.mrmlScene, *position_RAS, slicer.vtkMRMLSliceNode.CenteredJumpSlice)
-
-.. note::
-
- Crosshair node stores two positions: Cursor position is the current position of the mouse pointer in a slice or 3D view (modules should only read this position). Crosshair position is the location of the visible crosshair in views (modules can read or write this position).
-
-Display mouse pointer coordinates in alternative coordinate system
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The Data probe only shows coordinate values in the world coordinate system. You can make the world coordinate system mean anything you want (e.g., MNI) by applying a transform to the volume that transforms it into that space. See more details in `here `__.
-
-.. code-block:: python
-
- def onMouseMoved(observer,eventid):
- mniToWorldTransformNode = getNode("LinearTransform_3") # replace this by the name of your actual MNI to world transform
- worldToMniTransform = vtk.vtkGeneralTransform()
- mniToWorldTransformNode.GetTransformToWorld(worldToMniTransform)
- ras=[0,0,0]
- mni=[0,0,0]
- crosshairNode.GetCursorPositionRAS(ras)
- worldToMniTransform.TransformPoint(ras, mni)
- _ras = "; ".join([str(k) for k in ras])
- _mni = "; ".join([str(k) for k in mni])
- slicer.util.showStatusMessage(f"RAS={_ras} MNI={_mni}")
-
- crosshairNode=slicer.util.getNode("Crosshair")
- observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
-
- # Run this to stop displaying values:
- # crosshairNode.RemoveObserver(observationId)
-
-Get DataProbe text
-^^^^^^^^^^^^^^^^^^
-
-You can get the mouse location in pixel coordinates along with the pixel value at the mouse by hitting the ``.`` (period) key in a slice view after pasting in the following code.
-
-.. code-block:: python
-
- def printDataProbe():
- infoWidget = slicer.modules.DataProbeInstance.infoWidget
- for layer in ("B", "F", "L"):
- print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)
-
- s = qt.QShortcut(qt.QKeySequence("."), mainWindow())
- s.connect("activated()", printDataProbe)
-
-Create custom color table
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.
-
-.. code-block:: python
-
- invertedocean = slicer.vtkMRMLColorTableNode()
- invertedocean.SetTypeToUser()
- invertedocean.SetNumberOfColors(256)
- invertedocean.SetName("InvertedOcean")
-
- for i in range(0,255):
- invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)
-
- slicer.mrmlScene.AddNode(invertedocean)
-
-Show color scalar bar in slice views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Display color bar for background volume in slice views (managed by DataProbe):
-
-.. code-block:: python
-
- sliceAnnotations = slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations
- sliceAnnotations.sliceViewAnnotationsEnabled = True
- sliceAnnotations.scalarBarEnabled = 1
- sliceAnnotations.scalarBarSelectedLayer = "background" # alternative is "foreground"
- sliceAnnotations.rangeLabelFormat = "test %G"
- sliceAnnotations.updateSliceViewFromGUI()
-
-Display color scalar bar in 3D views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- colorTableRangeMm = 40
- title ="Radial\nCompression\n"
- labelsFormat = "%4.1f mm"
-
- # Create color node
- colorNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLProceduralColorNode")
- colorNode.UnRegister(None) # to prevent memory leaks
- colorNode.SetName(slicer.mrmlScene.GenerateUniqueName("MyColormap"))
- colorNode.SetAttribute("Category", "MyModule")
- # The color node is a procedural color node, which is saved using a storage node.
- # Hidden nodes are not saved if they use a storage node, therefore
- # the color node must be visible.
- colorNode.SetHideFromEditors(False)
- slicer.mrmlScene.AddNode(colorNode)
-
- # Specify colormap
- colorMap = colorNode.GetColorTransferFunction()
- colorMap.RemoveAllPoints()
- colorMap.AddRGBPoint(colorTableRangeMm * 0.0, 0.0, 0.0, 1.0)
- colorMap.AddRGBPoint(colorTableRangeMm * 0.2, 0.0, 1.0, 1.0)
- colorMap.AddRGBPoint(colorTableRangeMm * 0.5, 1.0, 1.0, 0.0)
- colorMap.AddRGBPoint(colorTableRangeMm * 1.0, 1.0, 0.0, 0.0)
-
- # Display color scalar bar
- colorWidget = slicer.modules.colors.widgetRepresentation()
- colorWidget.setCurrentColorNode(colorNode)
- ctkScalarBarWidget = slicer.util.findChildren(colorWidget, name="VTKScalarBar")[0]
- ctkScalarBarWidget.setDisplay(1)
- ctkScalarBarWidget.setTitle(title)
- ctkScalarBarWidget.setMaxNumberOfColors(256)
- ctkScalarBarWidget.setLabelsFormat(labelsFormat)
-
-Customize view layout
-^^^^^^^^^^^^^^^^^^^^^
-
-Show a custom layout of a 3D view on top of the red slice view:
-
-.. code-block:: python
-
- customLayout = """
-
-
-
- 1
-
-
-
-
- Axial
- R
- #F34A33
-
-
-
- """
-
- # Built-in layout IDs are all below 100, so you can choose any large random number
- # for your custom layout ID.
- customLayoutId=501
-
- layoutManager = slicer.app.layoutManager()
- layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)
-
- # Switch to the new custom layout
- layoutManager.setLayout(customLayoutId)
-
-See description of standard layouts (that can be used as examples) here: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx
-
-You can use this code snippet to add a button to the layout selector toolbar:
-
-.. code-block:: python
-
- # Add button to layout selector toolbar for this custom layout
- viewToolBar = mainWindow().findChild("QToolBar", "ViewToolBar")
- layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()
- layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list
- layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list
- layoutSwitchAction.setData(layoutId)
- layoutSwitchAction.setIcon(qt.QIcon(":Icons/Go.png"))
- layoutSwitchAction.setToolTip("3D and slice view")
-
-Turn on slice intersections
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- viewNodes = slicer.util.getNodesByClass("vtkMRMLSliceCompositeNode")
- for viewNode in viewNodes:
- viewNode.SetSliceIntersectionVisibility(1)
-
-.. note::
-
- How to find code corresponding to a user interface widget?
-
- For this one I searched for "slice intersections" text in the whole slicer source code, found that the function is implemented in ``Base\QTGUI\qSlicerViewersToolBar.cxx``, then translated the ``qSlicerViewersToolBarPrivate::setSliceIntersectionVisible(bool visible)`` method to Python.
-
-Hide slice view annotations
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This script can hide node name, patient information displayed in corners of slice views (managed by DataProbe module).
-
-.. code-block:: python
-
- # Disable slice annotations immediately
- sliceAnnotations = slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations
- sliceAnnotations.sliceViewAnnotationsEnabled=False
- sliceAnnotations.updateSliceViewFromGUI()
- # Disable slice annotations persistently (after Slicer restarts)
- settings = qt.QSettings()
- settings.setValue("DataProbe/sliceViewAnnotations.enabled", 0)
-
-Change slice offset
-^^^^^^^^^^^^^^^^^^^
-
-Equivalent to moving the slider in slice view controller.
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- red = layoutManager.sliceWidget("Red")
- redLogic = red.sliceLogic()
- # Print current slice offset position
- print(redLogic.GetSliceOffset())
- # Change slice position
- redLogic.SetSliceOffset(20)
-
-Change slice orientation
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Get ``Red`` slice node and rotate around ``X`` and ``Y`` axes.
-
-.. code-block:: python
-
- sliceNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
- sliceToRas = sliceNode.GetSliceToRAS()
- transform=vtk.vtkTransform()
- transform.SetMatrix(SliceToRAS)
- transform.RotateX(20)
- transform.RotateY(15)
- sliceToRas.DeepCopy(transform.GetMatrix())
- sliceNode.UpdateMatrices()
-
-Measure angle between two slice planes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.
-
-.. code-block:: python
-
- sliceNodeIds = ["vtkMRMLSliceNodeRed", "vtkMRMLSliceNodeYellow"]
-
- # Print angles between slice nodes
- def ShowAngle(unused1=None, unused2=None):
- sliceNormalVector = []
- for sliceNodeId in sliceNodeIds:
- sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()
- sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])
- angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])
- angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
- print("Angle between slice planes = {0:0.3f}".format(angleDeg))
-
- # Observe slice node changes
- for sliceNodeId in sliceNodeIds:
- slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)
-
- # Print current angle
- ShowAngle()
-
-Set slice position and orientation from a normal vector and position
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code snippet shows how to display a slice view defined by a normal vector and position in an anatomically sensible way: rotating slice view so that "up" direction (or "right" direction) is towards an anatomical axis.
-
-.. code-block:: python
-
- def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):
- """
- Set slice pose from the provided plane normal and position. View up direction is determined automatically,
- to make view up point towards defaultViewUpDirection.
- :param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.
- :param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction
- if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.
- """
- # Fix up input directions
- if defaultViewUpDirection is None:
- defaultViewUpDirection = [0,0,1]
- if backupViewRightDirection is None:
- backupViewRightDirection = [-1,0,0]
- if sliceNormal[1]>=0:
- sliceNormalStandardized = sliceNormal
- else:
- sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]
- # Compute slice axes
- sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)
- angleTooSmallThresholdRad = 0.25 # about 15 degrees
- if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:
- viewUpDirection = defaultViewUpDirection
- sliceAxisY = viewUpDirection
- sliceAxisX = [0, 0, 0]
- vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)
- else:
- sliceAxisX = backupViewRightDirection
- # Set slice axes
- sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],
- sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],
- slicePosition[0], slicePosition[1], slicePosition[2], 0)
-
- # Example usage:
- sliceNode = getNode("vtkMRMLSliceNodeRed")
- transformNode = getNode("Transform_3")
- transformMatrix = vtk.vtkMatrix4x4()
- transformNode.GetMatrixTransformToParent(transformMatrix)
- sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]
- slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]
- setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)
-
-Show slice views in 3D window
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Equivalent to clicking 'eye' icon in the slice view controller.
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- for sliceViewName in layoutManager.sliceViewNames():
- controller = layoutManager.sliceWidget(sliceViewName).sliceController()
- controller.setSliceVisible(True)
-
-Change default slice view orientation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can left-right "flip" slice view orientation presets (show patient left side on left/right side of the screen) by copy-pasting the script below to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-.. code-block:: python
-
- # Axial slice axes:
- # 1 0 0
- # 0 1 0
- # 0 0 1
- axialSliceToRas=vtk.vtkMatrix3x3()
-
- # Coronal slice axes:
- # 1 0 0
- # 0 0 -1
- # 0 1 0
- coronalSliceToRas=vtk.vtkMatrix3x3()
- coronalSliceToRas.SetElement(1,1, 0)
- coronalSliceToRas.SetElement(1,2, -1)
- coronalSliceToRas.SetElement(2,1, 1)
- coronalSliceToRas.SetElement(2,2, 0)
-
- # Replace orientation presets in all existing slice nodes and in the default slice node
- sliceNodes = slicer.util.getNodesByClass("vtkMRMLSliceNode")
- sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass("vtkMRMLSliceNode"))
- for sliceNode in sliceNodes:
- orientationPresetName = sliceNode.GetOrientation()
- sliceNode.RemoveSliceOrientationPreset("Axial")
- sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)
- sliceNode.RemoveSliceOrientationPreset("Coronal")
- sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)
- sliceNode.SetOrientation(orientationPresetName)
-
-Set all slice views linked by default
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-.. code-block:: python
-
- # Set linked slice views in all existing slice composite nodes and in the default node
- sliceCompositeNodes = slicer.util.getNodesByClass("vtkMRMLSliceCompositeNode")
- defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass("vtkMRMLSliceCompositeNode")
- if not defaultSliceCompositeNode:
- defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass("vtkMRMLSliceCompositeNode")
- defaultSliceCompositeNode.UnRegister(None) # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks
- slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)
- sliceCompositeNodes.append(defaultSliceCompositeNode)
- for sliceCompositeNode in sliceCompositeNodes:
- sliceCompositeNode.SetLinkedControl(True)
-
-Set crosshair jump mode to centered by default
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-.. code-block:: python
-
- crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")
- crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)
-
-Set up custom units in slice view ruler
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For microscopy or micro-CT images you may want to switch unit to micrometer instead of the default mm. To do that, 1. change the unit in Application settings / Units and 2. update ruler display settings using the script below (it can be copied to your Application startup script):
-
-.. code-block:: python
-
- lm = slicer.app.layoutManager()
- for sliceViewName in lm.sliceViewNames():
- sliceView = lm.sliceWidget(sliceViewName).sliceView()
- displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")
- displayableManager.RemoveAllRulerScalePresets()
- displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)
- displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)
- displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)
- displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)
- displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)
- displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)
- displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)
- displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)
- displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)
- displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)
- displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)
-
-Center the 3D view on the scene
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- threeDWidget = layoutManager.threeDWidget(0)
- threeDView = threeDWidget.threeDView()
- threeDView.resetFocalPoint()
-
-Rotate the 3D View
-^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- threeDWidget = layoutManager.threeDWidget(0)
- threeDView = threeDWidget.threeDView()
- threeDView.yaw()
-
-Change 3D view background color
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()
- viewNode.SetBackgroundColor(1,0,0)
- viewNode.SetBackgroundColor2(1,0,0)
-
-Show a slice view outside the view layout
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # layout name is used to create and identify the underlying slice node and should be set to a value that is not used in any of the layouts owned by the layout manager
- layoutName = "TestSlice1"
- layoutLabel = "TS1"
- layoutColor = [1.0, 1.0, 0.0]
- # ownerNode manages this view instead of the layout manager (it can be any node in the scene)
- viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
-
- # Create MRML nodes
- viewLogic = slicer.vtkMRMLSliceLogic()
- viewLogic.SetMRMLScene(slicer.mrmlScene)
- viewNode = viewLogic.AddSliceNode(layoutName)
- viewNode.SetLayoutLabel(layoutLabel)
- viewNode.SetLayoutColor(layoutColor)
- viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
-
- # Create widget
- viewWidget = slicer.qMRMLSliceWidget()
- viewWidget.setMRMLScene(slicer.mrmlScene)
- viewWidget.setMRMLSliceNode(viewNode)
- sliceLogics = slicer.app.applicationLogic().GetSliceLogics()
- viewWidget.setSliceLogics(sliceLogics)
- sliceLogics.AddItem(viewWidget.sliceLogic())
- viewWidget.show()
-
-Show a 3D view outside the view layout
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # layout name is used to create and identify the underlying view node and should be set to a value that is not used in any of the layouts owned by the layout manager
- layoutName = "Test3DView"
- layoutLabel = "T3"
- layoutColor = [1.0, 1.0, 0.0]
- # ownerNode manages this view instead of the layout manager (it can be any node in the scene)
- viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")
-
- # Create MRML node
- viewLogic = slicer.vtkMRMLViewLogic()
- viewLogic.SetMRMLScene(slicer.mrmlScene)
- viewNode = viewLogic.AddViewNode(layoutName)
- viewNode.SetLayoutLabel(layoutLabel)
- viewNode.SetLayoutColor(layoutColor)
- viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())
-
- # Create widget
- viewWidget = slicer.qMRMLThreeDWidget()
- viewWidget.setMRMLScene(slicer.mrmlScene)
- viewWidget.setMRMLViewNode(viewNode)
- viewWidget.show()
-
-Access VTK rendering classes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Accesss VTK views, renderers, and cameras
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Iterate through all 3D views in current layout:
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- for threeDViewIndex in range(layoutManager.threeDViewCount) :
- view = layoutManager.threeDWidget(threeDViewIndex).threeDView()
- threeDViewNode = view.mrmlViewNode()
- cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)
- print("View node for 3D widget " + str(threeDViewIndex))
- print(" Name: " + threeDViewNode .GetName())
- print(" ID: " + threeDViewNode .GetID())
- print(" Camera ID: " + cameraNode.GetID())
-
-Iterate through all slice views in current layout:
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- for sliceViewName in layoutManager.sliceViewNames():
- view = layoutManager.sliceWidget(sliceViewName).sliceView()
- sliceNode = view.mrmlSliceNode()
- sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
- compositeNode = sliceLogic.GetSliceCompositeNode()
- print("Slice view " + str(sliceViewName))
- print(" Name: " + sliceNode.GetName())
- print(" ID: " + sliceNode.GetID())
- print(" Background volume: {0}".format(compositeNode.GetBackgroundVolumeID()))
- print(" Foreground volume: {0} (opacity: {1})".format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))
- print(" Label volume: {0} (opacity: {1})".format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))
-
-For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.
-
-.. code-block:: python
-
- renderWindow = view.renderWindow()
- renderers = renderWindow.GetRenderers()
- renderer = renderers.GetItemAsObject(0)
- camera = cameraNode.GetCamera()
-
-Get displayable manager of a certain type for a certain view
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Displayable managers are responsible for creating VTK filters, mappers, and actors to display MRML nodes in renderers. Input to filters and mappers are VTK objects stored in MRML data nodes. Filter and actor properties are set based on display options specified in MRML display nodes.
-
-Accessing displayable managers is useful for troubleshooting or for testing new features that are not exposed via MRML classes yet, as they provide usually allow low-level access to VTK actors.
-
-.. code-block:: python
-
- threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
- modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLModelDisplayableManager")
- if modelDisplayableManager is None:
- logging.error("Failed to find the model displayable manager")
-
-Access VTK actor properties
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to access and modify VTK actor properties to experiment with physically-based rendering.
-
-.. code-block:: python
-
- modelNode = slicer.util.getNode("MyModel")
-
- threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
- modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLModelDisplayableManager")
- actor=modelDisplayableManager.GetActorByID(modelNode.GetDisplayNode().GetID())
- property=actor.GetProperty()
- property.SetInterpolationToPBR()
- property.SetMetallic(0.5)
- property.SetRoughness(0.5)
- property.SetColor(0.5,0.5,0.9)
- slicer.util.forceRenderAllViews()
-
-See more information on physically based rendering in VTK here: https://blog.kitware.com/vtk-pbr/
-
-
-Keyboard shortcuts and mouse gestures
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Customize keyboard shortcuts
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-For example, this script registers *Ctrl+b*, *Ctrl+n*, *Ctrl+m*, *Ctrl+,* keyboard shortcuts to switch between red, yellow, green, and 4-up view layouts.
-
-.. code-block:: python
-
- shortcuts = [
- ("Ctrl+b", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),
- ("Ctrl+n", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),
- ("Ctrl+m", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),
- ("Ctrl+,", lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))
- ]
-
- for (shortcutKey, callback) in shortcuts:
- shortcut = qt.QShortcut(slicer.util.mainWindow())
- shortcut.setKey(qt.QKeySequence(shortcutKey))
- shortcut.connect( "activated()", callback)
-
-Here's an example for cycling through Segment Editor effects (requested `on the forum `__ for the `SlicerMorph `__ project).
-
-.. code-block:: python
-
- def cycleEffect(delta=1):
- try:
- orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())
- allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()
- for name in allNames:
- try:
- orderedNames.index(name)
- except ValueError:
- orderedNames.append(name)
- orderedNames.insert(0, None)
- activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()
- if activeEffect:
- activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name
- else:
- activeName = None
- newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)
- slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])
- except AttributeError:
- # module not active
- pass
-
- shortcuts = [
- ("`", lambda: cycleEffect(-1)),
- ("~", lambda: cycleEffect(1)),
- ]
-
- for (shortcutKey, callback) in shortcuts:
- shortcut = qt.QShortcut(slicer.util.mainWindow())
- shortcut.setKey(qt.QKeySequence(shortcutKey))
- shortcut.connect( "activated()", callback)
-
-Customize keyboard/mouse gestures in viewers
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Example for making the 3D view rotate using right-click-and-drag:
-
-.. code-block:: python
-
- threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
- cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName("vtkMRMLCameraDisplayableManager")
- cameraWidget = cameraDisplayableManager.GetCameraWidget()
-
- # Remove old mapping from right-click-and-drag
- cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
- cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)
-
- # Make right-click-and-drag rotate the view
- cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,
- cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)
-
-Disable certain user interactions in slice views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:
-
-.. code-block:: python
-
- interactorStyle = slicer.app.layoutManager().sliceWidget("Red").sliceView().sliceViewInteractorStyle()
- interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)
-
-Hide all slice view controllers:
-
-.. code-block:: python
-
- lm = slicer.app.layoutManager()
- for sliceViewName in lm.sliceViewNames():
- lm.sliceWidget(sliceViewName).sliceController().setVisible(False)
-
-Hide all 3D view controllers:
-
-.. code-block:: python
-
- lm = slicer.app.layoutManager()
- for viewIndex in range(slicer.app.layoutManager().threeDViewCount):
- lm.threeDWidget(0).threeDController().setVisible(False)
-
-Add keyboard shortcut to jump to center or world coordinate system
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can copy-paste this into the Python console to jump slice views to (0,0,0) position on (Ctrl+e):
-
-.. code-block:: python
-
- shortcut = qt.QShortcut(qt.QKeySequence("Ctrl+e"), slicer.util.mainWindow())
- shortcut.connect("activated()",
- lambda: slicer.modules.markups.logic().JumpSlicesToLocation(0,0,0, True))
-
-Launch external applications
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-How to run external applications from Slicer.
-
-Launch external process in startup environment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When a process is launched from Slicer then by default Slicer"s ITK, VTK, Qt, etc. libraries are used. If an external application has its own version of these libraries, then the application is expected to crash. To prevent crashing, the application must be run in the environment where Slicer started up (without all Slicer-specific library paths). This startup environment can be retrieved using :func:`slicer.util.startupEnvironment()`.
-
-Example: run Python3 script from Slicer:
-
-.. code-block:: python
-
- command_to_execute = ["/usr/bin/python3", "-c", "print("hola")"]
- from subprocess import check_output
- check_output(
- command_to_execute,
- env=slicer.util.startupEnvironment()
- )
-
-will output:
-
-.. code-block:: python
-
- "hola\n"
-
-On some systems, *shell=True* must be specified as well.
-
-Manage extensions
-~~~~~~~~~~~~~~~~~
-
-Download and install extension
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- extensionName = 'SlicerIGT'
- em = slicer.app.extensionsManagerModel()
- if not em.isExtensionInstalled(extensionName):
- extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)
- url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']
- extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']
- slicer.util.downloadFile(url, extensionPackageFilename)
- em.installExtension(extensionPackageFilename)
- slicer.util.restart()
-
-Install a module directly from a git repository
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This `code snippet `__ can be useful for sharing code in development without requiring a restart of Slicer.
-
-Install a Python package
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Python packages that are optional or would be impractical to bundle into the extension can be installed at runtime. It is recommended to only install a package when it is actually needed (not at startup, not even when the user opens a module, but just before that Python package is used the first time), and ask the user about it (if it is more than just a few megabytes).
-
-.. code-block:: python
-
- try:
- import flywheel
- except ModuleNotFoundError as e:
- if slicer.util.confirmOkCancelDisplay("This module requires 'flywheel-sdk' Python package. Click OK to install it now."):
- slicer.util.pip_install("flywheel-sdk")
- import flywheel
diff --git a/Docs/developer_guide/script_repository/markups.md b/Docs/developer_guide/script_repository/markups.md
new file mode 100644
index 00000000000..dcc58fdf42f
--- /dev/null
+++ b/Docs/developer_guide/script_repository/markups.md
@@ -0,0 +1,637 @@
+## Markups
+
+### Load markups fiducial list from file
+
+Markups fiducials can be loaded from file:
+
+```python
+slicer.util.loadMarkupsFiducialList("/path/to/list/F.fcsv")
+```
+
+### Adding Fiducials Programatically
+
+Markups fiducials can be added to the currently active list from the python console by using the following module logic command:
+
+```python
+slicer.modules.markups.logic().AddFiducial()
+```
+
+The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:
+
+```python
+slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)
+```
+
+### How to draw a curve using control points stored in a numpy array
+
+```python
+# Create random numpy array to use as input
+import numpy as np
+pointPositions = np.random.uniform(-50,50,size=[15,3])
+
+# Create curve from numpy array
+curveNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsCurveNode")
+slicer.util.updateMarkupsControlPointsFromArray(curveNode, pointPositions)
+```
+
+### Add a button to module GUI to activate fiducial placement
+
+This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).
+
+The [qSlicerMarkupsPlaceWidget widget](http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html) can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.
+
+```python
+w=slicer.qSlicerMarkupsPlaceWidget()
+w.setMRMLScene(slicer.mrmlScene)
+markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()
+w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))
+# Hide all buttons and only show place button
+w.buttonsVisible=False
+w.placeButton().show()
+w.show()
+```
+
+### Adding Fiducials via mouse clicks
+
+You can also set the mouse mode into Markups fiducial placement by calling:
+
+```python
+placeModePersistence = 1
+slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)
+```
+
+A lower level way to do this is via the selection and interaction nodes:
+
+```python
+selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")
+selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
+interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
+placeModePersistence = 1
+interactionNode.SetPlaceModePersistence(placeModePersistence)
+# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place
+interactionNode.SetCurrentInteractionMode(1)
+```
+
+To switch back to view transform once you're done placing fiducials:
+
+```python
+interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
+interactionNode.SwitchToViewTransformMode()
+# also turn off place mode persistence if required
+interactionNode.SetPlaceModePersistence(0)
+```
+
+### Access to Fiducial Properties
+
+Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:
+
+```python
+fidNode = getNode("vtkMRMLMarkupsFiducialNode1")
+n = fidNode.AddFiducial(4.0, 5.5, -6.0)
+fidNode.SetNthFiducialLabel(n, "new label")
+# each markup is given a unique id which can be accessed from the superclass level
+id1 = fidNode.GetNthMarkupID(n)
+# manually set the position
+fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)
+# set the label
+fidNode.SetNthFiducialLabel(n, "New label")
+# set the selected flag, only selected = 1 fiducials will be passed to CLIs
+fidNode.SetNthFiducialSelected(n, 1)
+# set the visibility flag
+fidNode.SetNthFiducialVisibility(n, 0)
+```
+
+You can loop over the fiducials in a list and get the coordinates:
+
+```python
+fidList = slicer.util.getNode("F")
+numFids = fidList.GetNumberOfFiducials()
+for i in range(numFids):
+ ras = [0,0,0]
+ fidList.GetNthFiducialPosition(i,ras)
+ # the world position is the RAS position with any transform matrices applied
+ world = [0,0,0,0]
+ fidList.GetNthFiducialWorldCoordinates(0,world)
+ print(i,": RAS =",ras,", world =",world)
+```
+
+You can also look at the sample code in the [Endoscopy module](https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287) to see how python is used to access fiducials from a scripted module.
+
+### Define/edit a circular region of interest in a slice viewer
+
+Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.
+
+```python
+# Update the sphere from the fiducial points
+def UpdateSphere(param1, param2):
+ """Update the sphere from the fiducial points
+ """
+ import math
+ centerPointCoord = [0.0, 0.0, 0.0]
+ markups.GetNthFiducialPosition(0,centerPointCoord)
+ circumferencePointCoord = [0.0, 0.0, 0.0]
+ markups.GetNthFiducialPosition(1,circumferencePointCoord)
+ sphere.SetCenter(centerPointCoord)
+ radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)
+ sphere.SetRadius(radius)
+ sphere.SetPhiResolution(30)
+ sphere.SetThetaResolution(30)
+ sphere.Update()
+
+# Get markup node from scene
+markups=slicer.util.getNode("F")
+sphere = vtk.vtkSphereSource()
+UpdateSphere(0,0)
+
+# Create model node and add to scene
+modelsLogic = slicer.modules.models.logic()
+model = modelsLogic.AddModel(sphere.GetOutput())
+model.GetDisplayNode().SetSliceIntersectionVisibility(True)
+model.GetDisplayNode().SetSliceIntersectionThickness(3)
+model.GetDisplayNode().SetColor(1,1,0)
+
+# Call UpdateSphere whenever the fiducials are changed
+markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)
+```
+
+### Specify a sphere by multiple of markups points
+
+Drop multiple markup points at the boundary of the spherical object and and copy-paste the code below into the Python console to get best-fit sphere. Minimum 4 points are required, it is recommended to place the points far from each other for most accurate fit.
+
+```python
+# Get markup node from scene
+markups = slicer.util.getNode("F")
+
+from scipy.optimize import least_squares
+import numpy
+
+def fit_sphere_least_squares(x_values, y_values, z_values, initial_parameters, bounds=((-numpy.inf, -numpy.inf, -numpy.inf, -numpy.inf),(numpy.inf, numpy.inf, numpy.inf, numpy.inf))):
+ """
+ Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
+ Uses scipy's least squares optimisor to fit a sphere to a set
+ of 3D Points
+ :return: x: an array containing the four fitted parameters
+ :return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the
+ solution was found.
+ :param: (x,y,z) three arrays of equal length containing the x, y, and z
+ coordinates.
+ :param: an array containing four initial values (centre, and radius)
+ """
+ return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method="trf", jac="3-point", args=(x_values, y_values, z_values))
+
+def _calculate_residual_sphere(parameters, x_values, y_values, z_values):
+ """
+ Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
+ Calculates the residual error for an x,y,z coordinates, fitted
+ to a sphere with centre and radius defined by the parameters tuple
+ :return: The residual error
+ :param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]
+ :param: arrays containing the x,y, and z coordinates.
+ """
+ #extract the parameters
+ x_centre, y_centre, z_centre, radius = parameters
+ #use numpy's sqrt function here, which works by element on arrays
+ distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)
+ return distance_from_centre - radius
+
+# Fit a sphere to the markups fidicual points
+markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)
+import numpy as np
+# initial guess
+center0 = np.mean(markupsPositions, 0)
+radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0
+fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])
+[centerX, centerY, centerZ, radius] = fittingResult["x"]
+
+# Create a sphere using the fitted parameters
+sphere = vtk.vtkSphereSource()
+sphere.SetPhiResolution(30)
+sphere.SetThetaResolution(30)
+sphere.SetCenter(centerX, centerY, centerZ)
+sphere.SetRadius(radius)
+sphere.Update()
+
+# Add the sphere to the scene
+modelsLogic = slicer.modules.models.logic()
+model = modelsLogic.AddModel(sphere.GetOutput())
+model.GetDisplayNode().SetSliceIntersectionVisibility(True)
+model.GetDisplayNode().SetSliceIntersectionThickness(3)
+model.GetDisplayNode().SetColor(1,1,0)
+```
+
+### Measure angle between two markup planes
+
+Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.
+
+```python
+planeNodeNames = ["P", "P_1"]
+
+# Print angles between slice nodes
+def ShowAngle(unused1=None, unused2=None):
+ planeNormalVectors = []
+ for planeNodeName in planeNodeNames:
+ planeNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsPlaneNode", planeNodeName)
+ planeNormalVector = [0.0, 0.0, 0.0]
+ planeNode.GetNormalWorld(planeNormalVector)
+ planeNormalVectors.append(planeNormalVector)
+ angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])
+ angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
+ print("Angle between planes {0} and {1} = {2:0.3f}".format(planeNodeNames[0], planeNodeNames[1], angleDeg))
+
+# Observe plane node changes
+for planeNodeName in planeNodeNames:
+ planeNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsPlaneNode", planeNodeName)
+ planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)
+
+# Print current angle
+ShowAngle()
+```
+
+### Measure angle between two markup lines
+
+Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.
+
+```python
+lineNodeNames = ["L", "L_1"]
+
+# Print angles between slice nodes
+def ShowAngle(unused1=None, unused2=None):
+ import numpy as np
+ lineDirectionVectors = []
+ for lineNodeName in lineNodeNames:
+ lineNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsLineNode", lineNodeName)
+ lineStartPos = np.zeros(3)
+ lineEndPos = np.zeros(3)
+ lineNode.GetNthControlPointPositionWorld(0, lineStartPos)
+ lineNode.GetNthControlPointPositionWorld(1, lineEndPos)
+ lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)
+ lineDirectionVectors.append(lineDirectionVector)
+ angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])
+ angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
+ print("Angle between lines {0} and {1} = {2:0.3f}".format(lineNodeNames[0], lineNodeNames[1], angleDeg))
+
+# Observe line node changes
+for lineNodeName in lineNodeNames:
+ lineNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsLineNode", lineNodeName)
+ lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)
+
+# Print current angle
+ShowAngle()
+```
+
+### Set slice position and orientation from 3 markup fiducials
+
+Drop 3 markup points in the scene and copy-paste the code below into the Python console. After this, as you move the markups you’ll see the red slice view position and orientation will be set to make it fit to the 3 points.
+
+```python
+# Update plane from fiducial points
+def UpdateSlicePlane(param1=None, param2=None):
+ # Get point positions as numpy array
+ import numpy as np
+ nOfFiduciallPoints = markups.GetNumberOfFiducials()
+ if nOfFiduciallPoints < 3:
+ return # not enough points
+ points = np.zeros([3,nOfFiduciallPoints])
+ for i in range(0, nOfFiduciallPoints):
+ markups.GetNthFiducialPosition(i, points[:,i])
+ # Compute plane position and normal
+ planePosition = points.mean(axis=1)
+ planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])
+ planeX = points[:,1] - points[:,0]
+ sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],
+ planeX[0], planeX[1], planeX[2],
+ planePosition[0], planePosition[1], planePosition[2], 0)
+
+# Get markup node from scene
+sliceNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
+markups = slicer.util.getNode("F")
+
+# Update slice plane manually
+UpdateSlicePlane()
+
+# Update slice plane automatically whenever points are changed
+markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]
+```
+
+To stop automatic updates, run this:
+
+```python
+markupObservation[0].RemoveObserver(markupObservation[1])
+```
+
+### Switching to markup fiducial placement mode
+
+To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
+
+```python
+interactionNode = slicer.app.applicationLogic().GetInteractionNode()
+selectionNode = slicer.app.applicationLogic().GetSelectionNode()
+selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
+fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()
+slicer.mrmlScene.AddNode(fiducialNode)
+fiducialNode.CreateDefaultDisplayNodes()
+selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())
+interactionNode.SetCurrentInteractionMode(interactionNode.Place)
+```
+
+Alternatively, *qSlicerMarkupsPlaceWidget* widget can be used to initiate markup placement:
+
+```python
+# Temporary markups node
+markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
+
+def placementModeChanged(active):
+ print("Placement: " +("active" if active else "inactive"))
+ # You can inspect what is in the markups node here, delete the temporary markup node, etc.
+
+# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.
+placeWidget = slicer.qSlicerMarkupsPlaceWidget()
+placeWidget.setMRMLScene(slicer.mrmlScene)
+placeWidget.setCurrentNode(markupsNode)
+placeWidget.buttonsVisible=False
+placeWidget.placeButton().show()
+placeWidget.connect("activeMarkupsFiducialPlaceModeChanged(bool)", placementModeChanged)
+placeWidget.show()
+```
+
+### Change markup fiducial display properties
+
+Display properties are stored in display node(s) associated with the fiducial node.
+
+```python
+fiducialNode = getNode("F")
+fiducialDisplayNode = fiducialNode.GetDisplayNode()
+fiducialDisplayNode.SetVisibility(False) # Hide all points
+fiducialDisplayNode.SetVisibility(True) # Show all points
+fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow
+fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view
+```
+
+### Get a notification if a markup point position is modified
+
+Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
+
+```python
+def onMarkupChanged(caller,event):
+ markupsNode = caller
+ sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
+ movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
+ if movingMarkupIndex >= 0:
+ pos = [0,0,0]
+ markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)
+ isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview
+ if isPreview:
+ logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
+ else:
+ logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
+ else:
+ logging.info("Points modified: slice view = {0}".format(sliceView))
+
+def onMarkupStartInteraction(caller, event):
+ markupsNode = caller
+ sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
+ movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
+ logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))
+
+def onMarkupEndInteraction(caller, event):
+ markupsNode = caller
+ sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
+ movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
+ logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))
+
+markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
+markupsNode.CreateDefaultDisplayNodes()
+markupsNode.AddFiducial(0,0,0)
+markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)
+markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)
+markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)
+```
+
+### Write markup positions to JSON file
+
+```python
+markupNode = getNode("F")
+outputFileName = "c:/tmp/test.json"
+
+# Get markup positions
+data = []
+for fidIndex in range(markupNode.GetNumberOfFiducials()):
+ coords=[0,0,0]
+ markupNode.GetNthFiducialPosition(fidIndex,coords)
+ data.append({"label": markupNode.GetNthFiducialLabel(), "position": coords})
+
+import json
+with open(outputFileName, "w") as outfile:
+ json.dump(data, outfile)
+```
+
+### Write annotation ROI to JSON file
+
+```python
+roiNode = getNode("R")
+outputFileName = "c:/tmp/test.json"
+
+# Get annotation ROI data
+center = [0,0,0]
+radius = [0,0,0]
+roiNode.GetControlPointWorldCoordinates(0, center)
+roiNode.GetControlPointWorldCoordinates(1, radius)
+data = {"center": radius, "radius": radius}
+
+# Write to json file
+import json
+with open(outputFileName, "w") as outfile:
+ json.dump(data, outfile)
+```
+
+### Fit slice plane to markup fiducials
+
+```python
+sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
+# Get markup point positions as numpy arrays
+import numpy as np
+p1 = np.zeros(3)
+p2 = np.zeros(3)
+p3 = np.zeros(3)
+markupsNode.GetNthFiducialPosition(0, p1)
+markupsNode.GetNthFiducialPosition(1, p2)
+markupsNode.GetNthFiducialPosition(2, p3)
+# Get plane axis directions
+n = np.cross(p2-p1, p2-p3) # plane normal direction
+n = n/np.linalg.norm(n)
+t = np.cross([0.0, 0.0, 1], n) # plane transverse direction
+t = t/np.linalg.norm(t)
+# Set slice plane orientation and position
+sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
+```
+
+### Change color of a markups node
+
+Markups have `Color` and `SelectedColor` properties. `SelectedColor` is used if all control points are in "selected" state, which is the default. So, in most cases `SetSelectedColor` method must be used to change markups node color.
+
+### Display list of control points in my module's GUI
+
+The [qSlicerSimpleMarkupsWidget](http://apidocs.slicer.org/master/classqSlicerSimpleMarkupsWidget.html) can be integrated into module widgets to display list of markups control points and initiate placement. An example of this use is in [Gel Dosimetry module](https://www.slicer.org/wiki/Documentation/Nightly/Modules/GelDosimetry).
+
+### Pre-populate the scene with measurements
+
+This code snippet creates a set of predefined line markups (named A, B, C, D) in the scene when the user hits Ctrl+N.
+How to use this:
+
+1. Customize the code (replace A, B, C, D with your measurement names) and copy-paste the code into the Python console. This has to be done only once after Slicer is started. Add it to [.slicerrc.py file](../user_guide/settings.md#application-startup-file) so that it persists even if Slicer is restarted.
+2. Load the data set that has to be measured
+3. Hit Ctrl+N to create all the measurements
+4. Go to Markups module to see the list of measurements
+5. For each measurement: select it in the data tree, click on the place button on the toolbar then click in slice or 3D views
+
+```python
+sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+def createMeasurements():
+ for nodeName in ['A', 'B', 'C', 'D']:
+ lineNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", nodeName)
+ lineNode.CreateDefaultDisplayNodes()
+ dn = lineNode.GetDisplayNode()
+ # Use crosshair glyph to allow more accurate point placement
+ dn.SetGlyphTypeFromString("CrossDot2D")
+ # Hide measurement result while markup up
+ lineNode.GetMeasurement('length').SetEnabled(False)
+
+shortcut1 = qt.QShortcut(slicer.util.mainWindow())
+shortcut1.setKey(qt.QKeySequence("Ctrl+n"))
+shortcut1.connect( 'activated()', createMeasurements)
+```
+
+### Copy all measurements in the scene to Excel
+
+This code snippet creates a set of predefined line markups (named A, B, C, D) in the scene when the user hits Ctrl+N.
+How to use this:
+
+1. Copy-paste the code into the Python console. This has to be done only once after Slicer is started. Add it to [.slicerrc.py file](../user_guide/settings.md#application-startup-file) so that it persists even if Slicer is restarted.
+2. Load the data set that has to be measured and place line markups (you can use the "Pre-populate the scene with measurements" script above to help with this)
+3. Hit Ctrl+M to copy all line measurents to the clipboard
+4. Switch to Excel and hit Ctrl+V to paste the results there
+5. Save the scene, just in case later you need to review your measurements
+
+```python
+def copyLineMeasurementsToClipboard():
+ measurements = []
+ # Collect all line measurements from the scene
+ lineNodes = getNodesByClass('vtkMRMLMarkupsLineNode')
+ for lineNode in lineNodes:
+ # Get node filename that the length was measured on
+ try:
+ volumeNode = slicer.mrmlScene.GetNodeByID(lineNode.GetNthMarkupAssociatedNodeID(0))
+ imagePath = volumeNode.GetStorageNode().GetFileName()
+ except:
+ imagePath = ''
+ # Get line node n
+ measurementName = lineNode.GetName()
+ # Get length measurement
+ lineNode.GetMeasurement('length').SetEnabled(True)
+ length = str(lineNode.GetMeasurement('length').GetValue())
+ # Add fields to results
+ measurements.append('\t'.join([imagePath, measurementName, length]))
+ # Copy all measurements to clipboard (to be pasted into Excel)
+ slicer.app.clipboard().setText("\n".join(measurements))
+ slicer.util.delayDisplay(f"Copied {len(measurements)} length measurements to the clipboard.")
+
+shortcut2 = qt.QShortcut(slicer.util.mainWindow())
+shortcut2.setKey(qt.QKeySequence("Ctrl+m"))
+shortcut2.connect( 'activated()', copyLineMeasurementsToClipboard)
+```
+
+### Use markups json files in Python - outside Slicer
+
+The examples below show how to use markups json files outside Slicer, in any Python environment.
+
+To access content of a json file it can be either read as a json document or directly into a [pandas](https://pandas.pydata.org/) dataframe using a single command.
+
+#### Get a table of control point labels and positions
+
+Get table from the first markups node in the file:
+
+```python
+import pandas as pd
+controlPointsTable = pd.DataFrame.from_dict(pd.read_json(input_json_filename)['markups'][0]['controlPoints'])
+```
+
+Result:
+
+```python
+>>> controlPointsTable
+ label position
+0 F-1 [-53.388409961685824, -73.33572796934868, 0.0]
+1 F-2 [49.8682950191571, -88.58955938697324, 0.0]
+2 F-3 [-25.22749042145594, 59.255268199233726, 0.0]
+```
+
+#### Access position of control points positions in separate x, y, z columns
+
+```python
+controlPointsTable[['x','y','z']] = pd.DataFrame(controlPointsTable['position'].to_list())
+del controlPointsTable['position']
+```
+
+#### Write control points to a csv file
+
+```python
+controlPointsTable.to_csv(output_csv_filename)
+```
+
+Resulting csv file:
+
+```python
+ ,label,x,y,z
+ 0,F-1,-53.388409961685824,-73.33572796934868,0.0
+ 1,F-2,49.8682950191571,-88.58955938697324,0.0
+ 2,F-3,-25.22749042145594,59.255268199233726,0.0
+```
+
+### Assign custom actions to markups
+
+Custom actions can be assigned to markups, which can be triggered by any interaction event (mouse or keyboard action). The actions can be detected by adding observers to the markup node's display node.
+
+```python
+# This example adds an action to the default double-click action on a markup
+# and defines two new custom actions. It is done for all existing markups in the first 3D view.
+#
+# How to use:
+# 1. Create markups nodes.
+# 2. Run the script below.
+# 3. Double-click on the markup -> this triggers toggleLabelVisibilty.
+# 4. Hover the mouse over a markup then pressing `q` and `w` keys -> this triggers shrinkControlPoints and growControlPoints.
+
+threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
+markupsDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLMarkupsDisplayableManager')
+
+def shrinkControlPoints(caller, eventId):
+ markupsDisplayNode = caller
+ markupsDisplayNode.SetGlyphScale(markupsDisplayNode.GetGlyphScale()/1.1)
+
+def growControlPoints(caller, eventId):
+ markupsDisplayNode = caller
+ markupsDisplayNode.SetGlyphScale(markupsDisplayNode.GetGlyphScale()*1.1)
+
+def toggleLabelVisibility(caller, eventId):
+ markupsDisplayNode = caller
+ markupsDisplayNode.SetPointLabelsVisibility(not markupsDisplayNode.GetPointLabelsVisibility())
+
+observations = [] # store the observations so that later can be removed
+markupsDisplayNodes = slicer.util.getNodesByClass("vtkMRMLMarkupsDisplayNode")
+for markupsDisplayNode in markupsDisplayNodes:
+ # Assign keyboard shortcut to trigger custom actions
+ markupsWidget = markupsDisplayableManager.GetWidget(markupsDisplayNode)
+ # Left double-click interaction event is translated to markupsWidget.WidgetEventAction by default,
+ # therefore we don't need to add an event translation for that. We just add two keyboard event translation for two custom actions
+ markupsWidget.SetKeyboardEventTranslation(markupsWidget.WidgetStateOnWidget, vtk.vtkEvent.NoModifier, '\0', 0, "q", markupsWidget.WidgetEventCustomAction1)
+ markupsWidget.SetKeyboardEventTranslation(markupsWidget.WidgetStateOnWidget, vtk.vtkEvent.NoModifier, '\0', 0, "w", markupsWidget.WidgetEventCustomAction2)
+ # Add observer to custom actions
+ observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.ActionEvent, toggleLabelVisibility)])
+ observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.CustomActionEvent1, shrinkControlPoints)])
+ observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.CustomActionEvent2, growControlPoints)])
+
+# Remove observations when custom actions are not needed anymore by uncommenting these lines:
+for observedNode, observation in observations:
+ observedNode.RemoveObserver(observation)
+```
diff --git a/Docs/developer_guide/script_repository/markups.rst b/Docs/developer_guide/script_repository/markups.rst
deleted file mode 100644
index af55b426032..00000000000
--- a/Docs/developer_guide/script_repository/markups.rst
+++ /dev/null
@@ -1,668 +0,0 @@
-Markups
-~~~~~~~
-
-Load markups fiducial list from file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Markups fiducials can be loaded from file:
-
-.. code-block:: python
-
- slicer.util.loadMarkupsFiducialList("/path/to/list/F.fcsv")
-
-Adding Fiducials Programatically
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Markups fiducials can be added to the currently active list from the python console by using the following module logic command:
-
-.. code-block:: python
-
- slicer.modules.markups.logic().AddFiducial()
-
-The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:
-
-.. code-block:: python
-
- slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)
-
-How to draw a curve using control points stored in a numpy array
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Create random numpy array to use as input
- import numpy as np
- pointPositions = np.random.uniform(-50,50,size=[15,3])
-
- # Create curve from numpy array
- curveNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsCurveNode")
- slicer.util.updateMarkupsControlPointsFromArray(curveNode, pointPositions)
-
-Add a button to module GUI to activate fiducial placement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).
-
-The `qSlicerMarkupsPlaceWidget widget `__ can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.
-
-.. code-block:: python
-
- w=slicer.qSlicerMarkupsPlaceWidget()
- w.setMRMLScene(slicer.mrmlScene)
- markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()
- w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))
- # Hide all buttons and only show place button
- w.buttonsVisible=False
- w.placeButton().show()
- w.show()
-
-Adding Fiducials via mouse clicks
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can also set the mouse mode into Markups fiducial placement by calling:
-
-.. code-block:: python
-
- placeModePersistence = 1
- slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)
-
-A lower level way to do this is via the selection and interaction nodes:
-
-.. code-block:: python
-
- selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")
- selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
- interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
- placeModePersistence = 1
- interactionNode.SetPlaceModePersistence(placeModePersistence)
- # mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place
- interactionNode.SetCurrentInteractionMode(1)
-
-To switch back to view transform once you're done placing fiducials:
-
-.. code-block:: python
-
- interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
- interactionNode.SwitchToViewTransformMode()
- # also turn off place mode persistence if required
- interactionNode.SetPlaceModePersistence(0)
-
-Access to Fiducial Properties
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:
-
-.. code-block:: python
-
- fidNode = getNode("vtkMRMLMarkupsFiducialNode1")
- n = fidNode.AddFiducial(4.0, 5.5, -6.0)
- fidNode.SetNthFiducialLabel(n, "new label")
- # each markup is given a unique id which can be accessed from the superclass level
- id1 = fidNode.GetNthMarkupID(n)
- # manually set the position
- fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)
- # set the label
- fidNode.SetNthFiducialLabel(n, "New label")
- # set the selected flag, only selected = 1 fiducials will be passed to CLIs
- fidNode.SetNthFiducialSelected(n, 1)
- # set the visibility flag
- fidNode.SetNthFiducialVisibility(n, 0)
-
-You can loop over the fiducials in a list and get the coordinates:
-
-.. code-block:: python
-
- fidList = slicer.util.getNode("F")
- numFids = fidList.GetNumberOfFiducials()
- for i in range(numFids):
- ras = [0,0,0]
- fidList.GetNthFiducialPosition(i,ras)
- # the world position is the RAS position with any transform matrices applied
- world = [0,0,0,0]
- fidList.GetNthFiducialWorldCoordinates(0,world)
- print(i,": RAS =",ras,", world =",world)
-
-You can also look at the sample code in the `Endoscopy module `__ to see how python is used to access fiducials from a scripted module.
-
-Define/edit a circular region of interest in a slice viewer
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Drop two markup points on a slice view and copy-paste the code below into the Python console. After this, as you move the markups you’ll see a circle following the markups.
-
-.. code-block:: python
-
- # Update the sphere from the fiducial points
- def UpdateSphere(param1, param2):
- """Update the sphere from the fiducial points
- """
- import math
- centerPointCoord = [0.0, 0.0, 0.0]
- markups.GetNthFiducialPosition(0,centerPointCoord)
- circumferencePointCoord = [0.0, 0.0, 0.0]
- markups.GetNthFiducialPosition(1,circumferencePointCoord)
- sphere.SetCenter(centerPointCoord)
- radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)
- sphere.SetRadius(radius)
- sphere.SetPhiResolution(30)
- sphere.SetThetaResolution(30)
- sphere.Update()
-
- # Get markup node from scene
- markups=slicer.util.getNode("F")
- sphere = vtk.vtkSphereSource()
- UpdateSphere(0,0)
-
- # Create model node and add to scene
- modelsLogic = slicer.modules.models.logic()
- model = modelsLogic.AddModel(sphere.GetOutput())
- model.GetDisplayNode().SetSliceIntersectionVisibility(True)
- model.GetDisplayNode().SetSliceIntersectionThickness(3)
- model.GetDisplayNode().SetColor(1,1,0)
-
- # Call UpdateSphere whenever the fiducials are changed
- markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)
-
-Specify a sphere by multiple of markups points
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Drop multiple markup points at the boundary of the spherical object and and copy-paste the code below into the Python console to get best-fit sphere. Minimum 4 points are required, it is recommended to place the points far from each other for most accurate fit.
-
-.. code-block:: python
-
- # Get markup node from scene
- markups = slicer.util.getNode("F")
-
- from scipy.optimize import least_squares
- import numpy
-
- def fit_sphere_least_squares(x_values, y_values, z_values, initial_parameters, bounds=((-numpy.inf, -numpy.inf, -numpy.inf, -numpy.inf),(numpy.inf, numpy.inf, numpy.inf, numpy.inf))):
- """
- Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
- Uses scipy's least squares optimisor to fit a sphere to a set
- of 3D Points
- :return: x: an array containing the four fitted parameters
- :return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the
- solution was found.
- :param: (x,y,z) three arrays of equal length containing the x, y, and z
- coordinates.
- :param: an array containing four initial values (centre, and radius)
- """
- return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method="trf", jac="3-point", args=(x_values, y_values, z_values))
-
- def _calculate_residual_sphere(parameters, x_values, y_values, z_values):
- """
- Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py
- Calculates the residual error for an x,y,z coordinates, fitted
- to a sphere with centre and radius defined by the parameters tuple
- :return: The residual error
- :param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]
- :param: arrays containing the x,y, and z coordinates.
- """
- #extract the parameters
- x_centre, y_centre, z_centre, radius = parameters
- #use numpy's sqrt function here, which works by element on arrays
- distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)
- return distance_from_centre - radius
-
- # Fit a sphere to the markups fidicual points
- markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)
- import numpy as np
- # initial guess
- center0 = np.mean(markupsPositions, 0)
- radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0
- fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])
- [centerX, centerY, centerZ, radius] = fittingResult["x"]
-
- # Create a sphere using the fitted parameters
- sphere = vtk.vtkSphereSource()
- sphere.SetPhiResolution(30)
- sphere.SetThetaResolution(30)
- sphere.SetCenter(centerX, centerY, centerZ)
- sphere.SetRadius(radius)
- sphere.Update()
-
- # Add the sphere to the scene
- modelsLogic = slicer.modules.models.logic()
- model = modelsLogic.AddModel(sphere.GetOutput())
- model.GetDisplayNode().SetSliceIntersectionVisibility(True)
- model.GetDisplayNode().SetSliceIntersectionThickness(3)
- model.GetDisplayNode().SetColor(1,1,0)
-
-Measure angle between two markup planes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.
-
-.. code-block:: python
-
- planeNodeNames = ["P", "P_1"]
-
- # Print angles between slice nodes
- def ShowAngle(unused1=None, unused2=None):
- planeNormalVectors = []
- for planeNodeName in planeNodeNames:
- planeNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsPlaneNode", planeNodeName)
- planeNormalVector = [0.0, 0.0, 0.0]
- planeNode.GetNormalWorld(planeNormalVector)
- planeNormalVectors.append(planeNormalVector)
- angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])
- angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
- print("Angle between planes {0} and {1} = {2:0.3f}".format(planeNodeNames[0], planeNodeNames[1], angleDeg))
-
- # Observe plane node changes
- for planeNodeName in planeNodeNames:
- planeNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsPlaneNode", planeNodeName)
- planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)
-
- # Print current angle
- ShowAngle()
-
-Measure angle between two markup lines
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.
-
-.. code-block:: python
-
- lineNodeNames = ["L", "L_1"]
-
- # Print angles between slice nodes
- def ShowAngle(unused1=None, unused2=None):
- import numpy as np
- lineDirectionVectors = []
- for lineNodeName in lineNodeNames:
- lineNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsLineNode", lineNodeName)
- lineStartPos = np.zeros(3)
- lineEndPos = np.zeros(3)
- lineNode.GetNthControlPointPositionWorld(0, lineStartPos)
- lineNode.GetNthControlPointPositionWorld(1, lineEndPos)
- lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)
- lineDirectionVectors.append(lineDirectionVector)
- angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])
- angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)
- print("Angle between lines {0} and {1} = {2:0.3f}".format(lineNodeNames[0], lineNodeNames[1], angleDeg))
-
- # Observe line node changes
- for lineNodeName in lineNodeNames:
- lineNode = slicer.util.getFirstNodeByClassByName("vtkMRMLMarkupsLineNode", lineNodeName)
- lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)
-
- # Print current angle
- ShowAngle()
-
-Set slice position and orientation from 3 markup fiducials
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Drop 3 markup points in the scene and copy-paste the code below into the Python console. After this, as you move the markups you’ll see the red slice view position and orientation will be set to make it fit to the 3 points.
-
-.. code-block:: python
-
- # Update plane from fiducial points
- def UpdateSlicePlane(param1=None, param2=None):
- # Get point positions as numpy array
- import numpy as np
- nOfFiduciallPoints = markups.GetNumberOfFiducials()
- if nOfFiduciallPoints < 3:
- return # not enough points
- points = np.zeros([3,nOfFiduciallPoints])
- for i in range(0, nOfFiduciallPoints):
- markups.GetNthFiducialPosition(i, points[:,i])
- # Compute plane position and normal
- planePosition = points.mean(axis=1)
- planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])
- planeX = points[:,1] - points[:,0]
- sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],
- planeX[0], planeX[1], planeX[2],
- planePosition[0], planePosition[1], planePosition[2], 0)
-
- # Get markup node from scene
- sliceNode = slicer.app.layoutManager().sliceWidget("Red").mrmlSliceNode()
- markups = slicer.util.getNode("F")
-
- # Update slice plane manually
- UpdateSlicePlane()
-
- # Update slice plane automatically whenever points are changed
- markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]
-
-To stop automatic updates, run this:
-
-.. code-block:: python
-
- markupObservation[0].RemoveObserver(markupObservation[1])
-
-Switching to markup fiducial placement mode
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:
-
-.. code-block:: python
-
- interactionNode = slicer.app.applicationLogic().GetInteractionNode()
- selectionNode = slicer.app.applicationLogic().GetSelectionNode()
- selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")
- fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()
- slicer.mrmlScene.AddNode(fiducialNode)
- fiducialNode.CreateDefaultDisplayNodes()
- selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())
- interactionNode.SetCurrentInteractionMode(interactionNode.Place)
-
-Alternatively, *qSlicerMarkupsPlaceWidget* widget can be used to initiate markup placement:
-
-.. code-block:: python
-
- # Temporary markups node
- markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
-
- def placementModeChanged(active):
- print("Placement: " +("active" if active else "inactive"))
- # You can inspect what is in the markups node here, delete the temporary markup node, etc.
-
- # Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.
- placeWidget = slicer.qSlicerMarkupsPlaceWidget()
- placeWidget.setMRMLScene(slicer.mrmlScene)
- placeWidget.setCurrentNode(markupsNode)
- placeWidget.buttonsVisible=False
- placeWidget.placeButton().show()
- placeWidget.connect("activeMarkupsFiducialPlaceModeChanged(bool)", placementModeChanged)
- placeWidget.show()
-
-Change markup fiducial display properties
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Display properties are stored in display node(s) associated with the fiducial node.
-
-.. code-block:: python
-
- fiducialNode = getNode("F")
- fiducialDisplayNode = fiducialNode.GetDisplayNode()
- fiducialDisplayNode.SetVisibility(False) # Hide all points
- fiducialDisplayNode.SetVisibility(True) # Show all points
- fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow
- fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view
-
-Get a notification if a markup point position is modified
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.
-
-.. code-block:: python
-
- def onMarkupChanged(caller,event):
- markupsNode = caller
- sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
- movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
- if movingMarkupIndex >= 0:
- pos = [0,0,0]
- markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)
- isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview
- if isPreview:
- logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
- else:
- logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))
- else:
- logging.info("Points modified: slice view = {0}".format(sliceView))
-
- def onMarkupStartInteraction(caller, event):
- markupsNode = caller
- sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
- movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
- logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))
-
- def onMarkupEndInteraction(caller, event):
- markupsNode = caller
- sliceView = markupsNode.GetAttribute("Markups.MovingInSliceView")
- movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()
- logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))
-
- markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
- markupsNode.CreateDefaultDisplayNodes()
- markupsNode.AddFiducial(0,0,0)
- markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)
- markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)
- markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)
-
-Write markup positions to JSON file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- markupNode = getNode("F")
- outputFileName = "c:/tmp/test.json"
-
- # Get markup positions
- data = []
- for fidIndex in range(markupNode.GetNumberOfFiducials()):
- coords=[0,0,0]
- markupNode.GetNthFiducialPosition(fidIndex,coords)
- data.append({"label": markupNode.GetNthFiducialLabel(), "position": coords})
-
- import json
- with open(outputFileName, "w") as outfile:
- json.dump(data, outfile)
-
-Write annotation ROI to JSON file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- roiNode = getNode("R")
- outputFileName = "c:/tmp/test.json"
-
- # Get annotation ROI data
- center = [0,0,0]
- radius = [0,0,0]
- roiNode.GetControlPointWorldCoordinates(0, center)
- roiNode.GetControlPointWorldCoordinates(1, radius)
- data = {"center": radius, "radius": radius}
-
- # Write to json file
- import json
- with open(outputFileName, "w") as outfile:
- json.dump(data, outfile)
-
-Fit slice plane to markup fiducials
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
- markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
- # Get markup point positions as numpy arrays
- import numpy as np
- p1 = np.zeros(3)
- p2 = np.zeros(3)
- p3 = np.zeros(3)
- markupsNode.GetNthFiducialPosition(0, p1)
- markupsNode.GetNthFiducialPosition(1, p2)
- markupsNode.GetNthFiducialPosition(2, p3)
- # Get plane axis directions
- n = np.cross(p2-p1, p2-p3) # plane normal direction
- n = n/np.linalg.norm(n)
- t = np.cross([0.0, 0.0, 1], n) # plane transverse direction
- t = t/np.linalg.norm(t)
- # Set slice plane orientation and position
- sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)
-
-Change color of a markups node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Markups have ``Color`` and ``SelectedColor`` properties. ``SelectedColor`` is used if all control points are in "selected" state, which is the default. So, in most cases ``SetSelectedColor`` method must be used to change markups node color.
-
-Display list of control points in my module's GUI
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The `qSlicerSimpleMarkupsWidget `__ can be integrated into module widgets to display list of markups control points and initiate placement. An example of this use is in `Gel Dosimetry module `__.
-
-Pre-populate the scene with measurements
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code snippet creates a set of predefined line markups (named A, B, C, D) in the scene when the user hits Ctrl+N.
-How to use this:
-
-1. Customize the code (replace A, B, C, D with your measurement names) and copy-paste the code into the Python console. This has to be done only once after Slicer is started. Add it to `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__ so that it persists even if Slicer is restarted.
-2. Load the data set that has to be measured
-3. Hit Ctrl+N to create all the measurements
-4. Go to Markups module to see the list of measurements
-5. For each measurement: select it in the data tree, click on the place button on the toolbar then click in slice or 3D views
-
-.. code-block:: python
-
- sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
- def createMeasurements():
- for nodeName in ['A', 'B', 'C', 'D']:
- lineNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsLineNode", nodeName)
- lineNode.CreateDefaultDisplayNodes()
- dn = lineNode.GetDisplayNode()
- # Use crosshair glyph to allow more accurate point placement
- dn.SetGlyphTypeFromString("CrossDot2D")
- # Hide measurement result while markup up
- lineNode.GetMeasurement('length').SetEnabled(False)
-
- shortcut1 = qt.QShortcut(slicer.util.mainWindow())
- shortcut1.setKey(qt.QKeySequence("Ctrl+n"))
- shortcut1.connect( 'activated()', createMeasurements)
-
-Copy all measurements in the scene to Excel
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This code snippet creates a set of predefined line markups (named A, B, C, D) in the scene when the user hits Ctrl+N.
-How to use this:
-
-1. Copy-paste the code into the Python console. This has to be done only once after Slicer is started. Add it to `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__ so that it persists even if Slicer is restarted.
-2. Load the data set that has to be measured and place line markups (you can use the "Pre-populate the scene with measurements" script above to help with this)
-3. Hit Ctrl+M to copy all line measurents to the clipboard
-4. Switch to Excel and hit Ctrl+V to paste the results there
-5. Save the scene, just in case later you need to review your measurements
-
-.. code-block:: python
-
- def copyLineMeasurementsToClipboard():
- measurements = []
- # Collect all line measurements from the scene
- lineNodes = getNodesByClass('vtkMRMLMarkupsLineNode')
- for lineNode in lineNodes:
- # Get node filename that the length was measured on
- try:
- volumeNode = slicer.mrmlScene.GetNodeByID(lineNode.GetNthMarkupAssociatedNodeID(0))
- imagePath = volumeNode.GetStorageNode().GetFileName()
- except:
- imagePath = ''
- # Get line node n
- measurementName = lineNode.GetName()
- # Get length measurement
- lineNode.GetMeasurement('length').SetEnabled(True)
- length = str(lineNode.GetMeasurement('length').GetValue())
- # Add fields to results
- measurements.append('\t'.join([imagePath, measurementName, length]))
- # Copy all measurements to clipboard (to be pasted into Excel)
- slicer.app.clipboard().setText("\n".join(measurements))
- slicer.util.delayDisplay(f"Copied {len(measurements)} length measurements to the clipboard.")
-
- shortcut2 = qt.QShortcut(slicer.util.mainWindow())
- shortcut2.setKey(qt.QKeySequence("Ctrl+m"))
- shortcut2.connect( 'activated()', copyLineMeasurementsToClipboard)
-
-Use markups json files in Python - outside Slicer
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The examples below show how to use markups json files outside Slicer, in any Python environment.
-
-To access content of a json file it can be either read as a json document or directly into a `pandas `__ dataframe using a single command.
-
-Get a table of control point labels and positions
-'''''''''''''''''''''''''''''''''''''''''''''''''
-
-Get table from the first markups node in the file:
-
-.. code-block:: python
-
- import pandas as pd
- controlPointsTable = pd.DataFrame.from_dict(pd.read_json(input_json_filename)['markups'][0]['controlPoints'])
-
-Result:
-
-..
-
- >>> controlPointsTable
- label position
- 0 F-1 [-53.388409961685824, -73.33572796934868, 0.0]
- 1 F-2 [49.8682950191571, -88.58955938697324, 0.0]
- 2 F-3 [-25.22749042145594, 59.255268199233726, 0.0]
-
-
-Access position of control points positions in separate x, y, z columns
-'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- controlPointsTable[['x','y','z']] = pd.DataFrame(controlPointsTable['position'].to_list())
- del controlPointsTable['position']
-
-Write control points to a csv file
-''''''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- controlPointsTable.to_csv(output_csv_filename)
-
-Resulting csv file:
-
-::
-
- ,label,x,y,z
- 0,F-1,-53.388409961685824,-73.33572796934868,0.0
- 1,F-2,49.8682950191571,-88.58955938697324,0.0
- 2,F-3,-25.22749042145594,59.255268199233726,0.0
-
-Assign custom actions to markups
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Custom actions can be assigned to markups, which can be triggered by any interaction event (mouse or keyboard action). The actions can be detected by adding observers to the markup node's display node.
-
-.. code-block:: python
-
- # This example adds an action to the default double-click action on a markup
- # and defines two new custom actions. It is done for all existing markups in the first 3D view.
- #
- # How to use:
- # 1. Create markups nodes.
- # 2. Run the script below.
- # 3. Double-click on the markup -> this triggers toggleLabelVisibilty.
- # 4. Hover the mouse over a markup then pressing `q` and `w` keys -> this triggers shrinkControlPoints and growControlPoints.
-
- threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)
- markupsDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLMarkupsDisplayableManager')
-
- def shrinkControlPoints(caller, eventId):
- markupsDisplayNode = caller
- markupsDisplayNode.SetGlyphScale(markupsDisplayNode.GetGlyphScale()/1.1)
-
- def growControlPoints(caller, eventId):
- markupsDisplayNode = caller
- markupsDisplayNode.SetGlyphScale(markupsDisplayNode.GetGlyphScale()*1.1)
-
- def toggleLabelVisibility(caller, eventId):
- markupsDisplayNode = caller
- markupsDisplayNode.SetPointLabelsVisibility(not markupsDisplayNode.GetPointLabelsVisibility())
-
- observations = [] # store the observations so that later can be removed
- markupsDisplayNodes = slicer.util.getNodesByClass("vtkMRMLMarkupsDisplayNode")
- for markupsDisplayNode in markupsDisplayNodes:
- # Assign keyboard shortcut to trigger custom actions
- markupsWidget = markupsDisplayableManager.GetWidget(markupsDisplayNode)
- # Left double-click interaction event is translated to markupsWidget.WidgetEventAction by default,
- # therefore we don't need to add an event translation for that. We just add two keyboard event translation for two custom actions
- markupsWidget.SetKeyboardEventTranslation(markupsWidget.WidgetStateOnWidget, vtk.vtkEvent.NoModifier, '\0', 0, "q", markupsWidget.WidgetEventCustomAction1)
- markupsWidget.SetKeyboardEventTranslation(markupsWidget.WidgetStateOnWidget, vtk.vtkEvent.NoModifier, '\0', 0, "w", markupsWidget.WidgetEventCustomAction2)
- # Add observer to custom actions
- observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.ActionEvent, toggleLabelVisibility)])
- observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.CustomActionEvent1, shrinkControlPoints)])
- observations.append([markupsDisplayNode, markupsDisplayNode.AddObserver(markupsDisplayNode.CustomActionEvent2, growControlPoints)])
-
-Remove observations when custom actions are not needed anymore by uncommenting these lines:
-
-.. code-block:: python
-
- for observedNode, observation in observations:
- observedNode.RemoveObserver(observation)
diff --git a/Docs/developer_guide/script_repository/models.md b/Docs/developer_guide/script_repository/models.md
new file mode 100644
index 00000000000..5cdb7c51951
--- /dev/null
+++ b/Docs/developer_guide/script_repository/models.md
@@ -0,0 +1,329 @@
+## Models
+
+### Show a simple surface mesh as a model node
+
+This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
+
+```python
+# Create and set up polydata source
+box = vtk.vtkCubeSource()
+box.SetXLength(30)
+box.SetYLength(20)
+box.SetZLength(15)
+box.SetCenter(10,20,5)
+
+# Create a model node that displays output of the source
+boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
+
+# Adjust display properties
+boxNode.GetDisplayNode().SetColor(1,0,0)
+boxNode.GetDisplayNode().SetOpacity(0.8)
+```
+
+### Measure distance of points from surface
+
+This example computes closest distance of points (markups fiducial ``F``) from a surface (model node ``mymodel``) and writes results into a table.
+
+```python
+markupsNode = getNode("F")
+modelNode = getNode("mymodel")
+
+# Transform model polydata to world coordinate system
+if modelNode.GetParentTransformNode():
+ transformModelToWorld = vtk.vtkGeneralTransform()
+ slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)
+ polyTransformToWorld = vtk.vtkTransformPolyDataFilter()
+ polyTransformToWorld.SetTransform(transformModelToWorld)
+ polyTransformToWorld.SetInputData(modelNode.GetPolyData())
+ polyTransformToWorld.Update()
+ surface_World = polyTransformToWorld.GetOutput()
+else:
+ surface_World = modelNode.GetPolyData()
+
+# Create arrays to store results
+indexCol = vtk.vtkIntArray()
+indexCol.SetName("Index")
+labelCol = vtk.vtkStringArray()
+labelCol.SetName("Name")
+distanceCol = vtk.vtkDoubleArray()
+distanceCol.SetName("Distance")
+
+distanceFilter = vtk.vtkImplicitPolyDataDistance()
+distanceFilter.SetInput(surface_World);
+nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()
+for i in range(0, nOfFiduciallPoints):
+ point_World = [0,0,0]
+ markupsNode.GetNthControlPointPositionWorld(i, point_World)
+ closestPointOnSurface_World = [0,0,0]
+ closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)
+ indexCol.InsertNextValue(i)
+ labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))
+ distanceCol.InsertNextValue(closestPointDistance)
+
+# Create a table from result arrays
+resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")
+resultTableNode.AddColumn(indexCol)
+resultTableNode.AddColumn(labelCol)
+resultTableNode.AddColumn(distanceCol)
+
+# Show table in view layout
+slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)
+slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())
+slicer.app.applicationLogic().PropagateTableSelection()
+```
+
+### Add a texture mapped plane to the scene as a model
+
+```python
+# Create model node
+planeSource = vtk.vtkPlaneSource()
+planeSource.SetOrigin(-50.0, -50.0, 0.0)
+planeSource.SetPoint1(50.0, -50.0, 0.0)
+planeSource.SetPoint2(-50.0, 50.0, 0.0)
+model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())
+
+# Tune display properties
+modelDisplay = model.GetDisplayNode()
+modelDisplay.SetColor(1,1,0) # yellow
+modelDisplay.SetBackfaceCulling(0)
+
+# Add texture (just use image of an ellipsoid)
+e = vtk.vtkImageEllipsoidSource()
+modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())
+```
+
+:::{note}
+
+Model textures are not exposed in the GUI and are not saved in the scene.
+
+:::
+
+### Get scalar values at surface of a model
+
+The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.
+
+```python
+
+modelNode = getNode("sphere")
+modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
+markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
+
+if not markupsNode:
+ markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")
+
+pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
+pointsLocator.SetDataSet(modelNode.GetPolyData())
+pointsLocator.BuildLocator()
+
+def onMouseMoved(observer,eventid):
+ ras=[0,0,0]
+ crosshairNode.GetCursorPositionRAS(ras)
+ if markupsNode.GetNumberOfFiducials() == 0:
+ markupsNode.AddFiducial(*ras)
+ else:
+ markupsNode.SetNthFiducialPosition(0,*ras)
+ closestPointId = pointsLocator.FindClosestPoint(ras)
+ closestPointValue = modelPointValues.GetTuple(closestPointId)
+ print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))
+
+crosshairNode=slicer.util.getNode("Crosshair")
+observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
+
+# To stop printing of values run this:
+# crosshairNode.RemoveObserver(observationId)
+```
+
+### Apply VTK filter on a model node
+
+```python
+modelNode = getNode("tip")
+
+# Compute curvature
+curv = vtk.vtkCurvatures()
+curv.SetInputData(modelNode.GetPolyData())
+modelNode.SetPolyDataConnection(curv.GetOutputPort())
+
+# Set up coloring by Curvature
+modelNode.GetDisplayNode().SetActiveScalar("Gauss_Curvature", vtk.vtkAssignAttribute.POINT_DATA)
+modelNode.GetDisplayNode().SetAndObserveColorNodeID("Viridis")
+modelNode.GetDisplayNode().SetScalarVisibility(True)
+```
+
+### Select cells of a model using markups fiducial points
+
+The following script selects cells of a model node that are closest to positions of markups fiducial points.
+
+```python
+# Get input nodes
+modelNode = slicer.util.getNode("Segment_1") # select cells in this model
+markupsNode = slicer.util.getNode("F") # points will be selected at positions specified by this markups fiducial node
+
+# Create scalar array that will store selection state
+cellScalars = modelNode.GetMesh().GetCellData()
+selectionArray = cellScalars.GetArray("selection")
+if not selectionArray:
+ selectionArray = vtk.vtkIntArray()
+ selectionArray.SetName("selection")
+ selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())
+ selectionArray.Fill(0)
+ cellScalars.AddArray(selectionArray)
+
+# Set up coloring by selection array
+modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
+modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")
+modelNode.GetDisplayNode().SetScalarVisibility(True)
+
+# Initialize cell locator
+cell = vtk.vtkCellLocator()
+cell.SetDataSet(modelNode.GetMesh())
+cell.BuildLocator()
+
+def onPointsModified(observer=None, eventid=None):
+ global markupsNode, selectionArray
+ selectionArray.Fill(0) # set all cells to non-selected by default
+ markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)
+ closestPoint = [0.0, 0.0, 0.0]
+ cellObj = vtk.vtkGenericCell()
+ cellId = vtk.mutable(0)
+ subId = vtk.mutable(0)
+ dist2 = vtk.mutable(0.0)
+ for markupPoint in markupPoints:
+ cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)
+ closestCell = cellId.get()
+ if closestCell >=0:
+ selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero
+ selectionArray.Modified()
+
+# Initial update
+onPointsModified()
+# Automatic update each time when a markup point is modified
+markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)
+
+# To stop updating selection, run this:
+# markupsNode.RemoveObserver(markupsNodeObserverTag)
+```
+
+### Export entire scene as VRML
+
+Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
+
+```python
+exporter = vtk.vtkVRMLExporter()
+exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
+exporter.SetFileName("C:/tmp/something.wrl")
+exporter.Write()
+```
+
+### Export model to Blender, including color by scalar
+
+```python
+ modelNode = getNode("Model")
+ plyFilePath = "c:/tmp/model.ply"
+
+ modelDisplayNode = modelNode.GetDisplayNode()
+ triangles = vtk.vtkTriangleFilter()
+ triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())
+
+ plyWriter = vtk.vtkPLYWriter()
+ plyWriter.SetInputConnection(triangles.GetOutputPort())
+ lut = vtk.vtkLookupTable()
+ lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
+ lut.SetRange(modelDisplayNode.GetScalarRange())
+ plyWriter.SetLookupTable(lut)
+ plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())
+
+ plyWriter.SetFileName(plyFilePath)
+ plyWriter.Write()
+```
+
+### Show comparison view of all model files a folder
+
+```python
+# Inputs
+modelDir = "c:/some/folder/containing/models"
+modelFileExt = "stl"
+numberOfColumns = 4
+
+import math
+import os
+modelFiles = list(f for f in os.listdir(modelDir) if f.endswith("." + modelFileExt))
+
+# Create a custom layout
+numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))
+customLayoutId=567 # we pick a random id that is not used by others
+slicer.app.setRenderPaused(True)
+customLayout = ''
+viewIndex = 0
+for rowIndex in range(numberOfRows):
+ customLayout += ''
+ for colIndex in range(numberOfColumns):
+ name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare " + str(viewIndex)
+ customLayout += ''+name+''
+ viewIndex += 1
+ customLayout += ''
+
+customLayout += ''
+if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):
+ slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)
+
+slicer.app.layoutManager().setLayout(customLayoutId)
+
+# Load and show each model in a view
+for modelIndex, modelFile in enumerate(modelFiles):
+ # Show only one model in each view
+ name = os.path.basename(modelFile)
+ viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")
+ viewNode.LinkedControlOn()
+ modelNode = slicer.util.loadModel(modelDir + "/" + modelFile)
+ modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())
+
+slicer.app.setRenderPaused(False)
+```
+
+### Rasterize a model and save it to a series of image files
+
+This example shows how to generate a stack of image files from an STL file:
+
+```python
+inputModelFile = "/some/input/folder/SomeShape.stl"
+outputDir = "/some/output/folder"
+outputVolumeLabelValue = 100
+outputVolumeSpacingMm = [0.5, 0.5, 0.5]
+outputVolumeMarginMm = [10.0, 10.0, 10.0]
+
+# Read model
+inputModel = slicer.util.loadModel(inputModelFile)
+
+# Determine output volume geometry and create a corresponding reference volume
+import math
+import numpy as np
+bounds = np.zeros(6)
+inputModel.GetBounds(bounds)
+imageData = vtk.vtkImageData()
+imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]
+imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]
+imageData.SetDimensions(imageSize)
+imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
+imageData.GetPointData().GetScalars().Fill(0)
+referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
+referenceVolumeNode.SetOrigin(imageOrigin)
+referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)
+referenceVolumeNode.SetAndObserveImageData(imageData)
+referenceVolumeNode.CreateDefaultDisplayNodes()
+
+# Convert model to labelmap
+seg = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
+seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
+slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
+seg.CreateBinaryLabelmapRepresentation()
+outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)
+outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype("int8")
+
+# Write labelmap volume to series of TIFF files
+pip_install("imageio")
+import imageio
+for i in range(len(outputLabelmapVolumeArray)):
+ imageio.imwrite(f"{outputDir}/image_{i:03}.tiff", outputLabelmapVolumeArray[i])
+```
diff --git a/Docs/developer_guide/script_repository/models.rst b/Docs/developer_guide/script_repository/models.rst
deleted file mode 100644
index 18a5366de90..00000000000
--- a/Docs/developer_guide/script_repository/models.rst
+++ /dev/null
@@ -1,331 +0,0 @@
-Models
-~~~~~~
-
-Show a simple surface mesh as a model node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.
-
-.. code-block:: python
-
- # Create and set up polydata source
- box = vtk.vtkCubeSource()
- box.SetXLength(30)
- box.SetYLength(20)
- box.SetZLength(15)
- box.SetCenter(10,20,5)
-
- # Create a model node that displays output of the source
- boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())
-
- # Adjust display properties
- boxNode.GetDisplayNode().SetColor(1,0,0)
- boxNode.GetDisplayNode().SetOpacity(0.8)
-
-Measure distance of points from surface
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example computes closest distance of points (markups fiducial ``F``) from a surface (model node ``mymodel``) and writes results into a table.
-
-.. code-block:: python
-
- markupsNode = getNode("F")
- modelNode = getNode("mymodel")
-
- # Transform model polydata to world coordinate system
- if modelNode.GetParentTransformNode():
- transformModelToWorld = vtk.vtkGeneralTransform()
- slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)
- polyTransformToWorld = vtk.vtkTransformPolyDataFilter()
- polyTransformToWorld.SetTransform(transformModelToWorld)
- polyTransformToWorld.SetInputData(modelNode.GetPolyData())
- polyTransformToWorld.Update()
- surface_World = polyTransformToWorld.GetOutput()
- else:
- surface_World = modelNode.GetPolyData()
-
- # Create arrays to store results
- indexCol = vtk.vtkIntArray()
- indexCol.SetName("Index")
- labelCol = vtk.vtkStringArray()
- labelCol.SetName("Name")
- distanceCol = vtk.vtkDoubleArray()
- distanceCol.SetName("Distance")
-
- distanceFilter = vtk.vtkImplicitPolyDataDistance()
- distanceFilter.SetInput(surface_World);
- nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()
- for i in range(0, nOfFiduciallPoints):
- point_World = [0,0,0]
- markupsNode.GetNthControlPointPositionWorld(i, point_World)
- closestPointOnSurface_World = [0,0,0]
- closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)
- indexCol.InsertNextValue(i)
- labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))
- distanceCol.InsertNextValue(closestPointDistance)
-
- # Create a table from result arrays
- resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")
- resultTableNode.AddColumn(indexCol)
- resultTableNode.AddColumn(labelCol)
- resultTableNode.AddColumn(distanceCol)
-
- # Show table in view layout
- slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)
- slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())
- slicer.app.applicationLogic().PropagateTableSelection()
-
-Add a texture mapped plane to the scene as a model
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Note that model textures are not exposed in the GUI and are not saved in the scene
-
-.. code-block:: python
-
- # Create model node
- planeSource = vtk.vtkPlaneSource()
- planeSource.SetOrigin(-50.0, -50.0, 0.0)
- planeSource.SetPoint1(50.0, -50.0, 0.0)
- planeSource.SetPoint2(-50.0, 50.0, 0.0)
- model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())
-
- # Tune display properties
- modelDisplay = model.GetDisplayNode()
- modelDisplay.SetColor(1,1,0) # yellow
- modelDisplay.SetBackfaceCulling(0)
-
- # Add texture (just use image of an ellipsoid)
- e = vtk.vtkImageEllipsoidSource()
- modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())
-
-Get scalar values at surface of a model
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The following script allows getting selected scalar value at a selected position of a model. Position can be selected by moving the mouse while holding down Shift key.
-
-.. code-block:: python
-
- modelNode = getNode("sphere")
- modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")
- markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")
-
- if not markupsNode:
- markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")
-
- pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize
- pointsLocator.SetDataSet(modelNode.GetPolyData())
- pointsLocator.BuildLocator()
-
- def onMouseMoved(observer,eventid):
- ras=[0,0,0]
- crosshairNode.GetCursorPositionRAS(ras)
- if markupsNode.GetNumberOfFiducials() == 0:
- markupsNode.AddFiducial(*ras)
- else:
- markupsNode.SetNthFiducialPosition(0,*ras)
- closestPointId = pointsLocator.FindClosestPoint(ras)
- closestPointValue = modelPointValues.GetTuple(closestPointId)
- print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))
-
- crosshairNode=slicer.util.getNode("Crosshair")
- observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)
-
- # To stop printing of values run this:
- # crosshairNode.RemoveObserver(observationId)
-
-Apply VTK filter on a model node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- modelNode = getNode("tip")
-
- # Compute curvature
- curv = vtk.vtkCurvatures()
- curv.SetInputData(modelNode.GetPolyData())
- modelNode.SetPolyDataConnection(curv.GetOutputPort())
-
- # Set up coloring by Curvature
- modelNode.GetDisplayNode().SetActiveScalar("Gauss_Curvature", vtk.vtkAssignAttribute.POINT_DATA)
- modelNode.GetDisplayNode().SetAndObserveColorNodeID("Viridis")
- modelNode.GetDisplayNode().SetScalarVisibility(True)
-
-Select cells of a model using markups fiducial points
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The following script selects cells of a model node that are closest to positions of markups fiducial points.
-
-.. code-block:: python
-
- # Get input nodes
- modelNode = slicer.util.getNode("Segment_1") # select cells in this model
- markupsNode = slicer.util.getNode("F") # points will be selected at positions specified by this markups fiducial node
-
- # Create scalar array that will store selection state
- cellScalars = modelNode.GetMesh().GetCellData()
- selectionArray = cellScalars.GetArray("selection")
- if not selectionArray:
- selectionArray = vtk.vtkIntArray()
- selectionArray.SetName("selection")
- selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())
- selectionArray.Fill(0)
- cellScalars.AddArray(selectionArray)
-
- # Set up coloring by selection array
- modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)
- modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")
- modelNode.GetDisplayNode().SetScalarVisibility(True)
-
- # Initialize cell locator
- cell = vtk.vtkCellLocator()
- cell.SetDataSet(modelNode.GetMesh())
- cell.BuildLocator()
-
- def onPointsModified(observer=None, eventid=None):
- global markupsNode, selectionArray
- selectionArray.Fill(0) # set all cells to non-selected by default
- markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)
- closestPoint = [0.0, 0.0, 0.0]
- cellObj = vtk.vtkGenericCell()
- cellId = vtk.mutable(0)
- subId = vtk.mutable(0)
- dist2 = vtk.mutable(0.0)
- for markupPoint in markupPoints:
- cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)
- closestCell = cellId.get()
- if closestCell >=0:
- selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero
- selectionArray.Modified()
-
- # Initial update
- onPointsModified()
- # Automatic update each time when a markup point is modified
- markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)
-
- # To stop updating selection, run this:
- # markupsNode.RemoveObserver(markupsNodeObserverTag)
-
-Export entire scene as VRML
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.
-
-.. code-block:: python
-
- exporter = vtk.vtkVRMLExporter()
- exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())
- exporter.SetFileName("C:/tmp/something.wrl")
- exporter.Write()
-
-Export model to Blender, including color by scalar
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- modelNode = getNode("Model")
- plyFilePath = "c:/tmp/model.ply"
-
- modelDisplayNode = modelNode.GetDisplayNode()
- triangles = vtk.vtkTriangleFilter()
- triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())
-
- plyWriter = vtk.vtkPLYWriter()
- plyWriter.SetInputConnection(triangles.GetOutputPort())
- lut = vtk.vtkLookupTable()
- lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())
- lut.SetRange(modelDisplayNode.GetScalarRange())
- plyWriter.SetLookupTable(lut)
- plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())
-
- plyWriter.SetFileName(plyFilePath)
- plyWriter.Write()
-
-Show comparison view of all model files a folder
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Inputs
- modelDir = "c:/some/folder/containing/models"
- modelFileExt = "stl"
- numberOfColumns = 4
-
- import math
- import os
- modelFiles = list(f for f in os.listdir(modelDir) if f.endswith("." + modelFileExt))
-
- # Create a custom layout
- numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))
- customLayoutId=567 # we pick a random id that is not used by others
- slicer.app.setRenderPaused(True)
- customLayout = ''
- viewIndex = 0
- for rowIndex in range(numberOfRows):
- customLayout += ''
- for colIndex in range(numberOfColumns):
- name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare " + str(viewIndex)
- customLayout += ''+name+''
- viewIndex += 1
- customLayout += ''
-
- customLayout += ''
- if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):
- slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)
-
- slicer.app.layoutManager().setLayout(customLayoutId)
-
- # Load and show each model in a view
- for modelIndex, modelFile in enumerate(modelFiles):
- # Show only one model in each view
- name = os.path.basename(modelFile)
- viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")
- viewNode.LinkedControlOn()
- modelNode = slicer.util.loadModel(modelDir + "/" + modelFile)
- modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())
-
- slicer.app.setRenderPaused(False)
-
-Rasterize a model and save it to a series of image files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to generate a stack of image files from an STL file:
-
-.. code-block:: python
-
- inputModelFile = "/some/input/folder/SomeShape.stl"
- outputDir = "/some/output/folder"
- outputVolumeLabelValue = 100
- outputVolumeSpacingMm = [0.5, 0.5, 0.5]
- outputVolumeMarginMm = [10.0, 10.0, 10.0]
- # Read model
- inputModel = slicer.util.loadModel(inputModelFile)
- # Determine output volume geometry and create a corresponding reference volume
- import math
- import numpy as np
- bounds = np.zeros(6)
- inputModel.GetBounds(bounds)
- imageData = vtk.vtkImageData()
- imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]
- imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]
- imageData.SetDimensions(imageSize)
- imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)
- imageData.GetPointData().GetScalars().Fill(0)
- referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
- referenceVolumeNode.SetOrigin(imageOrigin)
- referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)
- referenceVolumeNode.SetAndObserveImageData(imageData)
- referenceVolumeNode.CreateDefaultDisplayNodes()
- # Convert model to labelmap
- seg = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
- seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)
- slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)
- seg.CreateBinaryLabelmapRepresentation()
- outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)
- outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype("int8")
- # Write labelmap volume to series of TIFF files
- pip_install("imageio")
- import imageio
- for i in range(len(outputLabelmapVolumeArray)):
- imageio.imwrite(f"{outputDir}/image_{i:03}.tiff", outputLabelmapVolumeArray[i])
diff --git a/Docs/developer_guide/script_repository/plots.md b/Docs/developer_guide/script_repository/plots.md
new file mode 100644
index 00000000000..6135fae458e
--- /dev/null
+++ b/Docs/developer_guide/script_repository/plots.md
@@ -0,0 +1,173 @@
+## Plots
+
+### Slicer plots displayed in view layout
+
+Create histogram plot of a volume and show it embedded in the view layout. More information: https://www.slicer.org/wiki/Documentation/Nightly/Developers/Plots
+
+### Using {func}`slicer.util.plot()` utility function
+
+```python
+# Get a volume from SampleData and compute its histogram
+import SampleData
+import numpy as np
+volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
+
+chartNode = slicer.util.plot(histogram, xColumnIndex = 1)
+chartNode.SetYAxisRangeAuto(False)
+chartNode.SetYAxisRange(0, 4e5)
+```
+
+
+
+#### Using MRML classes only
+
+```python
+# Get a volume from SampleData
+import SampleData
+volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
+# Compute histogram values
+import numpy as np
+histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
+
+# Save results to a new table node
+tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
+updateTableFromArray(tableNode, histogram)
+tableNode.GetTable().GetColumn(0).SetName("Count")
+tableNode.GetTable().GetColumn(1).SetName("Intensity")
+
+# Create plot
+plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + " histogram")
+plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
+plotSeriesNode.SetXColumnName("Intensity")
+plotSeriesNode.SetYColumnName("Count")
+plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)
+plotSeriesNode.SetColor(0, 0.6, 1.0)
+
+# Create chart and add plot
+plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
+plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
+plotChartNode.YAxisRangeAutoOff()
+plotChartNode.SetYAxisRange(0, 500000)
+
+# Show plot in layout
+slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
+```
+
+### Using matplotlib
+
+Matplotlib may be used from within Slicer, but the default Tk backend locks up and crashes Slicer. However, Matplotlib may still be used through other backends. More details can be found on the [MatPlotLib](http://matplotlib.sourceforge.net/) pages.
+
+#### Non-interactive plot
+
+```python
+try:
+ import matplotlib
+except ModuleNotFoundError:
+ pip_install("matplotlib")
+ import matplotlib
+
+matplotlib.use("Agg")
+from pylab import *
+
+t1 = arange(0.0, 5.0, 0.1)
+t2 = arange(0.0, 5.0, 0.02)
+t3 = arange(0.0, 2.0, 0.01)
+
+subplot(211)
+plot(t1, cos(2*pi*t1)*exp(-t1), "bo", t2, cos(2*pi*t2)*exp(-t2), "k")
+grid(True)
+title("A tale of 2 subplots")
+ylabel("Damped")
+
+subplot(212)
+plot(t3, cos(2*pi*t3), "r--")
+grid(True)
+xlabel("time (s)")
+ylabel("Undamped")
+savefig("MatplotlibExample.png")
+
+# Static image view
+pm = qt.QPixmap("MatplotlibExample.png")
+imageWidget = qt.QLabel()
+imageWidget.setPixmap(pm)
+imageWidget.setScaledContents(True)
+imageWidget.show()
+```
+
+
+
+#### Plot in Slicer Jupyter notebook
+
+```python
+import JupyterNotebooksLib as slicernb
+try:
+ import matplotlib
+except ModuleNotFoundError:
+ pip_install("matplotlib")
+ import matplotlib
+
+matplotlib.use("Agg")
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+def f(t):
+ s1 = np.cos(2*np.pi*t)
+ e1 = np.exp(-t)
+ return s1 * e1
+
+t1 = np.arange(0.0, 5.0, 0.1)
+t2 = np.arange(0.0, 5.0, 0.02)
+t3 = np.arange(0.0, 2.0, 0.01)
+
+
+fig, axs = plt.subplots(2, 1, constrained_layout=True)
+axs[0].plot(t1, f(t1), "o", t2, f(t2), "-")
+axs[0].set_title("subplot 1")
+axs[0].set_xlabel("distance (m)")
+axs[0].set_ylabel("Damped oscillation")
+fig.suptitle("This is a somewhat long figure title", fontsize=16)
+
+axs[1].plot(t3, np.cos(2*np.pi*t3), "--")
+axs[1].set_xlabel("time (s)")
+axs[1].set_title("subplot 2")
+axs[1].set_ylabel("Undamped")
+
+slicernb.MatplotlibDisplay(matplotlib.pyplot)
+```
+
+
+
+
+#### Interactive plot using wxWidgets GUI toolkit
+
+```python
+try:
+ import matplotlib
+ import wx
+except ModuleNotFoundError:
+ pip_install("matplotlib wxPython")
+ import matplotlib
+
+# Get a volume from SampleData and compute its histogram
+import SampleData
+import numpy as np
+volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
+
+# Set matplotlib to use WXAgg backend
+import matplotlib
+matplotlib.use("WXAgg")
+
+# Show an interactive plot
+import matplotlib.pyplot as plt
+fig, ax = plt.subplots()
+ax.plot(histogram[1][1:], histogram[0].astype(float))
+ax.grid(True)
+ax.set_ylim((0, 4e5))
+plt.show(block=False)
+```
+
+
diff --git a/Docs/developer_guide/script_repository/plots.rst b/Docs/developer_guide/script_repository/plots.rst
deleted file mode 100644
index 34c70bba19b..00000000000
--- a/Docs/developer_guide/script_repository/plots.rst
+++ /dev/null
@@ -1,188 +0,0 @@
-Plots
-~~~~~
-
-Slicer plots displayed in view layout
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Create histogram plot of a volume and show it embedded in the view layout. More information: https://www.slicer.org/wiki/Documentation/Nightly/Developers/Plots
-
-Using :func:`slicer.util.plot()` utility function
-'''''''''''''''''''''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- # Get a volume from SampleData and compute its histogram
- import SampleData
- import numpy as np
- volumeNode = SampleData.SampleDataLogic().downloadMRHead()
- histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
-
- chartNode = slicer.util.plot(histogram, xColumnIndex = 1)
- chartNode.SetYAxisRangeAuto(False)
- chartNode.SetYAxisRange(0, 4e5)
-
-.. figure:: https://www.slicer.org/w/img_auth.php/9/9c/SlicerPlot.png
-
- Plot displayed using Slicer's plotting module
-
-Using MRML classes only
-'''''''''''''''''''''''
-
-.. code-block:: python
-
- # Get a volume from SampleData
- import SampleData
- volumeNode = SampleData.SampleDataLogic().downloadMRHead()
-
- # Compute histogram values
- import numpy as np
- histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
-
- # Save results to a new table node
- tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
- updateTableFromArray(tableNode, histogram)
- tableNode.GetTable().GetColumn(0).SetName("Count")
- tableNode.GetTable().GetColumn(1).SetName("Intensity")
-
- # Create plot
- plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + " histogram")
- plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
- plotSeriesNode.SetXColumnName("Intensity")
- plotSeriesNode.SetYColumnName("Count")
- plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)
- plotSeriesNode.SetColor(0, 0.6, 1.0)
-
- # Create chart and add plot
- plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")
- plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
- plotChartNode.YAxisRangeAutoOff()
- plotChartNode.SetYAxisRange(0, 500000)
-
- # Show plot in layout
- slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)
-
-Using matplotlib
-^^^^^^^^^^^^^^^^
-
-Matplotlib may be used from within Slicer, but the default Tk backend locks up and crashes Slicer. However, Matplotlib may still be used through other backends. More details can be found on the `MatPlotLib `__ pages.
-
-Non-interactive plot
-''''''''''''''''''''
-
-.. code-block:: python
-
- try:
- import matplotlib
- except ModuleNotFoundError:
- pip_install("matplotlib")
- import matplotlib
-
- matplotlib.use("Agg")
- from pylab import *
-
- t1 = arange(0.0, 5.0, 0.1)
- t2 = arange(0.0, 5.0, 0.02)
- t3 = arange(0.0, 2.0, 0.01)
-
- subplot(211)
- plot(t1, cos(2*pi*t1)*exp(-t1), "bo", t2, cos(2*pi*t2)*exp(-t2), "k")
- grid(True)
- title("A tale of 2 subplots")
- ylabel("Damped")
-
- subplot(212)
- plot(t3, cos(2*pi*t3), "r--")
- grid(True)
- xlabel("time (s)")
- ylabel("Undamped")
- savefig("MatplotlibExample.png")
-
- # Static image view
- pm = qt.QPixmap("MatplotlibExample.png")
- imageWidget = qt.QLabel()
- imageWidget.setPixmap(pm)
- imageWidget.setScaledContents(True)
- imageWidget.show()
-
-.. figure:: https://www.slicer.org/w/img_auth.php/a/ab/MatplotlibExample.png
-
- Matplotlib example
-
-Plot in Slicer Jupyter notebook
-'''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- import JupyterNotebooksLib as slicernb
- try:
- import matplotlib
- except ModuleNotFoundError:
- pip_install("matplotlib")
- import matplotlib
-
- matplotlib.use("Agg")
-
- import matplotlib.pyplot as plt
- import numpy as np
-
- def f(t):
- s1 = np.cos(2*np.pi*t)
- e1 = np.exp(-t)
- return s1 * e1
-
- t1 = np.arange(0.0, 5.0, 0.1)
- t2 = np.arange(0.0, 5.0, 0.02)
- t3 = np.arange(0.0, 2.0, 0.01)
-
-
- fig, axs = plt.subplots(2, 1, constrained_layout=True)
- axs[0].plot(t1, f(t1), "o", t2, f(t2), "-")
- axs[0].set_title("subplot 1")
- axs[0].set_xlabel("distance (m)")
- axs[0].set_ylabel("Damped oscillation")
- fig.suptitle("This is a somewhat long figure title", fontsize=16)
-
- axs[1].plot(t3, np.cos(2*np.pi*t3), "--")
- axs[1].set_xlabel("time (s)")
- axs[1].set_title("subplot 2")
- axs[1].set_ylabel("Undamped")
-
- slicernb.MatplotlibDisplay(matplotlib.pyplot)
-
-.. figure:: https://www.slicer.org/w/img_auth.php/a/a2/JupyterNotebookMatplotlibExample.png
-
- Example for using Matplotlib in a Slicer Jupyter Notebook
-
-Interactive plot using wxWidgets GUI toolkit
-''''''''''''''''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- try:
- import matplotlib
- import wx
- except ModuleNotFoundError:
- pip_install("matplotlib wxPython")
- import matplotlib
-
- # Get a volume from SampleData and compute its histogram
- import SampleData
- import numpy as np
- volumeNode = SampleData.SampleDataLogic().downloadMRHead()
- histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)
-
- # Set matplotlib to use WXAgg backend
- import matplotlib
- matplotlib.use("WXAgg")
-
- # Show an interactive plot
- import matplotlib.pyplot as plt
- fig, ax = plt.subplots()
- ax.plot(histogram[1][1:], histogram[0].astype(float))
- ax.grid(True)
- ax.set_ylim((0, 4e5))
- plt.show(block=False)
-
-.. figure:: https://www.slicer.org/w/img_auth.php/d/d2/InteractiveMatplotlibExample.png
-
- Interactive Matplotlib Example
diff --git a/Docs/developer_guide/script_repository/screencapture.md b/Docs/developer_guide/script_repository/screencapture.md
new file mode 100644
index 00000000000..8613b3eff10
--- /dev/null
+++ b/Docs/developer_guide/script_repository/screencapture.md
@@ -0,0 +1,93 @@
+## Screen Capture
+
+### Capture the full Slicer screen and save it into a file
+
+```python
+img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()
+img.save("c:/tmp/test.png")
+```
+
+### Capture all the views save it into a file
+
+```python
+import ScreenCapture
+cap = ScreenCapture.ScreenCaptureLogic()
+cap.showViewControllers(False)
+cap.captureImageFromView(None, "c:/tmp/test.png")
+cap.showViewControllers(True)
+```
+
+### Capture a single view
+
+```python
+viewNodeID = "vtkMRMLViewNode1"
+import ScreenCapture
+cap = ScreenCapture.ScreenCaptureLogic()
+view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))
+cap.captureImageFromView(view, "c:/tmp/test.png")
+```
+
+Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.
+
+### Capture a slice view sweep into a series of PNG files
+
+For example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
+
+```python
+import ScreenCapture
+ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode("vtkMRMLSliceNodeRed"), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")
+```
+
+### Capture 3D view into PNG file with transparent background
+
+```python
+renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
+renderWindow.SetAlphaBitPlanes(1)
+wti = vtk.vtkWindowToImageFilter()
+wti.SetInputBufferTypeToRGBA()
+wti.SetInput(renderWindow)
+writer = vtk.vtkPNGWriter()
+writer.SetFileName("c:/tmp/screenshot.png")
+writer.SetInputConnection(wti.GetOutputPort())
+writer.Write()
+```
+
+### Capture slice view into PNG file with white background
+
+```python
+sliceViewName = "Red"
+filename = "c:/tmp/screenshot.png"
+
+# Set view background to white
+view = slicer.app.layoutManager().sliceWidget(sliceViewName).sliceView()
+view.setBackgroundColor(qt.QColor.fromRgbF(1,1,1))
+view.forceRender()
+
+# Capture a screenshot
+import ScreenCapture
+cap = ScreenCapture.ScreenCaptureLogic()
+cap.captureImageFromView(view, filename)
+```
+
+### Save a series of images from a slice view
+
+You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as ``/tmp/record.py`` and then in the slicer python console type ``execfile("/tmp/record.py")``
+
+```python
+layoutName = "Green"
+imagePathPattern = "/tmp/image-%03d.png"
+steps = 10
+
+widget = slicer.app.layoutManager().sliceWidget(layoutName)
+view = widget.sliceView()
+logic = widget.sliceLogic()
+bounds = [0,]*6
+logic.GetSliceBounds(bounds)
+
+for step in range(steps):
+ offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
+ logic.SetSliceOffset(offset)
+ view.forceRender()
+ image = qt.QPixmap.grabWidget(view).toImage()
+ image.save(imagePathPattern % step)
+```
diff --git a/Docs/developer_guide/script_repository/screencapture.rst b/Docs/developer_guide/script_repository/screencapture.rst
deleted file mode 100644
index 4db7558c6aa..00000000000
--- a/Docs/developer_guide/script_repository/screencapture.rst
+++ /dev/null
@@ -1,101 +0,0 @@
-Screen Capture
-~~~~~~~
-
-Capture the full Slicer screen and save it into a file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()
- img.save("c:/tmp/test.png")
-
-Capture all the views save it into a file
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- import ScreenCapture
- cap = ScreenCapture.ScreenCaptureLogic()
- cap.showViewControllers(False)
- cap.captureImageFromView(None, "c:/tmp/test.png")
- cap.showViewControllers(True)
-
-Capture a single view
-^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- viewNodeID = "vtkMRMLViewNode1"
- import ScreenCapture
- cap = ScreenCapture.ScreenCaptureLogic()
- view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))
- cap.captureImageFromView(view, "c:/tmp/test.png")
-
-Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.
-
-Capture a slice view sweep into a series of PNG files
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-For example, Red slice view, 30 images, from position -125.0 to 75.0, into c:/tmp folder, with name image_00001.png, image_00002.png, ...
-
-.. code-block:: python
-
- import ScreenCapture
- ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode("vtkMRMLSliceNodeRed"), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")
-
-Capture 3D view into PNG file with transparent background
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()
- renderWindow.SetAlphaBitPlanes(1)
- wti = vtk.vtkWindowToImageFilter()
- wti.SetInputBufferTypeToRGBA()
- wti.SetInput(renderWindow)
- writer = vtk.vtkPNGWriter()
- writer.SetFileName("c:/tmp/screenshot.png")
- writer.SetInputConnection(wti.GetOutputPort())
- writer.Write()
-
-Capture slice view into PNG file with white background
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- sliceViewName = "Red"
- filename = "c:/tmp/screenshot.png"
-
- # Set view background to white
- view = slicer.app.layoutManager().sliceWidget(sliceViewName).sliceView()
- view.setBackgroundColor(qt.QColor.fromRgbF(1,1,1))
- view.forceRender()
-
- # Capture a screenshot
- import ScreenCapture
- cap = ScreenCapture.ScreenCaptureLogic()
- cap.captureImageFromView(view, filename)
-
-Save a series of images from a slice view
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can use ScreenCapture module to capture series of images. To do it programmatically, save the following into a file such as ``/tmp/record.py`` and then in the slicer python console type ``execfile("/tmp/record.py")``
-
-.. code-block:: python
-
- layoutName = "Green"
- imagePathPattern = "/tmp/image-%03d.png"
- steps = 10
-
- widget = slicer.app.layoutManager().sliceWidget(layoutName)
- view = widget.sliceView()
- logic = widget.sliceLogic()
- bounds = [0,]*6
- logic.GetSliceBounds(bounds)
-
- for step in range(steps):
- offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])
- logic.SetSliceOffset(offset)
- view.forceRender()
- image = qt.QPixmap.grabWidget(view).toImage()
- image.save(imagePathPattern % step)
diff --git a/Docs/developer_guide/script_repository/segmentations.md b/Docs/developer_guide/script_repository/segmentations.md
new file mode 100644
index 00000000000..19e06982a64
--- /dev/null
+++ b/Docs/developer_guide/script_repository/segmentations.md
@@ -0,0 +1,525 @@
+## Segmentations
+
+### Load a 3D image or model file as segmentation
+
+```python
+slicer.util.loadSegmentation("c:/tmp/tmp/Segmentation.nrrd")
+slicer.util.loadSegmentation("c:/tmp/tmp/Segmentation.nii")
+slicer.util.loadSegmentation("c:/tmp/Segment_1.stl")
+```
+
+### Create a segmentation from a labelmap volume and display in 3D
+
+```python
+labelmapVolumeNode = getNode("label")
+seg = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
+slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
+seg.CreateClosedSurfaceRepresentation()
+slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+```
+
+The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
+
+### Export labelmap node from segmentation node
+
+Export labelmap matching reference geometry of the segmentation:
+
+```python
+segmentationNode = getNode("Segmentation")
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)
+```
+
+Export smallest possible labelmap:
+
+```python
+segmentationNode = getNode("Segmentation")
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)
+```
+
+Export labelmap that matches geometry of a chosen reference volume:
+
+```python
+segmentationNode = getNode("Segmentation")
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
+```
+
+Export a selection of segments (identified by their names):
+
+```python
+segmentNames = ["Prostate", "Urethra"]
+segmentIds = vtk.vtkStringArray()
+for segmentName in segmentNames:
+ segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)
+ segmentIds.InsertNextValue(segmentId)
+slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)
+```
+
+Export to file by pressing Ctrl+Shift+S key:
+
+```python
+outputPath = "c:/tmp"
+
+def exportLabelmap():
+ segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
+ referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
+ labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+ slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
+ filepath = outputPath + "/" + referenceVolumeNode.GetName() + "-label.nrrd"
+ slicer.util.saveNode(labelmapVolumeNode, filepath)
+ slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
+ slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+ slicer.util.delayDisplay("Segmentation saved to " + filepath)
+
+shortcut = qt.QShortcut(slicer.util.mainWindow())
+shortcut.setKey(qt.QKeySequence("Ctrl+Shift+s"))
+shortcut.connect( "activated()", exportLabelmap)
+```
+
+### Export model nodes from segmentation node
+
+```python
+segmentationNode = getNode("Segmentation")
+shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
+exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
+slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
+```
+
+### Create a hollow model from boundary of solid segment
+
+In most cases, the most robust and flexible tool for creating empty shell models (e.g., vessel wall model from contrast agent segmentation) is the "Hollow" effect in Segment Editor module. However, for very thin shells, extrusion of the exported surface mesh representation may be just as robust and require less memory and computation time. In this case it may be a better approach to to export the segment to a mesh and extrude it along surface normal direction:
+
+Example using Dynamic Modeler module (allows real-time update of parameters, using GUI in Dynamic Modeler module):
+
+```python
+segmentationNode = getNode("Segmentation")
+
+# Export segments to models
+shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
+exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
+slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
+segmentModels = vtk.vtkCollection()
+shNode.GetDataNodesInBranch(exportFolderItemId, segmentModels)
+# Get exported model of first segment
+modelNode = segmentModels.GetItemAsObject(0)
+
+# Set up Hollow tool
+hollowModeler = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLDynamicModelerNode")
+hollowModeler.SetToolName("Hollow")
+hollowModeler.SetNodeReferenceID("Hollow.InputModel", modelNode.GetID())
+hollowedModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode") # this node will store the hollow model
+hollowModeler.SetNodeReferenceID("Hollow.OutputModel", hollowedModelNode.GetID())
+hollowModeler.SetAttribute("ShellThickness", "2.5") # grow outside
+hollowModeler.SetContinuousUpdate(True) # auto-update output model if input parameters are changed
+
+# Hide inputs, show output
+segmentation.GetDisplayNode().SetVisibility(False)
+modelNode.GetDisplayNode().SetVisibility(False)
+hollowedModelNode.GetDisplayNode().SetOpacity(0.5)
+```
+
+Example using VTK filters:
+
+```python
+# Get closed surface representation of the segment
+shellThickness = 3.0 # mm
+segmentationNode = getNode("Segmentation")
+segmentationNode.CreateClosedSurfaceRepresentation()
+polyData = segmentationNode.GetClosedSurfaceInternalRepresentation("Segment_1")
+
+# Create shell
+extrude = vtk.vtkLinearExtrusionFilter()
+extrude.SetInputData(polyData)
+extrude.SetExtrusionTypeToNormalExtrusion()
+extrude.SetScaleFactor(shellThickness)
+
+# Compute consistent surface normals
+triangle_filter = vtk.vtkTriangleFilter()
+triangle_filter.SetInputConnection(extrude.GetOutputPort())
+normals = vtk.vtkPolyDataNormals()
+normals.SetInputConnection(triangle_filter.GetOutputPort())
+normals.FlipNormalsOn()
+
+# Save result into new model node
+slicer.modules.models.logic().AddModel(normals.GetOutputPort())
+```
+
+### Show a segmentation in 3D
+
+Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
+
+```python
+segmentation.CreateClosedSurfaceRepresentation()
+```
+
+### Get a representation of a segment
+
+Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:
+
+```python
+image = slicer.vtkOrientedImageData()
+segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)
+```
+
+Get closed surface, if it does not exist, it will return None:
+
+```python
+outputPolyData = vtk.vtkPolyData()
+segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)
+```
+
+Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):
+
+```python
+import vtkSegmentationCorePython as vtkSegmentationCore
+outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
+slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
+```
+
+Same as above, for closed surface representation:
+
+```python
+outputPolyData = vtk.vtkPolyData()
+slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
+```
+
+### Convert all segments using default path and conversion parameters
+
+```python
+segmentationNode.CreateBinaryLabelmapRepresentation()
+```
+
+### Convert all segments using custom path or conversion parameters
+
+Change reference image geometry parameter based on an existing referenceImageData image:
+
+```python
+referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
+segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
+```
+
+### Re-convert using a modified conversion parameter
+
+Changing smoothing factor for closed surface generation:
+
+```python
+import vtkSegmentationCorePython as vtkSegmentationCore
+segmentation = getNode("Segmentation").GetSegmentation()
+
+# Turn of surface smoothing
+segmentation.SetConversionParameter("Smoothing factor","0.0")
+
+# Recreate representation using modified parameters (and default conversion path)
+segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
+```
+
+### Create keyboard shortcut for toggling sphere brush for paint and erase effects
+
+```python
+def toggleSphereBrush():
+ segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
+ paintEffect = segmentEditorWidget.effectByName("Paint")
+ isSphere = paintEffect.integerParameter("BrushSphere")
+ # BrushSphere is "common" parameter (shared between paint and erase)
+ paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)
+
+shortcut = qt.QShortcut(slicer.util.mainWindow())
+shortcut.setKey(qt.QKeySequence("s"))
+shortcut.connect("activated()", toggleSphereBrush)
+```
+
+### Customize list of displayed Segment editor effects
+
+Only show Paint and Erase effects:
+
+```python
+segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
+segmentEditorWidget.setEffectNameOrder(["Paint", "Erase"])
+segmentEditorWidget.unorderedEffectsVisible = False
+```
+
+Show list of all available effect names:
+
+```python
+segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
+print(segmentEditorWidget.availableEffectNames())
+```
+
+### Get centroid of a segment in world (RAS) coordinates
+
+This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.
+
+```python
+segmentationNode = getNode("Segmentation")
+segmentId = "Segment_1"
+
+# Get array voxel coordinates
+import numpy as np
+seg=arrayFromSegment(segmentation_node, segmentId)
+# numpy array has voxel coordinates in reverse order (KJI instead of IJK)
+# and the array is cropped to minimum size in the segmentation
+mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]
+
+# Get segmentation voxel coordinates
+segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
+segImageExtent = segImage.GetExtent()
+# origin of the array in voxel coordinates is determined by the start extent
+mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])
+
+# Get segmentation physical coordinates
+ijkToWorld = vtk.vtkMatrix4x4()
+segImage.GetImageToWorldMatrix(ijkToWorld)
+mean_World = [0, 0, 0, 1]
+ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)
+mean_World = mean_World[0:3]
+
+# If segmentation node is transformed, apply that transform to get RAS coordinates
+transformWorldToRas = vtk.vtkGeneralTransform()
+slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)
+mean_Ras = transformWorldToRas.TransformPoint(mean_World)
+
+# Show mean position value and jump to it in all slice viewers
+print(mean_Ras)
+slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
+```
+
+### Get histogram of a segmented region
+
+```python
+# Generate input data
+################################################
+
+# Load master volume
+import SampleData
+sampleDataLogic = SampleData.SampleDataLogic()
+masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()
+
+# Create segmentation
+segmentationNode = slicer.vtkMRMLSegmentationNode()
+slicer.mrmlScene.AddNode(segmentationNode)
+segmentationNode.CreateDefaultDisplayNodes() # only needed for display
+segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)
+
+# Create segment
+tumorSeed = vtk.vtkSphereSource()
+tumorSeed.SetCenter(-6, 30, 28)
+tumorSeed.SetRadius(25)
+tumorSeed.Update()
+segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])
+
+# Compute histogram
+################################################
+
+labelValue = 1 # label value of first segment
+
+# Get segmentation as labelmap volume node
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)
+
+# Extract all voxels of the segment as numpy array
+volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)
+labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)
+segmentVoxels = volumeArray[labelArray==labelValue]
+
+# Compute histogram
+import numpy as np
+histogram = np.histogram(segmentVoxels, bins=50)
+
+# Plot histogram
+################################################
+
+slicer.util.plot(histogram, xColumnIndex = 1)
+```
+
+### Get segments visible at a selected position
+
+Show in the console names of segments visible at a markups fiducial position:
+
+```python
+segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
+markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")
+sliceViewLabel = "Red" # any slice view where segmentation node is visible works
+
+def printSegmentNames(unused1=None, unused2=None):
+
+ sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)
+ segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName("vtkMRMLSegmentationsDisplayableManager2D")
+ ras = [0,0,0]
+ markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)
+ segmentIds = vtk.vtkStringArray()
+ segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)
+ for idIndex in range(segmentIds.GetNumberOfValues()):
+ segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))
+ print("Segment found at position {0}: {1}".format(ras, segment.GetName()))
+
+# Observe markup node changes
+markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)
+printSegmentNames()
+```
+
+### Set default segmentation options
+
+Allow segments to overlap each other by default:
+
+```python
+defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()
+defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
+slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)
+```
+
+To always make this the default, add the lines above to your [.slicerrc.py file](../user_guide/settings.md#application-startup-file).
+
+### How to run segment editor effects from a script
+
+Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, it is recommended to use the effect by instantiating a qMRMLSegmentEditorWidget or use/extract processing logic of the effect and use that from a script.
+
+#### Use Segment editor effects from script (qMRMLSegmentEditorWidget)
+
+Examples:
+
+- [brain tumor segmentation using grow from seeds effect](https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b)
+- [AI-assisted brain tumor segmentation](https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5)
+- [skin surface extraction using thresholding and smoothing](https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9)
+- [mask a volume with segments and compute histogram for each region](https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f)
+- [create fat/muscle/bone segment by thresholding and report volume of each segment](https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37)
+- [segment cranial cavity automatically in dry bone skull CT](https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1)
+- [remove patient table from CT image](https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088)
+
+Description of effect parameters are available [here](https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmenteditor.md#effect-parameters).
+
+#### Use logic of effect from a script
+
+This example shows how to perform operations on segmentations using VTK filters *extracted* from an effect:
+
+- [brain tumor segmentation using grow from seeds effect](https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7)
+
+### Process segment using a VTK filter
+
+This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.
+
+```python
+segmentationNode = getNode("Segmentation")
+segmentId = "Segment_1"
+kernelSize = [3,1,5]
+
+# Export segment as vtkImageData (via temporary labelmap volume node)
+segmentIds = vtk.vtkStringArray()
+segmentIds.InsertNextValue(segmentId)
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)
+
+# Process segmentation
+segmentImageData = labelmapVolumeNode.GetImageData()
+erodeDilate = vtk.vtkImageDilateErode3D()
+erodeDilate.SetInputData(segmentImageData)
+erodeDilate.SetDilateValue(1)
+erodeDilate.SetErodeValue(0)
+erodeDilate.SetKernelSize(*kernelSize)
+erodeDilate.Update()
+segmentImageData.DeepCopy(erodeDilate.GetOutput())
+
+# Import segment from vtkImageData
+slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)
+
+# Cleanup temporary nodes
+slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
+slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+```
+
+### Use segmentation files in Python - outside Slicer
+
+You can use [slicerio](https://pypi.org/project/slicerio/) Python package (in any Python environment, not just within Slicer) to get information from segmentation (.seg.nrrd) files.
+
+For example, this code snippet extracts selected segments from a segmentation as a numpy array (`extracted_voxels`) and writes it into a nrrd file. This operation can be useful when creating training data for deep learning networks.
+
+```python
+# pip install slicerio
+
+import slicerio
+import nrrd
+
+input_filename = "path/to/Segmentation.seg.nrrd"
+output_filename = "path/to/SegmentationExtracted.seg.nrrd"
+segment_names_to_labels = [("ribs", 10), ("right lung", 12), ("left lung", 6)]
+
+# Read voxels and metadata from a .seg.nrrd file
+voxels, header = nrrd.read(input_filename)
+# Get selected segments in a 3D numpy array and updated segment metadata
+extracted_voxels, extracted_header = slicerio.extract_segments(voxels, header, segmentation_info, segment_names_to_labels)
+# Write extracted segments and metadata to .seg.nrrd file
+nrrd.write(output_filename, extracted_voxels, extracted_header)
+```
+
+### Quantifying segments
+
+#### Get centroid of each segment
+
+Place a markups fiducial point at the centroid of each segment.
+
+```python
+segmentationNode = getNode("Segmentation")
+
+# Compute centroids
+import SegmentStatistics
+segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
+segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))
+segStatLogic.computeStatistics()
+stats = segStatLogic.getStatistics()
+
+# Place a markup point in each centroid
+markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
+markupsNode.CreateDefaultDisplayNodes()
+for segmentId in stats["SegmentIDs"]:
+ centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]
+ segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()
+ markupsNode.AddFiducialFromArray(centroid_ras, segmentName)
+```
+
+#### Get size, position, and orientation of each segment
+
+This example computes oriented bounding box for each segment and displays them using annotation ROI.
+
+```python
+segmentationNode = getNode("Segmentation")
+
+# Compute bounding boxes
+import SegmentStatistics
+segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
+segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))
+segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))
+segStatLogic.computeStatistics()
+stats = segStatLogic.getStatistics()
+
+# Draw ROI for each oriented bounding box
+import numpy as np
+for segmentId in stats["SegmentIDs"]:
+ # Get bounding box
+ obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])
+ obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])
+ obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])
+ obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])
+ obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])
+ # Create ROI
+ segment = segmentationNode.GetSegmentation().GetSegment(segmentId)
+ roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")
+ roi.SetName(segment.GetName() + " bounding box")
+ roi.SetXYZ(0.0, 0.0, 0.0)
+ roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))
+ # Position and orient ROI using a transform
+ obb_center_ras = obb_origin_ras+0.5*(obb_diameter_mm[0] * obb_direction_ras_x + obb_diameter_mm[1] * obb_direction_ras_y + obb_diameter_mm[2] * obb_direction_ras_z)
+ boundingBoxToRasTransform = np.row_stack((np.column_stack((obb_direction_ras_x, obb_direction_ras_y, obb_direction_ras_z, obb_center_ras)), (0, 0, 0, 1)))
+ boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)
+ transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
+ transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)
+ roi.SetAndObserveTransformNodeID(transformNode.GetID())
+```
+
+Complete list of available parameters can be obtained by running `segStatLogic.getParameterNode().GetParameterNames()`.
diff --git a/Docs/developer_guide/script_repository/segmentations.rst b/Docs/developer_guide/script_repository/segmentations.rst
deleted file mode 100644
index 9d82fee4056..00000000000
--- a/Docs/developer_guide/script_repository/segmentations.rst
+++ /dev/null
@@ -1,550 +0,0 @@
-Segmentations
-~~~~~~~~~~~~~
-
-Load a 3D image or model file as segmentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- slicer.util.loadSegmentation("c:/tmp/tmp/Segmentation.nrrd")
- slicer.util.loadSegmentation("c:/tmp/tmp/Segmentation.nii")
- slicer.util.loadSegmentation("c:/tmp/Segment_1.stl")
-
-Create a segmentation from a labelmap volume and display in 3D
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- labelmapVolumeNode = getNode("label")
- seg = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
- slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
- seg.CreateClosedSurfaceRepresentation()
- slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
-
-The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.
-
-Export labelmap node from segmentation node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Export labelmap matching reference geometry of the segmentation:
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)
-
-Export smallest possible labelmap:
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)
-
-Export labelmap that matches geometry of a chosen reference volume:
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
-
-Export a selection of segments (identified by their names):
-
-.. code-block:: python
-
- segmentNames = ["Prostate", "Urethra"]
- segmentIds = vtk.vtkStringArray()
- for segmentName in segmentNames:
- segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)
- segmentIds.InsertNextValue(segmentId)
- slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)
-
-Export to file by pressing Ctrl+Shift+S key:
-
-.. code-block:: python
-
- outputPath = "c:/tmp"
-
- def exportLabelmap():
- segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
- referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)
- filepath = outputPath + "/" + referenceVolumeNode.GetName() + "-label.nrrd"
- slicer.util.saveNode(labelmapVolumeNode, filepath)
- slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
- slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
- slicer.util.delayDisplay("Segmentation saved to " + filepath)
-
- shortcut = qt.QShortcut(slicer.util.mainWindow())
- shortcut.setKey(qt.QKeySequence("Ctrl+Shift+s"))
- shortcut.connect( "activated()", exportLabelmap)
-
-Export model nodes from segmentation node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
- exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
- slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
-
-Create a hollow model from boundary of solid segment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In most cases, the most robust and flexible tool for creating empty shell models (e.g., vessel wall model from contrast agent segmentation) is the "Hollow" effect in Segment Editor module. However, for very thin shells, extrusion of the exported surface mesh representation may be just as robust and require less memory and computation time. In this case it may be a better approach to to export the segment to a mesh and extrude it along surface normal direction:
-
-Example using Dynamic Modeler module (allows real-time update of parameters, using GUI in Dynamic Modeler module):
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
-
- # Export segments to models
- shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
- exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")
- slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)
- segmentModels = vtk.vtkCollection()
- shNode.GetDataNodesInBranch(exportFolderItemId, segmentModels)
- # Get exported model of first segment
- modelNode = segmentModels.GetItemAsObject(0)
-
- # Set up Hollow tool
- hollowModeler = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLDynamicModelerNode")
- hollowModeler.SetToolName("Hollow")
- hollowModeler.SetNodeReferenceID("Hollow.InputModel", modelNode.GetID())
- hollowedModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode") # this node will store the hollow model
- hollowModeler.SetNodeReferenceID("Hollow.OutputModel", hollowedModelNode.GetID())
- hollowModeler.SetAttribute("ShellThickness", "2.5") # grow outside
- hollowModeler.SetContinuousUpdate(True) # auto-update output model if input parameters are changed
-
- # Hide inputs, show output
- segmentation.GetDisplayNode().SetVisibility(False)
- modelNode.GetDisplayNode().SetVisibility(False)
- hollowedModelNode.GetDisplayNode().SetOpacity(0.5)
-
-Example using VTK filters:
-
-.. code-block:: python
-
- # Get closed surface representation of the segment
- shellThickness = 3.0 # mm
- segmentationNode = getNode("Segmentation")
- segmentationNode.CreateClosedSurfaceRepresentation()
- polyData = segmentationNode.GetClosedSurfaceInternalRepresentation("Segment_1")
-
- # Create shell
- extrude = vtk.vtkLinearExtrusionFilter()
- extrude.SetInputData(polyData)
- extrude.SetExtrusionTypeToNormalExtrusion()
- extrude.SetScaleFactor(shellThickness)
-
- # Compute consistent surface normals
- triangle_filter = vtk.vtkTriangleFilter()
- triangle_filter.SetInputConnection(extrude.GetOutputPort())
- normals = vtk.vtkPolyDataNormals()
- normals.SetInputConnection(triangle_filter.GetOutputPort())
- normals.FlipNormalsOn()
-
- # Save result into new model node
- slicer.modules.models.logic().AddModel(normals.GetOutputPort())
-
-Show a segmentation in 3D
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:
-
-.. code-block:: python
-
- segmentation.CreateClosedSurfaceRepresentation()
-
-Get a representation of a segment
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:
-
-.. code-block:: python
-
- image = slicer.vtkOrientedImageData()
- segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)
-
-Get closed surface, if it does not exist, it will return None:
-
-.. code-block:: python
-
- outputPolyData = vtk.vtkPolyData()
- segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)
-
-Get binary labelmap representation. If it does not exist then it will be created for that single segment. Applies parent transforms by default (if not desired, another argument needs to be added to the end: false):
-
-.. code-block:: python
-
- import vtkSegmentationCorePython as vtkSegmentationCore
- outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()
- slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)
-
-Same as above, for closed surface representation:
-
-.. code-block:: python
-
- outputPolyData = vtk.vtkPolyData()
- slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)
-
-Convert all segments using default path and conversion parameters
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- segmentationNode.CreateBinaryLabelmapRepresentation()
-
-Convert all segments using custom path or conversion parameters
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Change reference image geometry parameter based on an existing referenceImageData image:
-
-.. code-block:: python
-
- referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)
- segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)
-
-Re-convert using a modified conversion parameter
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Changing smoothing factor for closed surface generation:
-
-.. code-block:: python
-
- import vtkSegmentationCorePython as vtkSegmentationCore
- segmentation = getNode("Segmentation").GetSegmentation()
-
- # Turn of surface smoothing
- segmentation.SetConversionParameter("Smoothing factor","0.0")
-
- # Recreate representation using modified parameters (and default conversion path)
- segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
- segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())
-
-Create keyboard shortcut for toggling sphere brush for paint and erase effects
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- def toggleSphereBrush():
- segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
- paintEffect = segmentEditorWidget.effectByName("Paint")
- isSphere = paintEffect.integerParameter("BrushSphere")
- # BrushSphere is "common" parameter (shared between paint and erase)
- paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)
-
- shortcut = qt.QShortcut(slicer.util.mainWindow())
- shortcut.setKey(qt.QKeySequence("s"))
- shortcut.connect("activated()", toggleSphereBrush)
-
-Customize list of displayed Segment editor effects
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Only show Paint and Erase effects:
-
-.. code-block:: python
-
- segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
- segmentEditorWidget.setEffectNameOrder(["Paint", "Erase"])
- segmentEditorWidget.unorderedEffectsVisible = False
-
-Show list of all available effect names:
-
-.. code-block:: python
-
- segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
- print(segmentEditorWidget.availableEffectNames())
-
-Get centroid of a segment in world (RAS) coordinates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- segmentId = "Segment_1"
-
- # Get array voxel coordinates
- import numpy as np
- seg=arrayFromSegment(segmentation_node, segmentId)
- # numpy array has voxel coordinates in reverse order (KJI instead of IJK)
- # and the array is cropped to minimum size in the segmentation
- mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]
-
- # Get segmentation voxel coordinates
- segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)
- segImageExtent = segImage.GetExtent()
- # origin of the array in voxel coordinates is determined by the start extent
- mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])
-
- # Get segmentation physical coordinates
- ijkToWorld = vtk.vtkMatrix4x4()
- segImage.GetImageToWorldMatrix(ijkToWorld)
- mean_World = [0, 0, 0, 1]
- ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)
- mean_World = mean_World[0:3]
-
- # If segmentation node is transformed, apply that transform to get RAS coordinates
- transformWorldToRas = vtk.vtkGeneralTransform()
- slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)
- mean_Ras = transformWorldToRas.TransformPoint(mean_World)
-
- # Show mean position value and jump to it in all slice viewers
- print(mean_Ras)
- slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)
-
-Get histogram of a segmented region
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Generate input data
- ################################################
-
- # Load master volume
- import SampleData
- sampleDataLogic = SampleData.SampleDataLogic()
- masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()
-
- # Create segmentation
- segmentationNode = slicer.vtkMRMLSegmentationNode()
- slicer.mrmlScene.AddNode(segmentationNode)
- segmentationNode.CreateDefaultDisplayNodes() # only needed for display
- segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)
-
- # Create segment
- tumorSeed = vtk.vtkSphereSource()
- tumorSeed.SetCenter(-6, 30, 28)
- tumorSeed.SetRadius(25)
- tumorSeed.Update()
- segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])
-
- # Compute histogram
- ################################################
-
- labelValue = 1 # label value of first segment
-
- # Get segmentation as labelmap volume node
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)
-
- # Extract all voxels of the segment as numpy array
- volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)
- labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)
- segmentVoxels = volumeArray[labelArray==labelValue]
-
- # Compute histogram
- import numpy as np
- histogram = np.histogram(segmentVoxels, bins=50)
-
- # Plot histogram
- ################################################
-
- slicer.util.plot(histogram, xColumnIndex = 1)
-
-Get segments visible at a selected position
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Show in the console names of segments visible at a markups fiducial position:
-
-.. code-block:: python
-
- segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
- markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")
- sliceViewLabel = "Red" # any slice view where segmentation node is visible works
-
- def printSegmentNames(unused1=None, unused2=None):
-
- sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)
- segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName("vtkMRMLSegmentationsDisplayableManager2D")
- ras = [0,0,0]
- markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)
- segmentIds = vtk.vtkStringArray()
- segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)
- for idIndex in range(segmentIds.GetNumberOfValues()):
- segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))
- print("Segment found at position {0}: {1}".format(ras, segment.GetName()))
-
- # Observe markup node changes
- markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)
- printSegmentNames()
-
-Set default segmentation options
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Allow segments to overlap each other by default:
-
-.. code-block:: python
-
- defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()
- defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)
- slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)
-
-To always make this the default, add the lines above to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-How to run segment editor effects from a script
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, it is recommended to use the effect by instantiating a qMRMLSegmentEditorWidget or use/extract processing logic of the effect and use that from a script.
-
-Use Segment editor effects from script (qMRMLSegmentEditorWidget)
-'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
-
-Examples:
-
-- `brain tumor segmentation using grow from seeds effect `__
-- `AI-assisted brain tumor segmentation `__
-- `skin surface extraction using thresholding and smoothing `__
-- `mask a volume with segments and compute histogram for each region `__
-- `create fat/muscle/bone segment by thresholding and report volume of each segment `__
-- `segment cranial cavity automatically in dry bone skull CT `__
-- `remove patient table from CT image `__
-
-Description of effect parameters are available `here `__.
-
-Use logic of effect from a script
-'''''''''''''''''''''''''''''''''
-
-This example shows how to perform operations on segmentations using VTK filters *extracted* from an effect:
-
-- `brain tumor segmentation using grow from seeds effect `__
-
-Process segment using a VTK filter
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
- segmentId = "Segment_1"
- kernelSize = [3,1,5]
-
- # Export segment as vtkImageData (via temporary labelmap volume node)
- segmentIds = vtk.vtkStringArray()
- segmentIds.InsertNextValue(segmentId)
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)
-
- # Process segmentation
- segmentImageData = labelmapVolumeNode.GetImageData()
- erodeDilate = vtk.vtkImageDilateErode3D()
- erodeDilate.SetInputData(segmentImageData)
- erodeDilate.SetDilateValue(1)
- erodeDilate.SetErodeValue(0)
- erodeDilate.SetKernelSize(*kernelSize)
- erodeDilate.Update()
- segmentImageData.DeepCopy(erodeDilate.GetOutput())
-
- # Import segment from vtkImageData
- slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)
-
- # Cleanup temporary nodes
- slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())
- slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
-
-Use segmentation files in Python - outside Slicer
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can use `slicerio `__ Python package (in any Python environment, not just within Slicer) to get information from segmentation (.seg.nrrd) files.
-
-For example, this code snippet extracts selected segments from a segmentation as a numpy array (``extracted_voxels``) and writes it into a nrrd file. This operation can be useful when creating training data for deep learning networks.
-
-.. code-block:: python
-
- # pip install slicerio
-
- import slicerio
- import nrrd
-
- input_filename = "path/to/Segmentation.seg.nrrd"
- output_filename = "path/to/SegmentationExtracted.seg.nrrd"
- segment_names_to_labels = [("ribs", 10), ("right lung", 12), ("left lung", 6)]
-
- # Read voxels and metadata from a .seg.nrrd file
- voxels, header = nrrd.read(input_filename)
- # Get selected segments in a 3D numpy array and updated segment metadata
- extracted_voxels, extracted_header = slicerio.extract_segments(voxels, header, segmentation_info, segment_names_to_labels)
- # Write extracted segments and metadata to .seg.nrrd file
- nrrd.write(output_filename, extracted_voxels, extracted_header)
-
-Quantifying segments
-^^^^^^^^^^^^^^^^^^^^
-
-Get centroid of each segment
-''''''''''''''''''''''''''''
-
-Place a markups fiducial point at the centroid of each segment.
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
-
- # Compute centroids
- import SegmentStatistics
- segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
- segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))
- segStatLogic.computeStatistics()
- stats = segStatLogic.getStatistics()
-
- # Place a markup point in each centroid
- markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")
- markupsNode.CreateDefaultDisplayNodes()
- for segmentId in stats["SegmentIDs"]:
- centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]
- segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()
- markupsNode.AddFiducialFromArray(centroid_ras, segmentName)
-
-Get size, position, and orientation of each segment
-'''''''''''''''''''''''''''''''''''''''''''''''''''
-
-This example computes oriented bounding box for each segment and displays them using annotation ROI.
-
-.. code-block:: python
-
- segmentationNode = getNode("Segmentation")
-
- # Compute bounding boxes
- import SegmentStatistics
- segStatLogic = SegmentStatistics.SegmentStatisticsLogic()
- segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))
- segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))
- segStatLogic.computeStatistics()
- stats = segStatLogic.getStatistics()
-
- # Draw ROI for each oriented bounding box
- import numpy as np
- for segmentId in stats["SegmentIDs"]:
- # Get bounding box
- obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])
- obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])
- obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])
- obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])
- obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])
- # Create ROI
- segment = segmentationNode.GetSegmentation().GetSegment(segmentId)
- roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")
- roi.SetName(segment.GetName() + " bounding box")
- roi.SetXYZ(0.0, 0.0, 0.0)
- roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))
- # Position and orient ROI using a transform
- obb_center_ras = obb_origin_ras+0.5*(obb_diameter_mm[0] * obb_direction_ras_x + obb_diameter_mm[1] * obb_direction_ras_y + obb_diameter_mm[2] * obb_direction_ras_z)
- boundingBoxToRasTransform = np.row_stack((np.column_stack((obb_direction_ras_x, obb_direction_ras_y, obb_direction_ras_z, obb_center_ras)), (0, 0, 0, 1)))
- boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)
- transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
- transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)
- roi.SetAndObserveTransformNodeID(transformNode.GetID())
-
-Complete list of available parameters can be obtained by running ``segStatLogic.getParameterNode().GetParameterNames()``.
diff --git a/Docs/developer_guide/script_repository/sequences.md b/Docs/developer_guide/script_repository/sequences.md
new file mode 100644
index 00000000000..d9521a4f8d6
--- /dev/null
+++ b/Docs/developer_guide/script_repository/sequences.md
@@ -0,0 +1,74 @@
+## Sequences
+
+### Access voxels of a 4D volume as numpy array
+
+```python
+# Get sequence node
+import SampleData
+sequenceNode = SampleData.SampleDataLogic().downloadSample("CTPCardioSeq")
+# Alternatively, get the first sequence node in the scene:
+# sequenceNode = slicer.util.getNodesByClass("vtkMRMLSequenceNode")[0]
+
+# Get voxels of itemIndex'th volume as numpy array
+itemIndex = 5
+voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))
+```
+
+### Get index value
+
+```python
+print("Index value of {0}th item: {1} = {2} {3}".format(
+ itemIndex,
+ sequenceNode.GetIndexName(),
+ sequenceNode.GetNthIndexValue(itemIndex),
+ sequenceNode.GetIndexUnit()))
+```
+
+### Browse a sequence and access currently displayed nodes
+
+```python
+# Get a sequence node
+import SampleData
+sequenceNode = SampleData.SampleDataLogic().downloadSample("CTPCardioSeq")
+
+# Find corresponding sequence browser node
+browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)
+
+# Print sequence information
+print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))
+print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))
+
+# Jump to a selected sequence item
+browserNode.SetSelectedItemNumber(5)
+
+# Get currently displayed volume node voxels as numpy array
+volumeNode = browserNode.GetProxyNode(sequenceNode)
+voxelArray = slicer.util.arrayFromVolume(volumeNode)
+```
+
+### Concatenate all sequences in the scene into a new sequence
+
+```python
+# Get all sequence nodes in the scene
+sequenceNodes = slicer.util.getNodesByClass("vtkMRMLSequenceNode")
+mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", "Merged sequence")
+
+# Merge all sequence nodes into a new sequence node
+mergedIndexValue = 0
+for sequenceNode in sequenceNodes:
+ for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):
+ dataNode = sequenceNode.GetNthDataNode(itemIndex)
+ mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))
+ mergedIndexValue += 1
+ # Delete the sequence node we copied the data from, to prevent sharing of the same
+ # node by multiple sequences
+ slicer.mrmlScene.RemoveNode(sequenceNode)
+
+# Create a sequence browser node for the new merged sequence
+mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceBrowserNode", "Merged")
+mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
+slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
+# Show proxy node in slice viewers
+mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
+slicer.util.setSliceViewerLayers(background=mergedProxyNode)
+```
diff --git a/Docs/developer_guide/script_repository/sequences.rst b/Docs/developer_guide/script_repository/sequences.rst
deleted file mode 100644
index 2c2aaa5cd41..00000000000
--- a/Docs/developer_guide/script_repository/sequences.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-Sequences
-~~~~~~~~~
-
-Access voxels of a 4D volume as numpy array
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Get sequence node
- import SampleData
- sequenceNode = SampleData.SampleDataLogic().downloadSample("CTPCardioSeq")
- # Alternatively, get the first sequence node in the scene:
- # sequenceNode = slicer.util.getNodesByClass("vtkMRMLSequenceNode")[0]
-
- # Get voxels of itemIndex'th volume as numpy array
- itemIndex = 5
- voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))
-
-Get index value
-^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- print("Index value of {0}th item: {1} = {2} {3}".format(
- itemIndex,
- sequenceNode.GetIndexName(),
- sequenceNode.GetNthIndexValue(itemIndex),
- sequenceNode.GetIndexUnit()))
-
-Browse a sequence and access currently displayed nodes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Get a sequence node
- import SampleData
- sequenceNode = SampleData.SampleDataLogic().downloadSample("CTPCardioSeq")
-
- # Find corresponding sequence browser node
- browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)
-
- # Print sequence information
- print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))
- print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))
-
- # Jump to a selected sequence item
- browserNode.SetSelectedItemNumber(5)
-
- # Get currently displayed volume node voxels as numpy array
- volumeNode = browserNode.GetProxyNode(sequenceNode)
- voxelArray = slicer.util.arrayFromVolume(volumeNode)
-
-Concatenate all sequences in the scene into a new sequence
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Get all sequence nodes in the scene
- sequenceNodes = slicer.util.getNodesByClass("vtkMRMLSequenceNode")
- mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceNode", "Merged sequence")
-
- # Merge all sequence nodes into a new sequence node
- mergedIndexValue = 0
- for sequenceNode in sequenceNodes:
- for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):
- dataNode = sequenceNode.GetNthDataNode(itemIndex)
- mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))
- mergedIndexValue += 1
- # Delete the sequence node we copied the data from, to prevent sharing of the same
- # node by multiple sequences
- slicer.mrmlScene.RemoveNode(sequenceNode)
-
- # Create a sequence browser node for the new merged sequence
- mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSequenceBrowserNode", "Merged")
- mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)
- slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)
- # Show proxy node in slice viewers
- mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)
- slicer.util.setSliceViewerLayers(background=mergedProxyNode)
diff --git a/Docs/developer_guide/script_repository/subjecthierarchy.md b/Docs/developer_guide/script_repository/subjecthierarchy.md
new file mode 100644
index 00000000000..8f7c8fa5d1a
--- /dev/null
+++ b/Docs/developer_guide/script_repository/subjecthierarchy.md
@@ -0,0 +1,190 @@
+## Subject hierarchy
+
+### Get the pseudo-singleton subject hierarchy node
+
+It manages the whole hierarchy and provides functions to access and manipulate
+
+```python
+shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
+```
+
+### Create subject hierarchy item
+
+```python
+# If it is for a data node, it is automatically created, but the create function can be used to set parent:
+shNode.CreateItem(parentItemID, dataNode)
+# If it is a hierarchy item without a data node, then the create function must be used:
+shNode.CreateSubjectItem(parentItemID, name)
+shNode.CreateFolderItem(parentItemID, name)
+shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)
+```
+
+### Get subject hierarchy item
+
+Items in subject hierarchy are uniquely identified by integer IDs
+
+```python
+# Get scene item ID first because it is the root item:
+sceneItemID = shNode.GetSceneItemID()
+# Get direct child by name
+subjectItemID = shNode.GetItemChildWithName(sceneItemID, "Subject_1")
+# Get item for data node
+itemID = shNode.GetItemByDataNode(dataNode)
+# Get item by UID (such as DICOM)
+itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)
+itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)
+# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)
+invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()
+```
+
+### Traverse children of a subject hierarchy item
+
+```python
+children = vtk.vtkIdList()
+shNode.GetItemChildren(parent, children) # Add a third argument with value True for recursive query
+for i in range(children.GetNumberOfIds()):
+ child = children.GetId(i)
+ ...
+```
+
+### Manipulate subject hierarchy item
+
+Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
+
+```python
+# Set item name
+shNode.SetItemName(itemID, "NewName")
+# Set item parent (reparent)
+shNode.SetItemParent(itemID, newParentItemID)
+# Set visibility of data node associated to an item
+shNode.SetItemDisplayVisibility(itemID, 1)
+# Set visibility of whole branch
+# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.
+# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.
+# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility
+# From python, this is how to trigger creating a folder display node
+pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()
+folderPlugin = pluginHandler.pluginByName("Folder")
+folderPlugin.setDisplayVisibility(folderItemID, 1)
+```
+
+### Filter items in TreeView or ComboBox
+
+Displayed items can be filtered using *setAttributeFilter* method. An example of the usage can be found in the [unit test](https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360). Modified version here:
+
+```python
+print(shTreeView.displayedItemCount()) # 5
+shTreeView.setAttributeFilter("DICOM.Modality") # Nodes must have this attribute
+print(shTreeView.displayedItemCount()) # 3
+shTreeView.setAttributeFilter("DICOM.Modality","CT") # Have attribute and equal ``CT``
+print(shTreeView.displayedItemCount()) # 1
+shTreeView.removeAttributeFilter()
+print(shTreeView.displayedItemCount()) # 5
+```
+
+### Listen to subject hierarchy item events
+
+The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).
+
+```python
+class MyListenerClass(VTKObservationMixin):
+ def __init__(self):
+ VTKObservationMixin.__init__(self)
+
+ shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
+ self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)
+
+ @vtk.calldata_type(vtk.VTK_LONG)
+ def shItemModifiedEvent(self, caller, eventId, callData):
+ print("SH Node modified")
+ print("SH item ID: {0}".format(callData))
+```
+
+### Subject hierarchy plugin offering view context menu action
+
+If an object that supports view context menus (e.g. markups) is right-clicked in a slice or 3D view, it can offer custom actions. Due to internal limitations these plugins must be set up differently, as explained [here](https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107). This example makes it easier to create such a plugin.
+
+```python
+import vtk, qt, ctk, slicer
+from slicer.ScriptedLoadableModule import *
+from slicer.util import VTKObservationMixin
+
+from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin
+
+class ViewContextMenu(ScriptedLoadableModule):
+"""Uses ScriptedLoadableModule base class, available at:
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+ """
+
+ def __init__(self, parent):
+ ScriptedLoadableModule.__init__(self, parent)
+ self.parent.title = "Markup Editor"
+ self.parent.categories = ["SlicerMorph", "Labs"]
+ self.parent.dependencies = []
+ self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]
+ self.parent.helpText = """
+A tool to manipulate Markups using the Segment Editor as a geometry backend
+"""
+ self.parent.helpText += self.getDefaultModuleDocumentationLink()
+ self.parent.acknowledgementText = """
+This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,
+through a NSF ABI Development grant, "An Integrated Platform for Retrieval,
+Visualization and Analysis of 3D Morphology From Digital Biological Collections"
+(Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).
+This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,
+Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.
+and was partially funded by NIH grant 3P41RR013218-12S1.
+"""
+
+ #
+ # register subject hierarchy plugin once app is initialized
+ #
+ def onStartupCompleted():
+ import SubjectHierarchyPlugins
+ from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin
+ scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)
+ scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)
+ pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
+ pluginHandler.registerPlugin(scriptedPlugin)
+ print("ViewContextMenuSubjectHierarchyPlugin loaded")
+
+ slicer.app.connect("startupCompleted()", onStartupCompleted)
+
+
+class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):
+
+ # Necessary static member to be able to set python source to scripted subject hierarchy plugin
+ filePath = __file__
+
+ def __init__(self, scriptedPlugin):
+ self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)
+ self.viewAction.objectName = "CustomViewAction"
+ self.viewAction.connect("triggered()", self.onViewAction)
+
+ def onViewAction(self):
+ print(f"VIEW ACTION")
+
+ def viewContextMenuActions(self):
+ return [self.viewAction]
+
+ def showViewContextMenuActionsForItem(self, itemID, eventData=None):
+ pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
+ pluginLogic = pluginHandler.pluginLogic()
+ menuActions = list(pluginLogic.availableViewMenuActionNames())
+ menuActions.append("CustomViewAction")
+ pluginLogic.setDisplayedViewMenuActionNames(menuActions)
+ self.viewAction.visible = True
+```
+
+### Use whitelist to customize view menu
+
+When right-clicking certain types of nodes in the 2D/3D views, a subject hierarchy menu pops up. If menu actions need to be removed, a whitelist can be used to specify the ones that should show up.
+
+```python
+pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
+pluginLogic = pluginHandler.pluginLogic()
+menuActions = pluginLogic.availableViewMenuActionNames()
+# Returns ("RenamePointAction", "DeletePointAction", "ToggleSelectPointAction", "EditPropertiesAction")
+newActions = ["RenamePointAction"]
+pluginLogic.setDisplayedViewMenuActionNames(newActions)
+```
diff --git a/Docs/developer_guide/script_repository/subjecthierarchy.rst b/Docs/developer_guide/script_repository/subjecthierarchy.rst
deleted file mode 100644
index 4a56f16febf..00000000000
--- a/Docs/developer_guide/script_repository/subjecthierarchy.rst
+++ /dev/null
@@ -1,201 +0,0 @@
-Subject hierarchy
-~~~~~~~~~~~~~~~~~
-
-Get the pseudo-singleton subject hierarchy node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-It manages the whole hierarchy and provides functions to access and manipulate
-
-.. code-block:: python
-
- shNode = slicer.mrmlScene.GetSubjectHierarchyNode()
-
-
-Create subject hierarchy item
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # If it is for a data node, it is automatically created, but the create function can be used to set parent:
- shNode.CreateItem(parentItemID, dataNode)
- # If it is a hierarchy item without a data node, then the create function must be used:
- shNode.CreateSubjectItem(parentItemID, name)
- shNode.CreateFolderItem(parentItemID, name)
- shNode.CreateHierarchyItem(parentItemID, name, level) # Advanced method to set level attribute manually (usually subject, study, or folder, but it can be a virtual branch for example)
-
-Get subject hierarchy item
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Items in subject hierarchy are uniquely identified by integer IDs
-
-.. code-block:: python
-
- # Get scene item ID first because it is the root item:
- sceneItemID = shNode.GetSceneItemID()
- # Get direct child by name
- subjectItemID = shNode.GetItemChildWithName(sceneItemID, "Subject_1")
- # Get item for data node
- itemID = shNode.GetItemByDataNode(dataNode)
- # Get item by UID (such as DICOM)
- itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)
- itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)
- # Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)
- invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()
-
-Traverse children of a subject hierarchy item
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- children = vtk.vtkIdList()
- shNode.GetItemChildren(parent, children) # Add a third argument with value True for recursive query
- for i in range(children.GetNumberOfIds()):
- child = children.GetId(i)
- ...
-
-Manipulate subject hierarchy item
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.
-
-.. code-block:: python
-
- # Set item name
- shNode.SetItemName(itemID, "NewName")
- # Set item parent (reparent)
- shNode.SetItemParent(itemID, newParentItemID)
- # Set visibility of data node associated to an item
- shNode.SetItemDisplayVisibility(itemID, 1)
- # Set visibility of whole branch
- # Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.
- # The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.
- # Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility
- # From python, this is how to trigger creating a folder display node
- pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()
- folderPlugin = pluginHandler.pluginByName("Folder")
- folderPlugin.setDisplayVisibility(folderItemID, 1)
-
-Filter items in TreeView or ComboBox
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Displayed items can be filtered using *setAttributeFilter* method. An example of the usage can be found in the `unit test `__. Modified version here:
-
-.. code-block:: python
-
- print(shTreeView.displayedItemCount()) # 5
- shTreeView.setAttributeFilter("DICOM.Modality") # Nodes must have this attribute
- print(shTreeView.displayedItemCount()) # 3
- shTreeView.setAttributeFilter("DICOM.Modality","CT") # Have attribute and equal ``CT``
- print(shTreeView.displayedItemCount()) # 1
- shTreeView.removeAttributeFilter()
- print(shTreeView.displayedItemCount()) # 5
-
-Listen to subject hierarchy item events
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-The subject hierarchy node sends the node item id as calldata. Item IDs are vtkIdType, which are NOT vtkObjects. You need to use vtk.calldata_type(vtk.VTK_LONG) (otherwise the application crashes).
-
-.. code-block:: python
-
- class MyListenerClass(VTKObservationMixin):
- def __init__(self):
- VTKObservationMixin.__init__(self)
-
- shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)
- self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)
-
- @vtk.calldata_type(vtk.VTK_LONG)
- def shItemModifiedEvent(self, caller, eventId, callData):
- print("SH Node modified")
- print("SH item ID: {0}".format(callData))
-
-Subject hierarchy plugin offering view context menu action
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If an object that supports view context menus (e.g. markups) is right-clicked in a slice or 3D view, it can offer custom actions. Due to internal limitations these plugins must be set up differently, as explained `here `__. This example makes it easier to create such a plugin.
-
-.. code:: python
-
- import vtk, qt, ctk, slicer
- from slicer.ScriptedLoadableModule import *
- from slicer.util import VTKObservationMixin
-
- from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin
-
- class ViewContextMenu(ScriptedLoadableModule):
- """Uses ScriptedLoadableModule base class, available at:
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
- """
-
- def __init__(self, parent):
- ScriptedLoadableModule.__init__(self, parent)
- self.parent.title = "Markup Editor"
- self.parent.categories = ["SlicerMorph", "Labs"]
- self.parent.dependencies = []
- self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]
- self.parent.helpText = """
- A tool to manipulate Markups using the Segment Editor as a geometry backend
- """
- self.parent.helpText += self.getDefaultModuleDocumentationLink()
- self.parent.acknowledgementText = """
- This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,
- through a NSF ABI Development grant, "An Integrated Platform for Retrieval,
- Visualization and Analysis of 3D Morphology From Digital Biological Collections"
- (Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).
- This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,
- Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.
- and was partially funded by NIH grant 3P41RR013218-12S1.
- """
-
- #
- # register subject hierarchy plugin once app is initialized
- #
- def onStartupCompleted():
- import SubjectHierarchyPlugins
- from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin
- scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)
- scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)
- pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
- pluginHandler.registerPlugin(scriptedPlugin)
- print("ViewContextMenuSubjectHierarchyPlugin loaded")
-
- slicer.app.connect("startupCompleted()", onStartupCompleted)
-
-
- class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):
-
- # Necessary static member to be able to set python source to scripted subject hierarchy plugin
- filePath = __file__
-
- def __init__(self, scriptedPlugin):
- self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)
- self.viewAction.objectName = "CustomViewAction"
- self.viewAction.connect("triggered()", self.onViewAction)
-
- def onViewAction(self):
- print(f"VIEW ACTION")
-
- def viewContextMenuActions(self):
- return [self.viewAction]
-
- def showViewContextMenuActionsForItem(self, itemID, eventData=None):
- pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
- pluginLogic = pluginHandler.pluginLogic()
- menuActions = list(pluginLogic.availableViewMenuActionNames())
- menuActions.append("CustomViewAction")
- pluginLogic.setDisplayedViewMenuActionNames(menuActions)
- self.viewAction.visible = True
-
-Use whitelist to customize view menu
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-When right-clicking certain types of nodes in the 2D/3D views, a subject hierarchy menu pops up. If menu actions need to be removed, a whitelist can be used to specify the ones that should show up.
-
-.. code-block:: python
-
- pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()
- pluginLogic = pluginHandler.pluginLogic()
- menuActions = pluginLogic.availableViewMenuActionNames()
- # Returns ("RenamePointAction", "DeletePointAction", "ToggleSelectPointAction", "EditPropertiesAction")
- newActions = ["RenamePointAction"]
- pluginLogic.setDisplayedViewMenuActionNames(newActions)
diff --git a/Docs/developer_guide/script_repository/tractography.md b/Docs/developer_guide/script_repository/tractography.md
new file mode 100644
index 00000000000..98ae9062f54
--- /dev/null
+++ b/Docs/developer_guide/script_repository/tractography.md
@@ -0,0 +1,75 @@
+## Tractography
+
+### Export a tract (FiberBundle) to Blender, including color
+
+:::{note}
+
+An interactive version of this script is now included in the [SlicerDMRI extension](http://dmri.slicer.org) ([module code](https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY)). After installing SlicerDMRI, go to *Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)*.
+
+:::
+
+The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
+
+```python
+lineDisplayNode = getNode("*LineDisplay*")
+plyFilePath = "/tmp/fibers.ply"
+
+tuber = vtk.vtkTubeFilter()
+tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
+tuber.Update()
+tubes = tuber.GetOutputDataObject(0)
+scalars = tubes.GetPointData().GetArray(0)
+scalars.SetName("scalars")
+
+triangles = vtk.vtkTriangleFilter()
+triangles.SetInputData(tubes)
+triangles.Update()
+
+colorNode = lineDisplayNode.GetColorNode()
+lookupTable = vtk.vtkLookupTable()
+lookupTable.DeepCopy(colorNode.GetLookupTable())
+lookupTable.SetTableRange(0,1)
+
+plyWriter = vtk.vtkPLYWriter()
+plyWriter.SetInputData(triangles.GetOutput())
+plyWriter.SetLookupTable(lookupTable)
+plyWriter.SetArrayName("scalars")
+
+plyWriter.SetFileName(plyFilePath)
+plyWriter.Write()
+```
+
+### Iterate over tract (FiberBundle) streamline points
+
+This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
+
+```python
+from vtk.util.numpy_support import vtk_to_numpy
+
+fb = getNode("FiberBundle_F") # <- fill in node ID here
+
+# get point data as 1d array
+points = slicer.util.arrayFromModelPoints(fb)
+
+# get line cell ids as 1d array
+line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
+
+# VTK cell ids are stored as
+# [ N0 c0_id0 ... c0_id0
+# N1 c1_id0 ... c1_idN1 ]
+# so we need to
+# - read point count for each line (cell)
+# - grab the ids in that range from `line_ids` array defined above
+# - index the `points` array by those ids
+cur_idx = 1
+for _ in range(pd.GetLines().GetNumberOfCells()):
+ # - read point count for this line (cell)
+ count = lines[cur_idx - 1]
+ # - grab the ids in that range from `lines`
+ index_array = line_ids[ cur_idx : cur_idx + count]
+ # update to the next range
+ cur_idx += count + 1
+ # - index the point array by those ids
+ line_points = points[index_array]
+ # do work here
+```
diff --git a/Docs/developer_guide/script_repository/tractography.rst b/Docs/developer_guide/script_repository/tractography.rst
deleted file mode 100644
index 97386210575..00000000000
--- a/Docs/developer_guide/script_repository/tractography.rst
+++ /dev/null
@@ -1,77 +0,0 @@
-Tractography
-~~~~~~~~~~~~
-
-Export a tract (FiberBundle) to Blender, including color
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Note: an interactive version of this script is now included in the `SlicerDMRI extension `__ (`module code `__). After installing SlicerDMRI, go to *Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)*.
-
-The example below shows how to export a tractography "FiberBundleNode" to a PLY file:
-
-.. code-block:: python
-
- lineDisplayNode = getNode("*LineDisplay*")
- plyFilePath = "/tmp/fibers.ply"
-
- tuber = vtk.vtkTubeFilter()
- tuber.SetInputData(lineDisplayNode.GetOutputPolyData())
- tuber.Update()
- tubes = tuber.GetOutputDataObject(0)
- scalars = tubes.GetPointData().GetArray(0)
- scalars.SetName("scalars")
-
- triangles = vtk.vtkTriangleFilter()
- triangles.SetInputData(tubes)
- triangles.Update()
-
- colorNode = lineDisplayNode.GetColorNode()
- lookupTable = vtk.vtkLookupTable()
- lookupTable.DeepCopy(colorNode.GetLookupTable())
- lookupTable.SetTableRange(0,1)
-
- plyWriter = vtk.vtkPLYWriter()
- plyWriter.SetInputData(triangles.GetOutput())
- plyWriter.SetLookupTable(lookupTable)
- plyWriter.SetArrayName("scalars")
-
- plyWriter.SetFileName(plyFilePath)
- plyWriter.Write()
-
-Iterate over tract (FiberBundle) streamline points
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to access the points in each line of a FiberBundle as a numpy array (view).
-
-.. code-block:: python
-
- from vtk.util.numpy_support import vtk_to_numpy
-
- fb = getNode("FiberBundle_F") # <- fill in node ID here
-
- # get point data as 1d array
- points = slicer.util.arrayFromModelPoints(fb)
-
- # get line cell ids as 1d array
- line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())
-
- # VTK cell ids are stored as
- # [ N0 c0_id0 ... c0_id0
- # N1 c1_id0 ... c1_idN1 ]
- # so we need to
- # - read point count for each line (cell)
- # - grab the ids in that range from `line_ids` array defined above
- # - index the `points` array by those ids
- cur_idx = 1
- for _ in range(pd.GetLines().GetNumberOfCells()):
- # - read point count for this line (cell)
- count = lines[cur_idx - 1]
-
- # - grab the ids in that range from `lines`
- index_array = line_ids[ cur_idx : cur_idx + count]
- # update to the next range
- cur_idx += count + 1
-
- # - index the point array by those ids
- line_points = points[index_array]
-
- # do work here
diff --git a/Docs/developer_guide/script_repository/transforms.md b/Docs/developer_guide/script_repository/transforms.md
new file mode 100644
index 00000000000..70715307b9a
--- /dev/null
+++ b/Docs/developer_guide/script_repository/transforms.md
@@ -0,0 +1,285 @@
+## Transforms
+
+### Get a notification if a transform is modified
+
+```python
+def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):
+ transformMatrix = vtk.vtkMatrix4x4()
+ transformNode.GetMatrixTransformToWorld(transformMatrix)
+ print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))
+
+transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
+transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)
+```
+
+### Rotate a node around a specified point
+
+Set up the scene:
+
+- Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.
+- Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.
+- Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the center of rotation point.
+
+Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.
+
+```python
+# This markups fiducial node specifies the center of rotation
+centerOfRotationMarkupsNode = getNode("F")
+# This transform can be edited in Transforms module
+rotationTransformNode = getNode("LinearTransform_3")
+# This transform has to be applied to the image, model, etc.
+finalTransformNode = getNode("LinearTransform_4")
+
+def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
+ rotationMatrix = vtk.vtkMatrix4x4()
+ rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
+ rotationCenterPointCoord = [0.0, 0.0, 0.0]
+ centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)
+ finalTransform = vtk.vtkTransform()
+ finalTransform.Translate(rotationCenterPointCoord)
+ finalTransform.Concatenate(rotationMatrix)
+ finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])
+ finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
+
+# Manual initial update
+updateFinalTransform()
+
+# Automatic update when point is moved or transform is modified
+rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
+centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
+
+# Execute these lines to stop automatic updates:
+# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
+# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)
+```
+
+### Rotate a node around a specified line
+
+Set up the scene:
+
+- Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.
+- Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.
+- Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.
+
+Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.
+
+```python
+# This markups fiducial node specifies the center of rotation
+rotationAxisMarkupsNode = getNode("L")
+# This transform can be edited in Transforms module (Edit / Rotation / IS slider)
+rotationTransformNode = getNode("LinearTransform_3")
+# This transform has to be applied to the image, model, etc.
+finalTransformNode = getNode("LinearTransform_4")
+
+def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
+ import numpy as np
+ rotationAxisPoint1_World = np.zeros(3)
+ rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)
+ rotationAxisPoint2_World = np.zeros(3)
+ rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)
+ axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World
+ axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)
+ # Get transformation between world coordinate system and rotation axis aligned coordinate system
+ worldToRotationAxisTransform = vtk.vtkMatrix4x4()
+ p=vtk.vtkPlaneSource()
+ p.SetNormal(axisDirectionZ_World)
+ axisOrigin = np.array(p.GetOrigin())
+ axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin
+ axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin
+ rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))
+ rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)
+ worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))
+ # Compute transformation chain
+ rotationMatrix = vtk.vtkMatrix4x4()
+ rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
+ finalTransform = vtk.vtkTransform()
+ finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)
+ finalTransform.Concatenate(rotationMatrix)
+ finalTransform.Concatenate(worldToRotationAxisTransformMatrix)
+ finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
+
+# Manual initial update
+updateFinalTransform()
+
+# Automatic update when point is moved or transform is modified
+rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
+rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
+
+# Execute these lines to stop automatic updates:
+# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
+# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)
+```
+
+### Convert between ITK and Slicer linear transforms
+
+```python
+# Copy the content between the following triple-quotes to a file called 'LinearTransform.tfm', and load into Slicer
+
+tfm_file = """#Insight Transform File V1.0
+#Transform 0
+Transform: AffineTransform_double_3_3
+Parameters: 0.929794207512361 0.03834792453582355 -0.3660767246906854 -0.2694570325150706 0.7484457003494506 -0.6059884002657121 0.2507501531497781 0.6620864522947292 0.7062335947709847 -46.99999999999999 49 17.00000000000002
+FixedParameters: 0 0 0"""
+
+import numpy as np
+
+# get the upper 3x4 transform matrix
+m = np.array( tfm_file.splitlines()[3].split()[1:], dtype=np.float64 )
+
+# pad to a 4x4 matrix
+m2 = np.vstack((m.reshape(4,3).T, [0,0,0,1]))
+
+def itktfm_to_slicer(tfm):
+ ras2lps = np.diag([-1, -1, 1, 1])
+ mt = ras2lps @ m2 @ ras2lps
+ mt[:3,3] = mt[:3,:3] @ mt[:3,3]
+ return mt
+
+print( itktfm_to_slicer(m2) )
+
+# Running the code above in Python should print the following output.
+# This output should match the display the loaded .tfm file in the Transforms module:
+# [[ 0.92979 -0.26946 -0.25075 52.64097]
+# [ 0.03835 0.74845 -0.66209 -46.12696]
+# [ 0.36608 0.60599 0.70623 -0.48185]
+# [ 0. 0. 0. 1. ]]
+```
+
+C++:
+
+```cpp
+// Convert from LPS (ITK) to RAS (Slicer)
+// input: transformVtk_LPS matrix in vtkMatrix4x4 in resampling convention in LPS
+// output: transformVtk_RAS matrix in vtkMatri4x4 in modeling convention in RAS
+
+// Tras = lps2ras * Tlps * ras2lps
+vtkSmartPointer lps2ras = vtkSmartPointer::New();
+lps2ras->SetElement(0,0,-1);
+lps2ras->SetElement(1,1,-1);
+vtkMatrix4x4* ras2lps = lps2ras; // lps2ras is diagonal therefore the inverse is identical
+vtkMatrix4x4::Multiply4x4(lps2ras, transformVtk_LPS, transformVtk_LPS);
+vtkMatrix4x4::Multiply4x4(transformVtk_LPS, ras2lps, transformVtk_RAS);
+
+// Convert the sense of the transform (from ITK resampling to Slicer modeling transform)
+vtkMatrix4x4::Invert(transformVtk_RAS);
+```
+
+### Apply a transform to a transformable node
+
+Python:
+
+```python
+transformToParentMatrix = vtk.vtkMatrix4x4()
+...
+transformNode.SetMatrixTransformToParent(matrix)
+transformableNode.SetAndObserveTransformNodeID(transformNode.GetID())
+```
+
+C++:
+
+```cpp
+vtkNew transformNode;
+scene->AddNode(transformNode.GetPointer());
+...
+vtkNew matrix;
+...
+transform->SetMatrixTransformToParent( matrix.GetPointer() );
+...
+vtkMRMLVolumeNode* transformableNode = ...; // or vtkMRMLModelNode*...
+transformableNode->SetAndObserveTransformNodeID( transformNode->GetID() );
+```
+
+### Set a transformation matrix from a numpy array
+
+```python
+# Create a 4x4 transformation matrix as numpy array
+transformNode = ...
+transformMatrixNP = np.array(
+ [[0.92979,-0.26946,-0.25075,52.64097],
+ [0.03835, 0.74845, -0.66209, -46.12696],
+ [0.36608, 0.60599, 0.70623, -0.48185],
+ [0, 0, 0, 1]])
+
+# Update matrix in transform node
+transformNode.SetAndObserveMatrixTransformToParent(slicer.util.vtkMatrixFromArray(transformMatrixNP))
+```
+
+### Example of moving a volume along a trajectory using a transform
+
+```python
+# Load sample volume
+import SampleData
+sampleDataLogic = SampleData.SampleDataLogic()
+mrHead = sampleDataLogic.downloadMRHead()
+
+# Create transform and apply to sample volume
+transformNode = slicer.vtkMRMLTransformNode()
+slicer.mrmlScene.AddNode(transformNode)
+mrHead.SetAndObserveTransformNodeID(transformNode.GetID())
+
+# How to move a volume along a trajectory using a transform:
+import time
+import math
+transformMatrix = vtk.vtkMatrix4x4()
+for xPos in range(-30,30):
+ transformMatrix.SetElement(0,3, xPos)
+ transformMatrix.SetElement(1,3, math.sin(xPos)*10)
+ transformNode.SetMatrixTransformToParent(transformMatrix)
+ slicer.app.processEvents()
+ time.sleep(0.02)
+# Note: for longer animations use qt.QTimer.singleShot(100, callbackFunction)
+# instead of a for loop.
+```
+
+### Combine multiple transforms
+
+Because a transform node is also a transformable node, it is possible to concatenate transforms with each other.
+
+Python:
+
+```python
+transformNode2.SetAndObserveTransformNodeID(transformNode1.GetID())
+transformableNode.SetAndObserveTransformNodeID(transformNode2.GetID())
+```
+
+C++:
+
+```cpp
+vtkMRMLTransformNode* transformNode1 = ...;
+vtkMRMLTransformNode* transformNode2 = ...;
+transformNode2->SetAndObserveTransformNodeID(transformNode1->GetID());
+transformable->SetAndObserveTransformNodeID(transformNode2->GetID());
+```
+
+### Convert the transform to a grid transform
+
+Any transform can be converted to a grid transform (also known as displacement field transform):
+
+```python
+transformNode=slicer.util.getNode('LinearTransform_3')
+referenceVolumeNode=slicer.util.getNode('MRHead')
+slicer.modules.transforms.logic().ConvertToGridTransform(transformNode, referenceVolumeNode)
+```
+
+:::{note}
+
+- Conversion to grid transform is useful because some software cannot use inverse transforms or can only use grid transforms.
+- Displacement field transforms are saved to file differently than displacement field volumes: displacement vectors in transforms are converted to LPS coordinate system on saving, displacement vectors in volumes are saved to file unchanged.
+
+:::
+
+### Export the displacement magnitude of the transform as a volume
+
+```python
+transformNode=slicer.util.getNode('LinearTransform_3')
+referenceVolumeNode=slicer.util.getNode('MRHead')
+slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, False)
+```
+
+### Visualize the displacement magnitude as a color volume
+
+```python
+transformNode=slicer.util.getNode('LinearTransform_3')
+referenceVolumeNode=slicer.util.getNode('MRHead')
+slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, True)
+```
diff --git a/Docs/developer_guide/script_repository/transforms.rst b/Docs/developer_guide/script_repository/transforms.rst
deleted file mode 100644
index 169d9fdfc43..00000000000
--- a/Docs/developer_guide/script_repository/transforms.rst
+++ /dev/null
@@ -1,292 +0,0 @@
-Transforms
-~~~~~~~~~~
-
-Get a notification if a transform is modified
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):
- transformMatrix = vtk.vtkMatrix4x4()
- transformNode.GetMatrixTransformToWorld(transformMatrix)
- print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))
-
- transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")
- transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)
-
-Rotate a node around a specified point
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Set up the scene:
-
-- Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.
-- Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.
-- Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the center of rotation point.
-
-Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.
-
-.. code-block:: python
-
- # This markups fiducial node specifies the center of rotation
- centerOfRotationMarkupsNode = getNode("F")
- # This transform can be edited in Transforms module
- rotationTransformNode = getNode("LinearTransform_3")
- # This transform has to be applied to the image, model, etc.
- finalTransformNode = getNode("LinearTransform_4")
-
- def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
- rotationMatrix = vtk.vtkMatrix4x4()
- rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
- rotationCenterPointCoord = [0.0, 0.0, 0.0]
- centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)
- finalTransform = vtk.vtkTransform()
- finalTransform.Translate(rotationCenterPointCoord)
- finalTransform.Concatenate(rotationMatrix)
- finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])
- finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
-
- # Manual initial update
- updateFinalTransform()
-
- # Automatic update when point is moved or transform is modified
- rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
- centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
-
- # Execute these lines to stop automatic updates:
- # rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
- # centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)
-
-Rotate a node around a specified line
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Set up the scene:
-
-- Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.
-- Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.
-- Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.
-
-Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.
-
-.. code-block:: python
-
- # This markups fiducial node specifies the center of rotation
- rotationAxisMarkupsNode = getNode("L")
- # This transform can be edited in Transforms module (Edit / Rotation / IS slider)
- rotationTransformNode = getNode("LinearTransform_3")
- # This transform has to be applied to the image, model, etc.
- finalTransformNode = getNode("LinearTransform_4")
-
- def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):
- import numpy as np
- rotationAxisPoint1_World = np.zeros(3)
- rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)
- rotationAxisPoint2_World = np.zeros(3)
- rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)
- axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World
- axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)
- # Get transformation between world coordinate system and rotation axis aligned coordinate system
- worldToRotationAxisTransform = vtk.vtkMatrix4x4()
- p=vtk.vtkPlaneSource()
- p.SetNormal(axisDirectionZ_World)
- axisOrigin = np.array(p.GetOrigin())
- axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin
- axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin
- rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))
- rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)
- worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))
- # Compute transformation chain
- rotationMatrix = vtk.vtkMatrix4x4()
- rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)
- finalTransform = vtk.vtkTransform()
- finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)
- finalTransform.Concatenate(rotationMatrix)
- finalTransform.Concatenate(worldToRotationAxisTransformMatrix)
- finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())
-
- # Manual initial update
- updateFinalTransform()
-
- # Automatic update when point is moved or transform is modified
- rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)
- rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)
-
- # Execute these lines to stop automatic updates:
- # rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)
- # rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)
-
-
-Convert between ITK and Slicer linear transforms
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Copy the content between the following triple-quotes to a file called 'LinearTransform.tfm', and load into Slicer
-
- tfm_file = """#Insight Transform File V1.0
- #Transform 0
- Transform: AffineTransform_double_3_3
- Parameters: 0.929794207512361 0.03834792453582355 -0.3660767246906854 -0.2694570325150706 0.7484457003494506 -0.6059884002657121 0.2507501531497781 0.6620864522947292 0.7062335947709847 -46.99999999999999 49 17.00000000000002
- FixedParameters: 0 0 0"""
-
- import numpy as np
-
- # get the upper 3x4 transform matrix
- m = np.array( tfm_file.splitlines()[3].split()[1:], dtype=np.float64 )
-
- # pad to a 4x4 matrix
- m2 = np.vstack((m.reshape(4,3).T, [0,0,0,1]))
-
- def itktfm_to_slicer(tfm):
- ras2lps = np.diag([-1, -1, 1, 1])
- mt = ras2lps @ m2 @ ras2lps
- mt[:3,3] = mt[:3,:3] @ mt[:3,3]
- return mt
-
- print( itktfm_to_slicer(m2) )
-
- # Running the code above in Python should print the following output.
- # This output should match the display the loaded .tfm file in the Transforms module:
- # [[ 0.92979 -0.26946 -0.25075 52.64097]
- # [ 0.03835 0.74845 -0.66209 -46.12696]
- # [ 0.36608 0.60599 0.70623 -0.48185]
- # [ 0. 0. 0. 1. ]]
-
-C++:
-
-.. code-block:: cpp
-
- // Convert from LPS (ITK) to RAS (Slicer)
- // input: transformVtk_LPS matrix in vtkMatrix4x4 in resampling convention in LPS
- // output: transformVtk_RAS matrix in vtkMatri4x4 in modeling convention in RAS
-
- // Tras = lps2ras * Tlps * ras2lps
- vtkSmartPointer lps2ras = vtkSmartPointer::New();
- lps2ras->SetElement(0,0,-1);
- lps2ras->SetElement(1,1,-1);
- vtkMatrix4x4* ras2lps = lps2ras; // lps2ras is diagonal therefore the inverse is identical
- vtkMatrix4x4::Multiply4x4(lps2ras, transformVtk_LPS, transformVtk_LPS);
- vtkMatrix4x4::Multiply4x4(transformVtk_LPS, ras2lps, transformVtk_RAS);
-
- // Convert the sense of the transform (from ITK resampling to Slicer modeling transform)
- vtkMatrix4x4::Invert(transformVtk_RAS);
-
-Apply a transform to a transformable node
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- transformToParentMatrix = vtk.vtkMatrix4x4()
- ...
- transformNode.SetMatrixTransformToParent(matrix)
- transformableNode.SetAndObserveTransformNodeID(transformNode.GetID())
-
-C++:
-
-.. code-block:: python
-
- vtkNew transformNode;
- scene->AddNode(transformNode.GetPointer());
- ...
- vtkNew matrix;
- ...
- transform->SetMatrixTransformToParent( matrix.GetPointer() );
- ...
- vtkMRMLVolumeNode* transformableNode = ...; // or vtkMRMLModelNode*...
- transformableNode->SetAndObserveTransformNodeID( transformNode->GetID() );
-
-Set a transformation matrix from a numpy array
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Create a 4x4 transformation matrix as numpy array
- transformNode = ...
- transformMatrixNP = np.array(
- [[0.92979,-0.26946,-0.25075,52.64097],
- [0.03835, 0.74845, -0.66209, -46.12696],
- [0.36608, 0.60599, 0.70623, -0.48185],
- [0, 0, 0, 1]])
-
- # Update matrix in transform node
- transformNode.SetAndObserveMatrixTransformToParent(slicer.util.vtkMatrixFromArray(transformMatrixNP))
-
-Example of moving a volume along a trajectory using a transform
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- # Load sample volume
- import SampleData
- sampleDataLogic = SampleData.SampleDataLogic()
- mrHead = sampleDataLogic.downloadMRHead()
-
- # Create transform and apply to sample volume
- transformNode = slicer.vtkMRMLTransformNode()
- slicer.mrmlScene.AddNode(transformNode)
- mrHead.SetAndObserveTransformNodeID(transformNode.GetID())
-
- # How to move a volume along a trajectory using a transform:
- import time
- import math
- transformMatrix = vtk.vtkMatrix4x4()
- for xPos in range(-30,30):
- transformMatrix.SetElement(0,3, xPos)
- transformMatrix.SetElement(1,3, math.sin(xPos)*10)
- transformNode.SetMatrixTransformToParent(transformMatrix)
- slicer.app.processEvents()
- time.sleep(0.02)
- # Note: for longer animations use qt.QTimer.singleShot(100, callbackFunction)
- # instead of a for loop.
-
-Combine multiple transforms
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Because a transform node is also a transformable node, it is possible to concatenate transforms with each other.
-
-.. code-block:: python
-
- transformNode2.SetAndObserveTransformNodeID(transformNode1.GetID())
- transformableNode.SetAndObserveTransformNodeID(transformNode2.GetID())
-
-C++:
-
-.. code-block:: cpp
-
- vtkMRMLTransformNode* transformNode1 = ...;
- vtkMRMLTransformNode* transformNode2 = ...;
- transformNode2->SetAndObserveTransformNodeID(transformNode1->GetID());
- transformable->SetAndObserveTransformNodeID(transformNode2->GetID());
-
-Convert the transform to a grid transform
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Any transform can be converted to a grid transform (also known as displacement field transform):
-
-.. code-block:: python
-
- transformNode=slicer.util.getNode('LinearTransform_3')
- referenceVolumeNode=slicer.util.getNode('MRHead')
- slicer.modules.transforms.logic().ConvertToGridTransform(transformNode, referenceVolumeNode)
-
-.. note::
-
- - Conversion to grid transform is useful because some software cannot use inverse transforms or can only use grid transforms.
- - Displacement field transforms are saved to file differently than displacement field volumes: displacement vectors in transforms are converted to LPS coordinate system on saving, displacement vectors in volumes are saved to file unchanged.
-
-Export the displacement magnitude of the transform as a volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- transformNode=slicer.util.getNode('LinearTransform_3')
- referenceVolumeNode=slicer.util.getNode('MRHead')
- slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, False)
-
-Visualize the displacement magnitude as a color volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- transformNode=slicer.util.getNode('LinearTransform_3')
- referenceVolumeNode=slicer.util.getNode('MRHead')
- slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, True)
diff --git a/Docs/developer_guide/script_repository/volumes.md b/Docs/developer_guide/script_repository/volumes.md
new file mode 100644
index 00000000000..c86005effd1
--- /dev/null
+++ b/Docs/developer_guide/script_repository/volumes.md
@@ -0,0 +1,924 @@
+## Volumes
+
+### Load volume from file
+
+```python
+loadedVolumeNode = slicer.util.loadVolume("c:/Users/abc/Documents/MRHead.nrrd")
+```
+
+Additional options may be specified in `properties` argument. For example, load an image stack by disabling `singleFile` option:
+
+```python
+loadedVolumeNode = slicer.util.loadVolume("c:/Users/abc/Documents/SomeImage/file001.png", {"singleFile": False})
+```
+
+:::{note}
+
+The following options can be passed to load volumes programmatically when using `qSlicerVolumesReader`:
+- `name` (string): Node name to set for the loaded volume
+- `labelmap` (bool, default=false): Load the file as labelmap volume
+- `singleFile` (bool, default=false): Force loading this file only (otherwise the loader may look for similar files in the same folder to load multiple slices as a 3D volume)
+- `autoWindowLevel` (bool, default=true): Automatically compute the window level based on the volume pixel intensities
+- `show` (bool, default=true): Show the volume in views after loading
+- `center` (bool, default=false): Apply a transform that places the volume in the patient coordinate system origin
+- `discardOrientation` (bool, default=false): Discard file orientation information.
+- `fileNames` (string list): List of files to be loaded as a volume
+- `colorNodeID` (string): ID of the color node used to display the volume. Default is `vtkMRMLColorTableNodeGrey` for scalar volume and `vtkMRMLColorTableNodeFileGenericColors.txt` for labelmap volume.
+
+:::
+
+### Save volume to file
+
+Get the first volume node in the scene and save as .nrrd file. To save in any other supported file format, change the output file name.
+
+```python
+volumeNode = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode')
+slicer.util.saveNode(volumeNode, "c:/tmp/test.nrrd")
+```
+
+### Load volume from .vti file
+
+Slicer does not provide reader for VTK XML image data file format (as they are not commonly used for storing medical images and they cannot store image axis directions) but such files can be read by using this script:
+
+```python
+reader=vtk.vtkXMLImageDataReader()
+reader.SetFileName("/path/to/file.vti")
+reader.Update()
+imageData = reader.GetOutput()
+spacing = imageData.GetSpacing()
+origin = imageData.GetOrigin()
+imageData.SetOrigin(0,0,0)
+imageData.SetSpacing(1,1,1)
+volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
+volumeNode.SetAndObserveImageData(imageData)
+volumeNode.SetSpacing(spacing)
+volumeNode.SetOrigin(origin)
+slicer.util.setSliceViewerLayers(volumeNode, fit=True)
+```
+
+### Load volume from a remote server
+
+Download a volume from a remote server by an URL and load it into the scene using the code snippets below.
+
+:::{note}
+
+Downloaded data is temporarily stored in the application's cache folder and if the checksum of the already downloaded data
+matches the specified checksum (:) then the file is retrieved from the cache instead of being downloaded
+again. To compute digest with algo *SHA256*, you can run {func}`slicer.util.computeChecksum("SHA256", "path/to/file")`.
+
+:::
+
+#### Simple download
+
+```python
+import SampleData
+sampleDataLogic = SampleData.SampleDataLogic()
+loadedNodes = sampleDataLogic.downloadFromURL(
+ nodeNames="MRHead",
+ fileNames="MR-head25.nrrd",
+ uris="https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93",
+ checksums="SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93")[0]
+```
+
+#### Download with interruptible progress reporting
+
+```python
+import SampleData
+
+def reportProgress(msg, level=None):
+ # Print progress in the console
+ print("Loading... {0}%".format(sampleDataLogic.downloadPercent))
+ # Abort download if cancel is clicked in progress bar
+ if slicer.progressWindow.wasCanceled:
+ raise Exception("download aborted")
+ # Update progress window
+ slicer.progressWindow.show()
+ slicer.progressWindow.activateWindow()
+ slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))
+ slicer.progressWindow.setLabelText("Downloading...")
+ # Process events to allow screen to refresh
+ slicer.app.processEvents()
+
+try:
+ volumeNode = None
+ slicer.progressWindow = slicer.util.createProgressDialog()
+ sampleDataLogic = SampleData.SampleDataLogic()
+ sampleDataLogic.logMessage = reportProgress
+ loadedNodes = sampleDataLogic.downloadFromURL(
+ nodeNames="MRHead",
+ fileNames="MR-head25.nrrd",
+ uris="https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93",
+ checksums="SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93")
+ volumeNode = loadedNodes[0]
+finally:
+ slicer.progressWindow.close()
+```
+
+### Show volume rendering automatically when a volume is loaded
+
+To show volume rendering of a volume automatically when it is loaded, add the lines below to your [.slicerrc.py file](../user_guide/settings.md#application-startup-file).
+
+```python
+@vtk.calldata_type(vtk.VTK_OBJECT)
+def onNodeAdded(caller, event, calldata):
+ node = calldata
+ if isinstance(node, slicer.vtkMRMLVolumeNode):
+ # Call showVolumeRendering using a timer instead of calling it directly
+ # to allow the volume loading to fully complete.
+ qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))
+
+def showVolumeRendering(volumeNode):
+ print("Show volume rendering of node " + volumeNode.GetName())
+ volRenLogic = slicer.modules.volumerendering.logic()
+ displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
+ displayNode.SetVisibility(True)
+ scalarRange = volumeNode.GetImageData().GetScalarRange()
+ if scalarRange[1]-scalarRange[0] < 1500:
+ # Small dynamic range, probably MRI
+ displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("MR-Default"))
+ else:
+ # Larger dynamic range, probably CT
+ displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("CT-Chest-Contrast-Enhanced"))
+
+slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)
+```
+
+### Show volume rendering using maximum intensity projection
+
+```python
+def showVolumeRenderingMIP(volumeNode, useSliceViewColors=True):
+ """Render volume using maximum intensity projection
+ :param useSliceViewColors: use the same colors as in slice views.
+ """
+ # Get/create volume rendering display node
+ volRenLogic = slicer.modules.volumerendering.logic()
+ displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode)
+ if not displayNode:
+ displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
+ # Choose MIP volume rendering preset
+ if useSliceViewColors:
+ volRenLogic.CopyDisplayToVolumeRenderingDisplayNode(displayNode)
+ else:
+ scalarRange = volumeNode.GetImageData().GetScalarRange()
+ if scalarRange[1]-scalarRange[0] < 1500:
+ # Small dynamic range, probably MRI
+ displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("MR-MIP"))
+ else:
+ # Larger dynamic range, probably CT
+ displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("CT-MIP"))
+ # Switch views to MIP mode
+ for viewNode in slicer.util.getNodesByClass("vtkMRMLViewNode"):
+ viewNode.SetRaycastTechnique(slicer.vtkMRMLViewNode.MaximumIntensityProjection)
+ # Show volume rendering
+ displayNode.SetVisibility(True)
+
+volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
+showVolumeRenderingMIP(volumeNode)
+```
+
+### Show volume rendering making soft tissues transparent
+
+```python
+def showTransparentRendering(volumeNode, maxOpacity=0.2, gradientThreshold=30.0):
+ """Make constant regions transparent and the entire volume somewhat transparent
+ :param maxOpacity: lower value makes the volume more transparent overall
+ (value is between 0.0 and 1.0)
+ :param gradientThreshold: regions that has gradient value below this threshold will be made transparent
+ (minimum value is 0.0, higher values make more tissues transparent, starting with soft tissues)
+ """
+ # Get/create volume rendering display node
+ volRenLogic = slicer.modules.volumerendering.logic()
+ displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode)
+ if not displayNode:
+ displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
+ # Set up gradient vs opacity transfer function
+ gradientOpacityTransferFunction = displayNode.GetVolumePropertyNode().GetVolumeProperty().GetGradientOpacity()
+ gradientOpacityTransferFunction.RemoveAllPoints()
+ gradientOpacityTransferFunction.AddPoint(0, 0.0)
+ gradientOpacityTransferFunction.AddPoint(gradientThreshold-1, 0.0)
+ gradientOpacityTransferFunction.AddPoint(gradientThreshold+1, maxOpacity)
+ # Show volume rendering
+ displayNode.SetVisibility(True)
+
+volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
+showTransparentRendering(volumeNode, 0.2, 30.0)
+```
+
+### Automatically load volumes that are copied into a folder
+
+This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.
+
+There are more efficient methods for file system monitoring or exchanging image data in real-time (for example, using OpenIGTLink), the example below is just for demonstration purposes.
+
+```python
+incomingVolumeFolder = "c:/tmp/incoming"
+incomingVolumesProcessed = []
+
+def checkForNewVolumes():
+ # Check if there is a new file in the
+ from os import listdir
+ from os.path import isfile, join
+ for f in listdir(incomingVolumeFolder):
+ if f in incomingVolumesProcessed:
+ # This is an incoming file, it was already there
+ continue
+ filePath = join(incomingVolumeFolder, f)
+ if not isfile(filePath):
+ # ignore directories
+ continue
+ logging.info("Loading new file: " + f)
+ incomingVolumesProcessed.append(f)
+ slicer.util.loadVolume(filePath)
+ # Check again in 3000ms
+ qt.QTimer.singleShot(3000, checkForNewVolumes)
+
+# Start monitoring
+checkForNewVolumes()
+```
+
+### Extract randomly oriented slabs of given shape from a volume
+
+Returns a numpy array of sliceCount random tiles.
+
+```python
+def randomSlices(volume, sliceCount, sliceShape):
+ layoutManager = slicer.app.layoutManager()
+ redWidget = layoutManager.sliceWidget("Red")
+ sliceNode = redWidget.mrmlSliceNode()
+ sliceNode.SetDimensions(*sliceShape, 1)
+ sliceNode.SetFieldOfView(*sliceShape, 1)
+ bounds = [0]*6
+ volume.GetRASBounds(bounds)
+ imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()
+
+ sliceSize = sliceShape[0] * sliceShape[1]
+ X = numpy.zeros([sliceCount, sliceSize])
+
+ for sliceIndex in range(sliceCount):
+ position = numpy.random.rand(3) * 2 - 1
+ position = [bounds[0] + bounds[1]-bounds[0] * position[0],
+ bounds[2] + bounds[3]-bounds[2] * position[1],
+ bounds[4] + bounds[5]-bounds[4] * position[2]]
+ normal = numpy.random.rand(3) * 2 - 1
+ normal = normal / numpy.linalg.norm(normal)
+ transverse = numpy.cross(normal, [0,0,1])
+ orientation = 0
+ sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2],
+ transverse[0], transverse[1], transverse[2],
+ position[0], position[1], position[2],
+ orientation)
+ if sliceIndex % 100 == 0:
+ slicer.app.processEvents()
+ imageReslice.Update()
+ imageData = imageReslice.GetOutputDataObject(0)
+ array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())
+ X[sliceIndex] = array
+ return X
+```
+
+### Clone a volume
+
+This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
+
+```python
+sourceVolumeNode = slicer.util.getNode("MRHead")
+volumesLogic = slicer.modules.volumes.logic()
+clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, "Cloned volume")
+```
+
+### Create a new volume
+
+This example shows how to create a new empty volume. The "Image Maker" extension contains a module that allows creating a volume from scratch without programming.
+
+```python
+nodeName = "MyNewVolume"
+imageSize = [512, 512, 512]
+voxelType=vtk.VTK_UNSIGNED_CHAR
+imageOrigin = [0.0, 0.0, 0.0]
+imageSpacing = [1.0, 1.0, 1.0]
+imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
+fillVoxelValue = 0
+
+# Create an empty image volume, filled with fillVoxelValue
+imageData = vtk.vtkImageData()
+imageData.SetDimensions(imageSize)
+imageData.AllocateScalars(voxelType, 1)
+imageData.GetPointData().GetScalars().Fill(fillVoxelValue)
+# Create volume node
+volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)
+volumeNode.SetOrigin(imageOrigin)
+volumeNode.SetSpacing(imageSpacing)
+volumeNode.SetIJKToRASDirections(imageDirections)
+volumeNode.SetAndObserveImageData(imageData)
+volumeNode.CreateDefaultDisplayNodes()
+volumeNode.CreateDefaultStorageNode()
+```
+
+C++:
+
+```cpp
+vtkNew imageData;
+imageData->SetDimensions(10,10,10); // image size
+imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1); // image type and number of components
+// initialize the pixels here
+
+vtkNew volumeNode;
+volumeNode->SetAndObserveImageData(imageData);
+volumeNode->SetOrigin( -10., -10., -10.);
+volumeNode->SetSpacing( 2., 2., 2. );
+mrmlScene->AddNode( volumeNode.GetPointer() );
+
+volumeNode->CreateDefaultDisplayNodes()
+```
+
+:::{note}
+
+Origin and spacing must be set on the volume node instead of the image data.
+
+:::
+
+### Get value of a volume at specific voxel coordinates
+
+This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.
+
+```python
+volumeNode = slicer.util.getNode("MRHead")
+ijk = [20,40,30] # volume voxel coordinates
+
+voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array
+voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)
+```
+
+### Modify voxels in a volume
+
+Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the `array` method and modified using standard numpy methods. For example, threshold a volume:
+
+```python
+nodeName = "MRHead"
+thresholdValue = 100
+voxelArray = array(nodeName) # get voxels as numpy array
+voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
+getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
+```
+
+This example shows how to change voxels values of the MRHead sample volume. The values will be computed by function `f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s`.
+
+```python
+volumeNode=slicer.util.getNode("MRHead")
+ijkToRas = vtk.vtkMatrix4x4()
+volumeNode.GetIJKToRASMatrix(ijkToRas)
+imageData=volumeNode.GetImageData()
+extent = imageData.GetExtent()
+for k in range(extent[4], extent[5]+1):
+ for j in range(extent[2], extent[3]+1):
+ for i in range(extent[0], extent[1]+1):
+ position_Ijk=[i, j, k, 1]
+ position_Ras=ijkToRas.MultiplyPoint(position_Ijk)
+ r=position_Ras[0]
+ a=position_Ras[1]
+ s=position_Ras[2]
+ functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s
+ imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)
+imageData.Modified()
+```
+
+### Get volume voxel coordinates from markup fiducial RAS coordinates
+
+This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
+
+```python
+# Inputs
+volumeNode = getNode("MRHead")
+markupsNode = getNode("F")
+markupsIndex = 0
+
+# Get point coordinate in RAS
+point_Ras = [0, 0, 0, 1]
+markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
+
+# If volume node is transformed, apply that transform to get volume's RAS coordinates
+transformRasToVolumeRas = vtk.vtkGeneralTransform()
+slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
+point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])
+
+# Get voxel coordinates from physical coordinates
+volumeRasToIjk = vtk.vtkMatrix4x4()
+volumeNode.GetRASToIJKMatrix(volumeRasToIjk)
+point_Ijk = [0, 0, 0, 1]
+volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)
+point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]
+
+# Print output
+print(point_Ijk)
+```
+
+### Get markup fiducial RAS coordinates from volume voxel coordinates
+
+This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.
+
+```python
+# Inputs
+volumeNode = getNode("MRHead")
+markupsNode = getNode("F")
+
+# Get voxel position in IJK coordinate system
+import numpy as np
+volumeArray = slicer.util.arrayFromVolume(volumeNode)
+# Get position of highest voxel value
+point_Kji = np.where(volumeArray == volumeArray.max())
+point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]
+
+# Get physical coordinates from voxel coordinates
+volumeIjkToRas = vtk.vtkMatrix4x4()
+volumeNode.GetIJKToRASMatrix(volumeIjkToRas)
+point_VolumeRas = [0, 0, 0, 1]
+volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)
+
+# If volume node is transformed, apply that transform to get volume's RAS coordinates
+transformVolumeRasToRas = vtk.vtkGeneralTransform()
+slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)
+point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])
+
+# Add a markup at the computed position and print its coordinates
+markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
+print(point_Ras)
+```
+
+### Get the values of all voxels for a label value
+
+If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:
+
+```python
+import numpy
+volume = array("Volume")
+label = array("Volume-label")
+points = numpy.where( label == 1 ) # or use another label number depending on what you segmented
+values = volume[points] # this will be a list of the label values
+values.mean() # should match the mean value of LabelStatistics calculation as a double-check
+numpy.savetxt("values.txt", values)
+```
+
+### Access values in a DTI tensor volume
+
+This example shows how to access individual tensors at the voxel level.
+
+First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’.
+
+Then open the python window: View->Python interactor.
+
+Use this command to access tensors through numpy:
+
+```python
+tensors = array("Output DTI Volume")
+```
+
+Type the following code into the Python window to access all tensor components using vtk commands:
+
+```python
+volumeNode=slicer.util.getNode("Output DTI Volume")
+imageData=volumeNode.GetImageData()
+tensors = imageData.GetPointData().GetTensors()
+extent = imageData.GetExtent()
+idx = 0
+for k in range(extent[4], extent[5]+1):
+ for j in range(extent[2], extent[3]+1):
+ for i in range(extent[0], extent[1]+1):
+ tensors.GetTuple9(idx)
+ idx += 1
+```
+
+### Change window/level (brightness/contrast) or colormap of a volume
+
+This example shows how to change window/level of the MRHead sample volume.
+
+```python
+volumeNode = getNode("MRHead")
+displayNode = volumeNode.GetDisplayNode()
+displayNode.AutoWindowLevelOff()
+displayNode.SetWindow(50)
+displayNode.SetLevel(100)
+```
+
+Change color mapping from grayscale to rainbow:
+
+```python
+displayNode.SetAndObserveColorNodeID("vtkMRMLColorTableNodeRainbow")
+```
+
+### Make mouse left-click and drag on the image adjust window/level
+
+In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:
+
+```python
+slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
+```
+
+### Reset field of view to show background volume maximized
+
+Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
+
+```python
+slicer.util.resetSliceViews()
+```
+
+### Rotate slice views to volume plane
+
+Aligns slice views to volume axes, shows original image acquisition planes in slice views.
+
+```python
+volumeNode = slicer.util.getNode("MRHead")
+layoutManager = slicer.app.layoutManager()
+for sliceViewName in layoutManager.sliceViewNames():
+ layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
+```
+
+### Iterate over current visible slice views, and set foreground and background images
+
+```python
+slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
+```
+
+Internally, this method performs something like this:
+
+```python
+layoutManager = slicer.app.layoutManager()
+for sliceViewName in layoutManager.sliceViewNames():
+ compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()
+ # Setup background volume
+ compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
+ # Setup foreground volume
+ compositeNode.SetForegroundVolumeID(ctVolume.GetID())
+ # Change opacity
+ compositeNode.SetForegroundOpacity(0.3)
+```
+
+### Show a volume in slice views
+
+Recommended:
+
+```python
+volumeNode = slicer.util.getNode("YourVolumeNode")
+slicer.util.setSliceViewerLayers(background=volumeNode)
+```
+
+or
+
+Show volume in all visible views where volume selection propagation is enabled:
+
+```python
+volumeNode = slicer.util.getNode("YourVolumeNode")
+applicationLogic = slicer.app.applicationLogic()
+selectionNode = applicationLogic.GetSelectionNode()
+selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
+applicationLogic.PropagateForegroundVolumeSelection(0)
+```
+
+or
+
+Show volume in selected views:
+
+```python
+n = slicer.util.getNode("YourVolumeNode")
+for color in ["Red", "Yellow", "Green"]:
+ slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
+```
+
+### Change opacity of foreground volume in slice views
+
+```python
+slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
+```
+
+or
+
+Change opacity in a selected view
+
+```python
+lm = slicer.app.layoutManager()
+sliceLogic = lm.sliceWidget("Red").sliceLogic()
+compositeNode = sliceLogic.GetSliceCompositeNode()
+compositeNode.SetForegroundOpacity(0.4)
+```
+
+### Turning off interpolation
+
+You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
+
+```python
+def NoInterpolate(caller,event):
+ for node in slicer.util.getNodes("*").values():
+ if node.IsA("vtkMRMLScalarVolumeDisplayNode"):
+ node.SetInterpolate(0)
+
+slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
+```
+
+You can place this code snippet in your [.slicerrc.py file](../user_guide/settings.md#application-startup-file) to always disable interpolation by default.
+
+### Running an ITK filter in Python using SimpleITK
+
+Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
+
+```python
+import SampleData
+import SimpleITK as sitk
+import sitkUtils
+
+# Get input volume node
+inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
+# Create new volume node for output
+outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", "MRHeadFiltered")
+
+# Run processing
+inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
+filter = sitk.SignedMaurerDistanceMapImageFilter()
+outputImage = filter.Execute(inputImage)
+sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
+
+# Show processing result
+slicer.util.setSliceViewerLayers(background=outputVolumeNode)
+```
+
+More information:
+
+- See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html
+- sitkUtils in Slicer is used for pushing and pulling images from Slicer to SimpleITK: https://github.com/Slicer/Slicer/blob/master/Base/Python/sitkUtils.py
+
+### Get axial slice as numpy array
+
+An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:
+
+```python
+import SampleData
+volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+sliceIndex = 12
+
+voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array
+slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array
+```
+
+### Get reformatted image from a slice viewer as numpy array
+
+Set up `red` slice viewer to show thick slab reconstructed from 3 slices:
+
+```python
+sliceNodeID = "vtkMRMLSliceNodeRed"
+
+# Get image data from slice view
+sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
+appLogic = slicer.app.applicationLogic()
+sliceLogic = appLogic.GetSliceLogic(sliceNode)
+sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+reslice = sliceLayerLogic.GetReslice()
+reslicedImage = vtk.vtkImageData()
+reslicedImage.DeepCopy(reslice.GetOutput())
+
+# Create new volume node using resliced image
+volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
+volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
+volumeNode.SetAndObserveImageData(reslicedImage)
+volumeNode.CreateDefaultDisplayNodes()
+volumeNode.CreateDefaultStorageNode()
+
+# Get voxels as a numpy array
+voxels = slicer.util.arrayFromVolume(volumeNode)
+print(voxels.shape)
+```
+
+### Combine multiple volumes into one
+
+This example combines two volumes into a new one by subtracting one from the other.
+
+```python
+import SampleData
+[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()
+
+import slicer.util
+a = slicer.util.arrayFromVolume(input1Volume)
+b = slicer.util.arrayFromVolume(input2Volume)
+
+# `a` and `b` are numpy arrays,
+# they can be combined using any numpy array operations
+# to produce the result array `c`
+c = b - a
+
+volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")
+slicer.util.updateVolumeFromArray(volumeNode, c)
+setSliceViewerLayers(background=volumeNode)
+```
+
+### Add noise to image
+
+This example shows how to add simulated noise to a volume.
+
+```python
+import SampleData
+import numpy as np
+
+# Get a sample input volume node
+volumeNode = SampleData.SampleDataLogic().downloadMRHead()
+
+# Get volume as numpy array and add noise
+voxels = slicer.util.arrayFromVolume(volumeNode)
+voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)
+slicer.util.arrayFromVolumeModified(volumeNode)
+```
+
+### Mask volume using segmentation
+
+This example shows how to blank out voxels of a volume outside all segments.
+
+```python
+# Input nodes
+volumeNode = getNode("MRHead")
+segmentationNode = getNode("Segmentation")
+
+# Write segmentation to labelmap volume node with a geometry that matches the volume node
+labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
+slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)
+
+# Masking
+import numpy as np
+voxels = slicer.util.arrayFromVolume(volumeNode)
+mask = slicer.util.arrayFromVolume(labelmapVolumeNode)
+maskedVoxels = np.copy(voxels) # we don't want to modify the original volume
+maskedVoxels[mask==0] = 0
+
+# Write masked volume to volume node and show it
+maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")
+slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)
+slicer.util.setSliceViewerLayers(maskedVolumeNode)
+```
+
+### Apply random deformations to image
+
+This example shows how to apply random translation, rotation, and deformations to a volume to simulate variation in patient positioning, soft tissue motion, and random anatomical variations. Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount. Thin-plate spline transform is computed from the original and transformed point list.
+
+https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940
+
+### Thick slab reconstruction and maximum/minimum intensity volume projections
+
+Set up `red` slice viewer to show thick slab reconstructed from 3 slices:
+
+```python
+sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+appLogic = slicer.app.applicationLogic()
+sliceLogic = appLogic.GetSliceLogic(sliceNode)
+sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+reslice = sliceLayerLogic.GetReslice()
+reslice.SetSlabModeToMean()
+reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
+reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
+sliceNode.Modified()
+```
+
+Set up `red` slice viewer to show maximum intensity projection (MIP):
+
+```python
+sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
+appLogic = slicer.app.applicationLogic()
+sliceLogic = appLogic.GetSliceLogic(sliceNode)
+sliceLayerLogic = sliceLogic.GetBackgroundLayer()
+reslice = sliceLayerLogic.GetReslice()
+reslice.SetSlabModeToMax()
+reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume
+reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)
+sliceNode.Modified()
+```
+
+The projected image is available in a `vtkImageData` object by calling `reslice.GetOutput()`.
+
+### Display volume using volume rendering
+
+```python
+ logic = slicer.modules.volumerendering.logic()
+ volumeNode = slicer.mrmlScene.GetNodeByID('vtkMRMLScalarVolumeNode1')
+ displayNode = logic.CreateVolumeRenderingDisplayNode()
+ displayNode.UnRegister(logic)
+ slicer.mrmlScene.AddNode(displayNode)
+ volumeNode.AddAndObserveDisplayNodeID(displayNode.GetID())
+ logic.UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode)
+```
+
+C++:
+
+```cpp
+qSlicerAbstractCoreModule* volumeRenderingModule =
+ qSlicerCoreApplication::application()->moduleManager()->module("VolumeRendering");
+vtkSlicerVolumeRenderingLogic* volumeRenderingLogic =
+ volumeRenderingModule ? vtkSlicerVolumeRenderingLogic::SafeDownCast(volumeRenderingModule->logic()) : 0;
+vtkMRMLVolumeNode* volumeNode = mrmlScene->GetNodeByID('vtkMRMLScalarVolumeNode1');
+if (volumeRenderingLogic)
+ {
+ vtkSmartPointer displayNode =
+ vtkSmartPointer::Take(volumeRenderingLogic->CreateVolumeRenderingDisplayNode());
+ mrmlScene->AddNode(displayNode);
+ volumeNode->AddAndObserveDisplayNodeID(displayNode->GetID());
+ volumeRenderingLogic->UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode);
+ }
+```
+
+### Apply a custom volume rendering color/opacity transfer function
+
+```cpp
+vtkColorTransferFunction* colors = ...
+vtkPiecewiseFunction* opacities = ...
+vtkMRMLVolumeRenderingDisplayNode* displayNode = ...
+vtkMRMLVolumePropertyNode* propertyNode = displayNode->GetVolumePropertyNode();
+propertyNode->SetColor(colorTransferFunction);
+propertyNode->SetScalarOpacity(opacities);
+// optionally set the gradients opacities with SetGradientOpacity
+```
+
+Volume rendering logic has utility functions to help you create those transfer functions: [SetWindowLevelToVolumeProp](http://slicer.org/doc/html/classvtkSlicerVolumeRenderingLogic.html#ab8dbda38ad81b39b445b01e1bf8c7a86), [SetThresholdToVolumeProp](http://slicer.org/doc/html/classvtkSlicerVolumeRenderingLogic.html#a1dcbe614493f3cbb9aa50c68a64764ca), [SetLabelMapToVolumeProp](http://slicer.org/doc/html/classvtkSlicerVolumeRenderingLogic.html#a359314889c2b386fd4c3ffe5414522da).
+
+### Limit volume rendering to a specific region of the volume
+
+```python
+displayNode.SetAndObserveROINodeID(roiNode.GetID())
+displayNode.CroppingEnabled = True
+```
+
+C++:
+
+```cpp
+vtkMRMLAnnotationROINode]* roiNode =...
+vtkMRMLVolumeRenderingDisplayNode* displayNode = ...
+displayNode->SetAndObserveROINodeID(roiNode->GetID());
+displayNode->SetCroppingEnabled(1);
+```
+
+### Register a new Volume Rendering mapper
+
+You need to derive from [vtkMRMLVolumeRenderingDisplayNode](http://apidocs.slicer.org/master/classvtkMRMLVolumeRenderingDisplayNode.html) and register your class within [vtkSlicerVolumeRenderingLogic](http://apidocs.slicer.org/master/classvtkSlicerVolumeRenderingLogic.html).
+
+C++:
+
+```cpp
+void qSlicerMyABCVolumeRenderingModule::setup()
+{
+ vtkMRMLThreeDViewDisplayableManagerFactory::GetInstance()->
+ RegisterDisplayableManager("vtkMRMLMyABCVolumeRenderingDisplayableManager");
+
+ this->Superclass::setup();
+
+ qSlicerAbstractCoreModule* volumeRenderingModule =
+ qSlicerCoreApplication::application()->moduleManager()->module("VolumeRendering");
+ if (volumeRenderingModule)
+ {
+ vtkNew displayNode;
+ vtkSlicerVolumeRenderingLogic* volumeRenderingLogic =
+ vtkSlicerVolumeRenderingLogic::SafeDownCast(volumeRenderingModule->logic());
+ volumeRenderingLogic->RegisterRenderingMethod(
+ "My ABC Volume Rendering", displayNode->GetClassName());
+ }
+ else
+ {
+ qWarning() << "Volume Rendering module is not found";
+ }
+}
+```
+
+If you want to expose control widgets for your volume rendering method, then register your widget with [addRenderingMethodWidget()](http://apidocs.slicer.org/master/classqSlicerVolumeRenderingModuleWidget.html#acd9cdb60f1fd260f3ebf74428bb7c45b).
+
+### Register custom volume rendering presets
+
+Custom presets can be added to the volume rendering module by calling AddPreset() method of the volume rendering module logic. The example below shows how to define multiple custom volume rendering presets in an external MRML scene file and add them to the volume rendering module user interface.
+
+Create a *MyPresets.mrml* file that describes two custom volume rendering presets:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+For this example, thumbnail images for the presets should be located in the same directory as `MyPresets.mrml`, with the file names `MyPreset1.png` and `MyPreset2.png`.
+
+Use the following code to read all the custom presets from *MyPresets.mrml* and load it into the scene:
+
+```python
+presetsScenePath = "MyPresets.mrml"
+
+# Read presets scene
+customPresetsScene = slicer.vtkMRMLScene()
+vrPropNode = slicer.vtkMRMLVolumePropertyNode()
+customPresetsScene.RegisterNodeClass(vrPropNode)
+customPresetsScene.SetURL(presetsScenePath)
+customPresetsScene.Connect()
+
+# Add presets to volume rendering logic
+vrLogic = slicer.modules.volumerendering.logic()
+presetsScene = vrLogic.GetPresetsScene()
+vrNodes = customPresetsScene.GetNodesByClass("vtkMRMLVolumePropertyNode")
+vrNodes.UnRegister(None)
+for itemNum in range(vrNodes.GetNumberOfItems()):
+ node = vrNodes.GetItemAsObject(itemNum)
+ vrLogic.AddPreset(node)
+```
diff --git a/Docs/developer_guide/script_repository/volumes.rst b/Docs/developer_guide/script_repository/volumes.rst
deleted file mode 100644
index 87dd9cfcd04..00000000000
--- a/Docs/developer_guide/script_repository/volumes.rst
+++ /dev/null
@@ -1,954 +0,0 @@
-Volumes
-~~~~~~~
-
-Load volume from file
-^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- loadedVolumeNode = slicer.util.loadVolume("c:/Users/abc/Documents/MRHead.nrrd")
-
-Additional options may be specified in ``properties`` argument. For example, load an image stack by disabling ``singleFile`` option:
-
-.. code-block:: python
-
- loadedVolumeNode = slicer.util.loadVolume("c:/Users/abc/Documents/SomeImage/file001.png", {"singleFile": False})
-
-.. note::
-
- The following options can be passed to load volumes programmatically when using ``qSlicerVolumesReader``:
- - ``name`` (string): Node name to set for the loaded volume
- - ``labelmap`` (bool, default=false): Load the file as labelmap volume
- - ``singleFile`` (bool, default=false): Force loading this file only (otherwise the loader may look for similar files in the same folder to load multiple slices as a 3D volume)
- - ``autoWindowLevel`` (bool, default=true): Automatically compute the window level based on the volume pixel intensities
- - ``show`` (bool, default=true): Show the volume in views after loading
- - ``center`` (bool, default=false): Apply a transform that places the volume in the patient coordinate system origin
- - ``discardOrientation`` (bool, default=false): Discard file orientation information.
- - ``fileNames`` (string list): List of files to be loaded as a volume
- - ``colorNodeID`` (string): ID of the color node used to display the volume. Default is ``vtkMRMLColorTableNodeGrey`` for scalar volume and ``vtkMRMLColorTableNodeFileGenericColors.txt`` for labelmap volume.
-
-Save volume to file
-^^^^^^^^^^^^^^^^^^^
-
-Get the first volume node in the scene and save as .nrrd file. To save in any other supported file format, change the output file name.
-
-.. code-block:: python
-
- volumeNode = slicer.mrmlScene.GetFirstNodeByClass('vtkMRMLScalarVolumeNode')
- slicer.util.saveNode(volumeNode, "c:/tmp/test.nrrd")
-
-Load volume from .vti file
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Slicer does not provide reader for VTK XML image data file format (as they are not commonly used for storing medical images and they cannot store image axis directions) but such files can be read by using this script:
-
-.. code-block:: python
-
- reader=vtk.vtkXMLImageDataReader()
- reader.SetFileName("/path/to/file.vti")
- reader.Update()
- imageData = reader.GetOutput()
- spacing = imageData.GetSpacing()
- origin = imageData.GetOrigin()
- imageData.SetOrigin(0,0,0)
- imageData.SetSpacing(1,1,1)
- volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
- volumeNode.SetAndObserveImageData(imageData)
- volumeNode.SetSpacing(spacing)
- volumeNode.SetOrigin(origin)
- slicer.util.setSliceViewerLayers(volumeNode, fit=True)
-
-Load volume from a remote server
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Download a volume from a remote server by an URL and load it into the scene using the code snippets below.
-
-.. note::
-
- Downloaded data is temporarily stored in the application's cache folder and if the checksum of the already downloaded data
- matches the specified checksum (:) then the file is retrieved from the cache instead of being downloaded
- again. To compute digest with algo *SHA256*, you can run :func:`slicer.util.computeChecksum("SHA256", "path/to/file")`.
-
-Simple download
-'''''''''''''''
-
-.. code-block:: python
-
- import SampleData
- sampleDataLogic = SampleData.SampleDataLogic()
- loadedNodes = sampleDataLogic.downloadFromURL(
- nodeNames="MRHead",
- fileNames="MR-head25.nrrd",
- uris="https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93",
- checksums="SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93")[0]
-
-Download with interruptible progress reporting
-''''''''''''''''''''''''''''''''''''''''''''''
-
-.. code-block:: python
-
- import SampleData
-
- def reportProgress(msg, level=None):
- # Print progress in the console
- print("Loading... {0}%".format(sampleDataLogic.downloadPercent))
- # Abort download if cancel is clicked in progress bar
- if slicer.progressWindow.wasCanceled:
- raise Exception("download aborted")
- # Update progress window
- slicer.progressWindow.show()
- slicer.progressWindow.activateWindow()
- slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))
- slicer.progressWindow.setLabelText("Downloading...")
- # Process events to allow screen to refresh
- slicer.app.processEvents()
-
- try:
- volumeNode = None
- slicer.progressWindow = slicer.util.createProgressDialog()
- sampleDataLogic = SampleData.SampleDataLogic()
- sampleDataLogic.logMessage = reportProgress
- loadedNodes = sampleDataLogic.downloadFromURL(
- nodeNames="MRHead",
- fileNames="MR-head25.nrrd",
- uris="https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93",
- checksums="SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93")
- volumeNode = loadedNodes[0]
- finally:
- slicer.progressWindow.close()
-
-Show volume rendering automatically when a volume is loaded
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-To show volume rendering of a volume automatically when it is loaded, add the lines below to your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__.
-
-.. code-block:: python
-
- @vtk.calldata_type(vtk.VTK_OBJECT)
- def onNodeAdded(caller, event, calldata):
- node = calldata
- if isinstance(node, slicer.vtkMRMLVolumeNode):
- # Call showVolumeRendering using a timer instead of calling it directly
- # to allow the volume loading to fully complete.
- qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))
-
- def showVolumeRendering(volumeNode):
- print("Show volume rendering of node " + volumeNode.GetName())
- volRenLogic = slicer.modules.volumerendering.logic()
- displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
- displayNode.SetVisibility(True)
- scalarRange = volumeNode.GetImageData().GetScalarRange()
- if scalarRange[1]-scalarRange[0] < 1500:
- # Small dynamic range, probably MRI
- displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("MR-Default"))
- else:
- # Larger dynamic range, probably CT
- displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("CT-Chest-Contrast-Enhanced"))
-
- slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)
-
-Show volume rendering using maximum intensity projection
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- def showVolumeRenderingMIP(volumeNode, useSliceViewColors=True):
- """Render volume using maximum intensity projection
- :param useSliceViewColors: use the same colors as in slice views.
- """
- # Get/create volume rendering display node
- volRenLogic = slicer.modules.volumerendering.logic()
- displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode)
- if not displayNode:
- displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
- # Choose MIP volume rendering preset
- if useSliceViewColors:
- volRenLogic.CopyDisplayToVolumeRenderingDisplayNode(displayNode)
- else:
- scalarRange = volumeNode.GetImageData().GetScalarRange()
- if scalarRange[1]-scalarRange[0] < 1500:
- # Small dynamic range, probably MRI
- displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("MR-MIP"))
- else:
- # Larger dynamic range, probably CT
- displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName("CT-MIP"))
- # Switch views to MIP mode
- for viewNode in slicer.util.getNodesByClass("vtkMRMLViewNode"):
- viewNode.SetRaycastTechnique(slicer.vtkMRMLViewNode.MaximumIntensityProjection)
- # Show volume rendering
- displayNode.SetVisibility(True)
-
- volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
- showVolumeRenderingMIP(volumeNode)
-
-
-Show volume rendering making soft tissues transparent
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- def showTransparentRendering(volumeNode, maxOpacity=0.2, gradientThreshold=30.0):
- """Make constant regions transparent and the entire volume somewhat transparent
- :param maxOpacity: lower value makes the volume more transparent overall
- (value is between 0.0 and 1.0)
- :param gradientThreshold: regions that has gradient value below this threshold will be made transparent
- (minimum value is 0.0, higher values make more tissues transparent, starting with soft tissues)
- """
- # Get/create volume rendering display node
- volRenLogic = slicer.modules.volumerendering.logic()
- displayNode = volRenLogic.GetFirstVolumeRenderingDisplayNode(volumeNode)
- if not displayNode:
- displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
- # Set up gradient vs opacity transfer function
- gradientOpacityTransferFunction = displayNode.GetVolumePropertyNode().GetVolumeProperty().GetGradientOpacity()
- gradientOpacityTransferFunction.RemoveAllPoints()
- gradientOpacityTransferFunction.AddPoint(0, 0.0)
- gradientOpacityTransferFunction.AddPoint(gradientThreshold-1, 0.0)
- gradientOpacityTransferFunction.AddPoint(gradientThreshold+1, maxOpacity)
- # Show volume rendering
- displayNode.SetVisibility(True)
-
- volumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")
- showTransparentRendering(volumeNode, 0.2, 30.0)
-
-Automatically load volumes that are copied into a folder
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to implement a simple background task by using a timer. The background task is to check for any new volume files in folder and if there is any then automatically load it.
-
-There are more efficient methods for file system monitoring or exchanging image data in real-time (for example, using OpenIGTLink), the example below is just for demonstration purposes.
-
-.. code-block:: python
-
- incomingVolumeFolder = "c:/tmp/incoming"
- incomingVolumesProcessed = []
-
- def checkForNewVolumes():
- # Check if there is a new file in the
- from os import listdir
- from os.path import isfile, join
- for f in listdir(incomingVolumeFolder):
- if f in incomingVolumesProcessed:
- # This is an incoming file, it was already there
- continue
- filePath = join(incomingVolumeFolder, f)
- if not isfile(filePath):
- # ignore directories
- continue
- logging.info("Loading new file: " + f)
- incomingVolumesProcessed.append(f)
- slicer.util.loadVolume(filePath)
- # Check again in 3000ms
- qt.QTimer.singleShot(3000, checkForNewVolumes)
-
- # Start monitoring
- checkForNewVolumes()
-
-Extract randomly oriented slabs of given shape from a volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Returns a numpy array of sliceCount random tiles.
-
-.. code-block:: python
-
- def randomSlices(volume, sliceCount, sliceShape):
- layoutManager = slicer.app.layoutManager()
- redWidget = layoutManager.sliceWidget("Red")
- sliceNode = redWidget.mrmlSliceNode()
- sliceNode.SetDimensions(*sliceShape, 1)
- sliceNode.SetFieldOfView(*sliceShape, 1)
- bounds = [0]*6
- volume.GetRASBounds(bounds)
- imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()
-
- sliceSize = sliceShape[0] * sliceShape[1]
- X = numpy.zeros([sliceCount, sliceSize])
-
- for sliceIndex in range(sliceCount):
- position = numpy.random.rand(3) * 2 - 1
- position = [bounds[0] + bounds[1]-bounds[0] * position[0],
- bounds[2] + bounds[3]-bounds[2] * position[1],
- bounds[4] + bounds[5]-bounds[4] * position[2]]
- normal = numpy.random.rand(3) * 2 - 1
- normal = normal / numpy.linalg.norm(normal)
- transverse = numpy.cross(normal, [0,0,1])
- orientation = 0
- sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2],
- transverse[0], transverse[1], transverse[2],
- position[0], position[1], position[2],
- orientation)
- if sliceIndex % 100 == 0:
- slicer.app.processEvents()
- imageReslice.Update()
- imageData = imageReslice.GetOutputDataObject(0)
- array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())
- X[sliceIndex] = array
- return X
-
-Clone a volume
-^^^^^^^^^^^^^^
-
-This example shows how to clone the MRHead sample volume, including its pixel data and display settings.
-
-.. code-block:: python
-
- sourceVolumeNode = slicer.util.getNode("MRHead")
- volumesLogic = slicer.modules.volumes.logic()
- clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, "Cloned volume")
-
-Create a new volume
-^^^^^^^^^^^^^^^^^^^
-
-This example shows how to create a new empty volume. The "Image Maker" extension contains a module that allows creating a volume from scratch without programming.
-
-.. code-block:: python
-
- nodeName = "MyNewVolume"
- imageSize = [512, 512, 512]
- voxelType=vtk.VTK_UNSIGNED_CHAR
- imageOrigin = [0.0, 0.0, 0.0]
- imageSpacing = [1.0, 1.0, 1.0]
- imageDirections = [[1,0,0], [0,1,0], [0,0,1]]
- fillVoxelValue = 0
-
- # Create an empty image volume, filled with fillVoxelValue
- imageData = vtk.vtkImageData()
- imageData.SetDimensions(imageSize)
- imageData.AllocateScalars(voxelType, 1)
- imageData.GetPointData().GetScalars().Fill(fillVoxelValue)
- # Create volume node
- volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)
- volumeNode.SetOrigin(imageOrigin)
- volumeNode.SetSpacing(imageSpacing)
- volumeNode.SetIJKToRASDirections(imageDirections)
- volumeNode.SetAndObserveImageData(imageData)
- volumeNode.CreateDefaultDisplayNodes()
- volumeNode.CreateDefaultStorageNode()
-
-C++:
-
-.. code-block:: cpp
-
- vtkNew imageData;
- imageData->SetDimensions(10,10,10); // image size
- imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1); // image type and number of components
- // initialize the pixels here
-
- vtkNew volumeNode;
- volumeNode->SetAndObserveImageData(imageData);
- volumeNode->SetOrigin( -10., -10., -10.);
- volumeNode->SetSpacing( 2., 2., 2. );
- mrmlScene->AddNode( volumeNode.GetPointer() );
-
- volumeNode->CreateDefaultDisplayNodes()
-
-.. note::
-
- Origin and spacing must be set on the volume node instead of the image data.
-
-Get value of a volume at specific voxel coordinates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.
-
-.. code-block:: python
-
- volumeNode = slicer.util.getNode("MRHead")
- ijk = [20,40,30] # volume voxel coordinates
-
- voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array
- voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)
-
-Modify voxels in a volume
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Typically the fastest and simplest way of modifying voxels is by using numpy operators. Voxels can be retrieved in a numpy array using the ``array`` method and modified using standard numpy methods. For example, threshold a volume:
-
-.. code-block:: python
-
- nodeName = "MRHead"
- thresholdValue = 100
- voxelArray = array(nodeName) # get voxels as numpy array
- voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values
- getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed
-
-This example shows how to change voxels values of the MRHead sample volume. The values will be computed by function ``f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s``.
-
-.. code-block:: python
-
- volumeNode=slicer.util.getNode("MRHead")
- ijkToRas = vtk.vtkMatrix4x4()
- volumeNode.GetIJKToRASMatrix(ijkToRas)
- imageData=volumeNode.GetImageData()
- extent = imageData.GetExtent()
- for k in range(extent[4], extent[5]+1):
- for j in range(extent[2], extent[3]+1):
- for i in range(extent[0], extent[1]+1):
- position_Ijk=[i, j, k, 1]
- position_Ras=ijkToRas.MultiplyPoint(position_Ijk)
- r=position_Ras[0]
- a=position_Ras[1]
- s=position_Ras[2]
- functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s
- imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)
- imageData.Modified()
-
-Get volume voxel coordinates from markup fiducial RAS coordinates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.
-
-.. code-block:: python
-
- # Inputs
- volumeNode = getNode("MRHead")
- markupsNode = getNode("F")
- markupsIndex = 0
-
- # Get point coordinate in RAS
- point_Ras = [0, 0, 0, 1]
- markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)
-
- # If volume node is transformed, apply that transform to get volume's RAS coordinates
- transformRasToVolumeRas = vtk.vtkGeneralTransform()
- slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)
- point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])
-
- # Get voxel coordinates from physical coordinates
- volumeRasToIjk = vtk.vtkMatrix4x4()
- volumeNode.GetRASToIJKMatrix(volumeRasToIjk)
- point_Ijk = [0, 0, 0, 1]
- volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)
- point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]
-
- # Print output
- print(point_Ijk)
-
-Get markup fiducial RAS coordinates from volume voxel coordinates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to get position of maximum intensity voxel of a volume (determined by numpy, in IJK coordinates) in RAS coordinates so that it can be marked with a markup fiducial.
-
-.. code-block:: python
-
- # Inputs
- volumeNode = getNode("MRHead")
- markupsNode = getNode("F")
-
- # Get voxel position in IJK coordinate system
- import numpy as np
- volumeArray = slicer.util.arrayFromVolume(volumeNode)
- # Get position of highest voxel value
- point_Kji = np.where(volumeArray == volumeArray.max())
- point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]
-
- # Get physical coordinates from voxel coordinates
- volumeIjkToRas = vtk.vtkMatrix4x4()
- volumeNode.GetIJKToRASMatrix(volumeIjkToRas)
- point_VolumeRas = [0, 0, 0, 1]
- volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)
-
- # If volume node is transformed, apply that transform to get volume's RAS coordinates
- transformVolumeRasToRas = vtk.vtkGeneralTransform()
- slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)
- point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])
-
- # Add a markup at the computed position and print its coordinates
- markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")
- print(point_Ras)
-
-Get the values of all voxels for a label value
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:
-
-.. code-block:: python
-
- import numpy
- volume = array("Volume")
- label = array("Volume-label")
- points = numpy.where( label == 1 ) # or use another label number depending on what you segmented
- values = volume[points] # this will be a list of the label values
- values.mean() # should match the mean value of LabelStatistics calculation as a double-check
- numpy.savetxt("values.txt", values)
-
-Access values in a DTI tensor volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to access individual tensors at the voxel level.
-
-First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’
-
-Then open the python window: View->Python interactor
-
-Use this command to access tensors through numpy:
-
-.. code-block:: python
-
- tensors = array("Output DTI Volume")
-
-Type the following code into the Python window to access all tensor components using vtk commands:
-
-.. code-block:: python
-
- volumeNode=slicer.util.getNode("Output DTI Volume")
- imageData=volumeNode.GetImageData()
- tensors = imageData.GetPointData().GetTensors()
- extent = imageData.GetExtent()
- idx = 0
- for k in range(extent[4], extent[5]+1):
- for j in range(extent[2], extent[3]+1):
- for i in range(extent[0], extent[1]+1):
- tensors.GetTuple9(idx)
- idx += 1
-
-Change window/level (brightness/contrast) or colormap of a volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to change window/level of the MRHead sample volume.
-
-.. code-block:: python
-
- volumeNode = getNode("MRHead")
- displayNode = volumeNode.GetDisplayNode()
- displayNode.AutoWindowLevelOff()
- displayNode.SetWindow(50)
- displayNode.SetLevel(100)
-
-Change color mapping from grayscale to rainbow:
-
-.. code-block:: python
-
- displayNode.SetAndObserveColorNodeID("vtkMRMLColorTableNodeRainbow")
-
-Make mouse left-click and drag on the image adjust window/level
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In older Slicer versions, by default, left-click and drag in a slice view adjusted window/level of the displayed image. Window/level adjustment is now a new mouse mode that can be activated by clicking on its toolbar button or running this code:
-
-.. code-block:: python
-
- slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)
-
-Reset field of view to show background volume maximized
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.
-
-.. code-block:: python
-
- slicer.util.resetSliceViews()
-
-Rotate slice views to volume plane
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Aligns slice views to volume axes, shows original image acquisition planes in slice views.
-
-.. code-block:: python
-
- volumeNode = slicer.util.getNode("MRHead")
- layoutManager = slicer.app.layoutManager()
- for sliceViewName in layoutManager.sliceViewNames():
- layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)
-
-Iterate over current visible slice views, and set foreground and background images
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)
-
-Internally, this method performs something like this:
-
-.. code-block:: python
-
- layoutManager = slicer.app.layoutManager()
- for sliceViewName in layoutManager.sliceViewNames():
- compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()
- # Setup background volume
- compositeNode.SetBackgroundVolumeID(mrVolume.GetID())
- # Setup foreground volume
- compositeNode.SetForegroundVolumeID(ctVolume.GetID())
- # Change opacity
- compositeNode.SetForegroundOpacity(0.3)
-
-Show a volume in slice views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Recommended:
-
-.. code-block:: python
-
- volumeNode = slicer.util.getNode("YourVolumeNode")
- slicer.util.setSliceViewerLayers(background=volumeNode)
-
-or
-
-Show volume in all visible views where volume selection propagation is enabled:
-
-.. code-block:: python
-
- volumeNode = slicer.util.getNode("YourVolumeNode")
- applicationLogic = slicer.app.applicationLogic()
- selectionNode = applicationLogic.GetSelectionNode()
- selectionNode.SetSecondaryVolumeID(volumeNode.GetID())
- applicationLogic.PropagateForegroundVolumeSelection(0)
-
-or
-
-Show volume in selected views:
-
-.. code-block:: python
-
- n = slicer.util.getNode("YourVolumeNode")
- for color in ["Red", "Yellow", "Green"]:
- slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())
-
-Change opacity of foreground volume in slice views
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: python
-
- slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)
-
-or
-
-Change opacity in a selected view
-
-.. code-block:: python
-
- lm = slicer.app.layoutManager()
- sliceLogic = lm.sliceWidget("Red").sliceLogic()
- compositeNode = sliceLogic.GetSliceCompositeNode()
- compositeNode.SetForegroundOpacity(0.4)
-
-Turning off interpolation
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.
-
-.. code-block:: python
-
- def NoInterpolate(caller,event):
- for node in slicer.util.getNodes("*").values():
- if node.IsA("vtkMRMLScalarVolumeDisplayNode"):
- node.SetInterpolate(0)
-
- slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)
-
-You can place this code snippet in your `.slicerrc.py file <../user_guide/settings.html#application-startup-file>`__ to always disable interpolation by default.
-
-Running an ITK filter in Python using SimpleITK
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:
-
-.. code-block:: python
-
- import SampleData
- import SimpleITK as sitk
- import sitkUtils
-
- # Get input volume node
- inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()
- # Create new volume node for output
- outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", "MRHeadFiltered")
-
- # Run processing
- inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)
- filter = sitk.SignedMaurerDistanceMapImageFilter()
- outputImage = filter.Execute(inputImage)
- sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)
-
- # Show processing result
- slicer.util.setSliceViewerLayers(background=outputVolumeNode)
-
-More information:
-
-- See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html
-- sitkUtils in Slicer is used for pushing and pulling images from Slicer to SimpleITK: https://github.com/Slicer/Slicer/blob/master/Base/Python/sitkUtils.py
-
-Get axial slice as numpy array
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:
-
-.. code-block:: python
-
- import SampleData
- volumeNode = SampleData.SampleDataLogic().downloadMRHead()
- sliceIndex = 12
-
- voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array
- slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array
-
-Get reformatted image from a slice viewer as numpy array
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Set up ``red`` slice viewer to show thick slab reconstructed from 3 slices:
-
-.. code-block:: python
-
- sliceNodeID = "vtkMRMLSliceNodeRed"
-
- # Get image data from slice view
- sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)
- appLogic = slicer.app.applicationLogic()
- sliceLogic = appLogic.GetSliceLogic(sliceNode)
- sliceLayerLogic = sliceLogic.GetBackgroundLayer()
- reslice = sliceLayerLogic.GetReslice()
- reslicedImage = vtk.vtkImageData()
- reslicedImage.DeepCopy(reslice.GetOutput())
-
- # Create new volume node using resliced image
- volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")
- volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())
- volumeNode.SetAndObserveImageData(reslicedImage)
- volumeNode.CreateDefaultDisplayNodes()
- volumeNode.CreateDefaultStorageNode()
-
- # Get voxels as a numpy array
- voxels = slicer.util.arrayFromVolume(volumeNode)
- print(voxels.shape)
-
-Combine multiple volumes into one
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example combines two volumes into a new one by subtracting one from the other.
-
-.. code-block:: python
-
- import SampleData
- [input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()
-
- import slicer.util
- a = slicer.util.arrayFromVolume(input1Volume)
- b = slicer.util.arrayFromVolume(input2Volume)
-
- # ``a`` and ``b`` are numpy arrays,
- # they can be combined using any numpy array operations
- # to produce the result array ``c``
- c = b - a
-
- volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")
- slicer.util.updateVolumeFromArray(volumeNode, c)
- setSliceViewerLayers(background=volumeNode)
-
-Add noise to image
-^^^^^^^^^^^^^^^^^^
-
-This example shows how to add simulated noise to a volume.
-
-.. code-block:: python
-
- import SampleData
- import numpy as np
-
- # Get a sample input volume node
- volumeNode = SampleData.SampleDataLogic().downloadMRHead()
-
- # Get volume as numpy array and add noise
- voxels = slicer.util.arrayFromVolume(volumeNode)
- voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)
- slicer.util.arrayFromVolumeModified(volumeNode)
-
-Mask volume using segmentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to blank out voxels of a volume outside all segments.
-
-.. code-block:: python
-
- # Input nodes
- volumeNode = getNode("MRHead")
- segmentationNode = getNode("Segmentation")
-
- # Write segmentation to labelmap volume node with a geometry that matches the volume node
- labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
- slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)
-
- # Masking
- import numpy as np
- voxels = slicer.util.arrayFromVolume(volumeNode)
- mask = slicer.util.arrayFromVolume(labelmapVolumeNode)
- maskedVoxels = np.copy(voxels) # we don't want to modify the original volume
- maskedVoxels[mask==0] = 0
-
- # Write masked volume to volume node and show it
- maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")
- slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)
- slicer.util.setSliceViewerLayers(maskedVolumeNode)
-
-Apply random deformations to image
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This example shows how to apply random translation, rotation, and deformations to a volume to simulate variation in patient positioning, soft tissue motion, and random anatomical variations. Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount. Thin-plate spline transform is computed from the original and transformed point list.
-
-https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940
-
-Thick slab reconstruction and maximum/minimum intensity volume projections
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Set up ``red`` slice viewer to show thick slab reconstructed from 3 slices:
-
-.. code-block:: python
-
- sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
- appLogic = slicer.app.applicationLogic()
- sliceLogic = appLogic.GetSliceLogic(sliceNode)
- sliceLayerLogic = sliceLogic.GetBackgroundLayer()
- reslice = sliceLayerLogic.GetReslice()
- reslice.SetSlabModeToMean()
- reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed
- reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)
- sliceNode.Modified()
-
-Set up ``red`` slice viewer to show maximum intensity projection (MIP):
-
-.. code-block:: python
-
- sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")
- appLogic = slicer.app.applicationLogic()
- sliceLogic = appLogic.GetSliceLogic(sliceNode)
- sliceLayerLogic = sliceLogic.GetBackgroundLayer()
- reslice = sliceLayerLogic.GetReslice()
- reslice.SetSlabModeToMax()
- reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume
- reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)
- sliceNode.Modified()
-
-The projected image is available in a *vtkImageData* object by calling *reslice.GetOutput()*.
-
-Display volume using volume rendering
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: cpp
-
- qSlicerAbstractCoreModule* volumeRenderingModule =
- qSlicerCoreApplication::application()->moduleManager()->module("VolumeRendering");
- vtkSlicerVolumeRenderingLogic* volumeRenderingLogic =
- volumeRenderingModule ? vtkSlicerVolumeRenderingLogic::SafeDownCast(volumeRenderingModule->logic()) : 0;
- vtkMRMLVolumeNode* volumeNode = mrmlScene->GetNodeByID('vtkMRMLScalarVolumeNode1');
- if (volumeRenderingLogic)
- {
- vtkSmartPointer displayNode =
- vtkSmartPointer::Take(volumeRenderingLogic->CreateVolumeRenderingDisplayNode());
- mrmlScene->AddNode(displayNode);
- volumeNode->AddAndObserveDisplayNodeID(displayNode->GetID());
- volumeRenderingLogic->UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode);
- }
-
-.. code-block:: python
-
- logic = slicer.modules.volumerendering.logic()
- volumeNode = slicer.mrmlScene.GetNodeByID('vtkMRMLScalarVolumeNode1')
- displayNode = logic.CreateVolumeRenderingDisplayNode()
- displayNode.UnRegister(logic)
- slicer.mrmlScene.AddNode(displayNode)
- volumeNode.AddAndObserveDisplayNodeID(displayNode.GetID())
- logic.UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode)
-
-Apply a custom volume rendering color/opacity transfer function
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: cpp
-
- vtkColorTransferFunction* colors = ...
- vtkPiecewiseFunction* opacities = ...
- vtkMRMLVolumeRenderingDisplayNode* displayNode = ...
- vtkMRMLVolumePropertyNode* propertyNode = displayNode->GetVolumePropertyNode();
- propertyNode->SetColor(colorTransferFunction);
- propertyNode->SetScalarOpacity(opacities);
- // optionally set the gradients opacities with SetGradientOpacity
-
-Volume rendering logic has utility functions to help you create those transfer functions: `SetWindowLevelToVolumeProp `_, `SetThresholdToVolumeProp `_, `SetLabelMapToVolumeProp `_.
-
-Limit volume rendering to a specific region of the volume
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: cpp
-
- vtkMRMLAnnotationROINode]* roiNode =...
- vtkMRMLVolumeRenderingDisplayNode* displayNode = ...
- displayNode->SetAndObserveROINodeID(roiNode->GetID());
- displayNode->SetCroppingEnabled(1);
-
-.. code-block:: python
-
- displayNode.SetAndObserveROINodeID(roiNode.GetID())
- displayNode.CroppingEnabled = True
-
-Register a new Volume Rendering mapper
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-You need to derive from `vtkMRMLVolumeRenderingDisplayNode `__ and register your class within `vtkSlicerVolumeRenderingLogic `__.
-
-.. code-block:: cpp
-
- void qSlicerMyABCVolumeRenderingModule::setup()
- {
- vtkMRMLThreeDViewDisplayableManagerFactory::GetInstance()->
- RegisterDisplayableManager("vtkMRMLMyABCVolumeRenderingDisplayableManager");
-
- this->Superclass::setup();
-
- qSlicerAbstractCoreModule* volumeRenderingModule =
- qSlicerCoreApplication::application()->moduleManager()->module("VolumeRendering");
- if (volumeRenderingModule)
- {
- vtkNew displayNode;
- vtkSlicerVolumeRenderingLogic* volumeRenderingLogic =
- vtkSlicerVolumeRenderingLogic::SafeDownCast(volumeRenderingModule->logic());
- volumeRenderingLogic->RegisterRenderingMethod(
- "My ABC Volume Rendering", displayNode->GetClassName());
- }
- else
- {
- qWarning() << "Volume Rendering module is not found";
- }
- }
-
-If you want to expose control widgets for your volume rendering method, then register your widget with `addRenderingMethodWidget() `__.
-
-Register custom volume rendering presets
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Custom presets can be added to the volume rendering module by calling AddPreset() method of the volume rendering module logic. The example below shows how to define multiple custom volume rendering presets in an external MRML scene file and add them to the volume rendering module user interface.
-
-Create a *MyPresets.mrml* file that describes two custom volume rendering presets:
-
-.. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-For this example, thumbnail images for the presets should be located in the same directory as *MyPresets.mrml*, with the file names *MyPreset1.png* and *MyPreset2.png*.
-
-Use the following code to read all the custom presets from *MyPresets.mrml* and load it into the scene:
-
-.. code-block:: python
-
- presetsScenePath = "MyPresets.mrml"
-
- # Read presets scene
- customPresetsScene = slicer.vtkMRMLScene()
- vrPropNode = slicer.vtkMRMLVolumePropertyNode()
- customPresetsScene.RegisterNodeClass(vrPropNode)
- customPresetsScene.SetURL(presetsScenePath)
- customPresetsScene.Connect()
-
- # Add presets to volume rendering logic
- vrLogic = slicer.modules.volumerendering.logic()
- presetsScene = vrLogic.GetPresetsScene()
- vrNodes = customPresetsScene.GetNodesByClass("vtkMRMLVolumePropertyNode")
- vrNodes.UnRegister(None)
- for itemNum in range(vrNodes.GetNumberOfItems()):
- node = vrNodes.GetItemAsObject(itemNum)
- vrLogic.AddPreset(node)
diff --git a/Docs/developer_guide/slicer.md b/Docs/developer_guide/slicer.md
new file mode 100644
index 00000000000..e835197ec1e
--- /dev/null
+++ b/Docs/developer_guide/slicer.md
@@ -0,0 +1,69 @@
+# slicer package
+
+## Submodules
+
+### slicer.ScriptedLoadableModule module
+
+```{eval-rst}
+.. automodule:: slicer.ScriptedLoadableModule
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
+
+### slicer.cli module
+
+```{eval-rst}
+.. automodule:: slicer.cli
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
+
+### slicer.logic module
+
+```{eval-rst}
+.. automodule:: slicer.logic
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
+
+% Commented out for now, because it breaks documentation building on Windows
+% (Sphinx returns with an error code and nothing is generated)
+%
+% ### slicer.slicerqt module
+%
+% ```{eval-rst}
+% .. automodule:: slicer.slicerqt
+% :members:
+% :undoc-members:
+% :show-inheritance:
+% ```
+
+### slicer.testing module
+
+```{eval-rst}
+.. automodule:: slicer.testing
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
+
+### slicer.util module
+
+```{eval-rst}
+.. automodule:: slicer.util
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
+
+## Module contents
+
+```{eval-rst}
+.. automodule:: slicer
+ :members:
+ :undoc-members:
+ :show-inheritance:
+```
diff --git a/Docs/developer_guide/slicer.rst b/Docs/developer_guide/slicer.rst
deleted file mode 100644
index 9161ae1572b..00000000000
--- a/Docs/developer_guide/slicer.rst
+++ /dev/null
@@ -1,65 +0,0 @@
-slicer package
-==============
-
-Submodules
-----------
-
-slicer.ScriptedLoadableModule module
-------------------------------------
-
-.. automodule:: slicer.ScriptedLoadableModule
- :members:
- :undoc-members:
- :show-inheritance:
-
-slicer.cli module
------------------
-
-.. automodule:: slicer.cli
- :members:
- :undoc-members:
- :show-inheritance:
-
-slicer.logic module
--------------------
-
-.. automodule:: slicer.logic
- :members:
- :undoc-members:
- :show-inheritance:
-
-.. Commented out for now, because it breaks documentation building on Windows
-.. (Sphinx returns with an error code and nothing is generated)
-
-.. slicer.slicerqt module
-.. ----------------------
-
-.. .. automodule:: slicer.slicerqt
-.. :members:
-.. :undoc-members:
-.. :show-inheritance:
-
-slicer.testing module
----------------------
-
-.. automodule:: slicer.testing
- :members:
- :undoc-members:
- :show-inheritance:
-
-slicer.util module
-------------------
-
-.. automodule:: slicer.util
- :members:
- :undoc-members:
- :show-inheritance:
-
-
-Module contents
----------------
-
-.. automodule:: slicer
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/Docs/developer_guide/teem.rst b/Docs/developer_guide/teem.rst
deleted file mode 100644
index cd895f03034..00000000000
--- a/Docs/developer_guide/teem.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-teem module
-===========
-
-.. automodule:: teem
- :members:
- :undoc-members:
- :show-inheritance:
- :imported-members:
diff --git a/Docs/developer_guide/vtk.md.dis b/Docs/developer_guide/vtk.md.dis
new file mode 100644
index 00000000000..94fa0e0e71e
--- /dev/null
+++ b/Docs/developer_guide/vtk.md.dis
@@ -0,0 +1,8 @@
+# vtk module
+
+```{automodule} vtk
+:members:
+:undoc-members:
+:show-inheritance:
+:imported-members:
+```
diff --git a/Docs/developer_guide/vtk.rst.dis b/Docs/developer_guide/vtk.rst.dis
deleted file mode 100644
index 7010aea7bcd..00000000000
--- a/Docs/developer_guide/vtk.rst.dis
+++ /dev/null
@@ -1,8 +0,0 @@
-vtk module
-==========
-
-.. automodule:: vtk
- :members:
- :undoc-members:
- :show-inheritance:
- :imported-members:
diff --git a/Docs/developer_guide/vtkAddon.md b/Docs/developer_guide/vtkAddon.md
new file mode 100644
index 00000000000..e8242916801
--- /dev/null
+++ b/Docs/developer_guide/vtkAddon.md
@@ -0,0 +1,9 @@
+# vtkAddon module
+
+```{eval-rst}
+.. automodule:: vtkAddonPython
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :imported-members:
+```
diff --git a/Docs/developer_guide/vtkAddon.rst b/Docs/developer_guide/vtkAddon.rst
deleted file mode 100644
index 4fe03aee4a2..00000000000
--- a/Docs/developer_guide/vtkAddon.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-vtkAddon module
-===============
-
-.. automodule:: vtkAddon
- :members:
- :undoc-members:
- :show-inheritance:
- :imported-members:
diff --git a/Docs/developer_guide/vtkITK.rst b/Docs/developer_guide/vtkITK.md
similarity index 75%
rename from Docs/developer_guide/vtkITK.rst
rename to Docs/developer_guide/vtkITK.md
index 67f1a709c7d..962c85c6502 100644
--- a/Docs/developer_guide/vtkITK.rst
+++ b/Docs/developer_guide/vtkITK.md
@@ -1,8 +1,9 @@
-vtkITK module
-=============
+# vtkITK module
+```{eval-rst}
.. automodule:: vtkITK
:members:
:undoc-members:
:show-inheritance:
:imported-members:
+```
diff --git a/Docs/developer_guide/vtkTeem.md b/Docs/developer_guide/vtkTeem.md
new file mode 100644
index 00000000000..bf96a44c666
--- /dev/null
+++ b/Docs/developer_guide/vtkTeem.md
@@ -0,0 +1,9 @@
+# vtkTeem module
+
+```{eval-rst}
+.. automodule:: vtkTeemPython
+ :members:
+ :undoc-members:
+ :show-inheritance:
+ :imported-members:
+```
diff --git a/Docs/index.md b/Docs/index.md
new file mode 100644
index 00000000000..8ba3a3ee2d7
--- /dev/null
+++ b/Docs/index.md
@@ -0,0 +1,30 @@
+# Welcome to 3D Slicer's documentation!
+
+This is documentation is a work in progress, in preparation for the new Slicer-5.0 release.
+
+For Slicer-4.10 documentation, refer to the [3D Slicer wiki](https://www.slicer.org/wiki/Documentation/4.10).
+
+```{toctree}
+:maxdepth: 2
+
+user_guide/about
+user_guide/getting_started
+user_guide/get_help
+
+user_guide/user_interface
+user_guide/data_loading_and_saving
+user_guide/image_segmentation
+user_guide/registration
+user_guide/modules/index
+user_guide/extensions_manager
+user_guide/settings
+
+developer_guide/index
+```
+
+Indices and tables
+==================
+
+* {ref}`genindex`
+* {ref}`modindex`
+* {ref}`search`
diff --git a/Docs/index.rst b/Docs/index.rst
deleted file mode 100644
index 5074f635afd..00000000000
--- a/Docs/index.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-.. 3D Slicer documentation master file, created by
- sphinx-quickstart on Tue Mar 21 03:07:30 2017.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
-
-Welcome to 3D Slicer's documentation!
-=====================================
-
-This is documentation is a work in progress, in preparation for the new Slicer-5.0 release.
-
-For Slicer-4.10 documentation, refer to the `3D Slicer wiki `_.
-
-.. toctree::
- :maxdepth: 2
-
- user_guide/about
- user_guide/getting_started
- user_guide/get_help
-
- user_guide/user_interface
- user_guide/data_loading_and_saving
- user_guide/image_segmentation
- user_guide/registration
- user_guide/modules/index.rst
- user_guide/extensions_manager
- user_guide/settings
-
- developer_guide/index
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/Docs/user_guide/data_loading_and_saving.md b/Docs/user_guide/data_loading_and_saving.md
index fa97ed07443..3e8c5dab4cf 100644
--- a/Docs/user_guide/data_loading_and_saving.md
+++ b/Docs/user_guide/data_loading_and_saving.md
@@ -124,7 +124,7 @@ Surface or volumetric meshes.
### Scenes
-- **MRML (Medical Reality Markup Language File)** (.mrml): MRML file is a xml-formatted text file with scene metadata and pointers to externally stored data files. See [MRML overview](mrml_overview). Coordinate system: RAS.
+- **MRML (Medical Reality Markup Language File)** (.mrml): MRML file is a xml-formatted text file with scene metadata and pointers to externally stored data files. See [MRML overview](../developer_guide/mrml_overview.md). Coordinate system: RAS.
- **MRB (Medical Reality Bundle)** (.mrb, .zip): MRB is a binary format encapsulating all scene data (bulk data and metadata). Internally it uses zip format. Any .zip file that contains a self-contained data tree including a .mrml file can be opened. Coordinate system: RAS. Note: only .mrb file extension can be chosen for writing, but after that the file can be manually renamed to .zip if you need access to internal data.
- **Data collections in XNAT Catalog format** (.xcat; reading only)
- **Data collections in XNAT Archive format** (.xar; reading only)
@@ -134,8 +134,8 @@ Surface or volumetric meshes.
- **Text** (.txt, .xml., json)
- **Table** (.csv, .tsv)
- [**Color table**](https://www.slicer.org/wiki/Documentation/Nightly/Modules/Colors#File_format) (.ctbl, .txt)
-- [**Volume rendering properties**](../../developer_guide/modules/volumerendering) (.vp)
-- [**Volume rendering shader properties**](../../developer_guide/modules/volumerendering) (.sp)
+- [**Volume rendering properties**](../developer_guide/modules/volumerendering.md) (.vp)
+- [**Volume rendering shader properties**](../developer_guide/modules/volumerendering.md) (.sp)
- **Terminology** (.term.json, .json): dictionary of standard DICOM or other terms
- **Node sequence** (.seq.mrb): sequence of any MRML node (for storage of 4D data)
diff --git a/Docs/user_guide/extensions_manager.md b/Docs/user_guide/extensions_manager.md
index cb40fab09aa..d7278e1d5a4 100644
--- a/Docs/user_guide/extensions_manager.md
+++ b/Docs/user_guide/extensions_manager.md
@@ -12,7 +12,7 @@ The Slicer community maintains a website referred to as the [Slicer Extensions C
### Install extensions
-- Open Extensions manager using menu: View / Extensions manager. On macOS, Extensions manager requires the [application to be installed](getting_started.html#mac).
+- Open Extensions manager using menu: View / Extensions manager. On macOS, Extensions manager requires the [application to be installed](getting_started.md#mac).
- To restore previously installed extensions:
- Go to "Restore Extensions" tab
- Check the checkbox on the left side of each extension that should be installed. If the extension that you want to install is disabled and has "not found for current Slicer version" message is displayed below its name then follow instructions in [troubleshooting section](#extension-is-not-found-for-current-slicer-version).
@@ -85,7 +85,7 @@ When starting the extensions manager, the "Extensions manager is starting, pleas
### Extensions manager does not show any extensions
This can be due to several reasons:
-- On macOS: Extensions manager displays the message "Extensions can not be installed" if the application is not installed. See [application installation instructions](getting_started.html#mac).
+- On macOS: Extensions manager displays the message "Extensions can not be installed" if the application is not installed. See [application installation instructions](getting_started.md#mac).
- Extensions Catalog server is temporarily overloaded (indicated by extensions manager taking several minutes to start and having dozens of `Error retrieving extension metadata` messages in the application log)
- Recommended action: retry installing extensions an hour later.
- Extensions have not yet been built for the installed Slicer Preview Release (extensions are made available for latest Slicer Preview Release each day at around 12pm EST)
@@ -104,7 +104,7 @@ For Slicer Stable Releases: If certain extensions are available, but a particula
For Slicer Preview Releases, due to the constantly updating nature of the preview release, extensions may be missing at times:
- Extensions for latest Slicer Preview Release are uploaded by about 10am EST each day. If you need complete set of extensions then either wait or install previous releases as described [above](#extensions-manager-does-not-show-any-extensions).
-- Factory system errors: Occasionally, issues with the factory system will prevent some or all extensions from building. See [dashboard](../developer_guide/extensions.html#continuous-integration) for status information.
+- Factory system errors: Occasionally, issues with the factory system will prevent some or all extensions from building. See [dashboard](../developer_guide/extensions.md#continuous-integration) for status information.
- Extension build errors: The extension may not have been updated to work with your Slicer version or a problem may have been introduced recently. Contact the extension's maintainer. If the maintainer cannot be reached then ask for help on the [Slicer forum](https://discourse.slicer.org).
### Extensions manager is not visible in the menu
diff --git a/Docs/user_guide/get_help.md b/Docs/user_guide/get_help.md
index c28ce6a8994..0a069013883 100644
--- a/Docs/user_guide/get_help.md
+++ b/Docs/user_guide/get_help.md
@@ -1,6 +1,6 @@
# Get Help
-[Contact the Slicer community or commercial partners](about.html#contact-us) if you have any questions, bug reports, or enhancement requests - following the guidelines described below.
+[Contact the Slicer community or commercial partners](about.md#contact-us) if you have any questions, bug reports, or enhancement requests - following the guidelines described below.
## I need help in using Slicer
@@ -36,11 +36,11 @@ Background: Funding for Slicer is provided through competitive mechanisms primar
### Slicer application does not start
-- Your computer CPU or graphics capabilities may not meet [minimum system requirements](getting_started.html#system-requirements). Updating your graphics driver may fix some problems, but if that does not help and you have an old computer then you may need to upgrade to a more recently manufactured computer.
+- Your computer CPU or graphics capabilities may not meet [minimum system requirements](getting_started.md#system-requirements). Updating your graphics driver may fix some problems, but if that does not help and you have an old computer then you may need to upgrade to a more recently manufactured computer.
- Slicer may not work if it is installed in a folder that has special characters in their name. Try installing Slicer in a path that only contains latin letters and numbers (a-z, 0-9).
- Your Slicer settings might have become corrupted
- Try launching slicer using `Slicer.exe --disable-settings` (if it fixes the problem, delete Slicer.ini and Slicer-.ini files from your Slicer settings directory.
- - Rename or remove your Slicer settings directory (for example, `c:\Users\\AppData\Roaming\NA-MIC`). See instructions for getting the settings directory [here](settings.html#settings-file-location). Try to launch Slicer.
+ - Rename or remove your Slicer settings directory (for example, `c:\Users\\AppData\Roaming\NA-MIC`). See instructions for getting the settings directory [here](settings.md#settings-file-location). Try to launch Slicer.
- There may be conflicting/incompatible libraries in your system path (most likely caused by installing applications that place libraries in incorrect location on your system). Check your system logs for details and report the problem.
- On Windows:
- Start Event Viewer (eventvwr.exe), select Windows Logs / Application, and find the application error. If there is a DLL loading problem a line similar to this will appear: `Faulting module path: .dll`. If you found a line similar to this, then try the following workaround: Start a command window. Enter `set path=` to clear the path variable. Enter Slicer.exe to start Slicer. If Slicer starts successfully then you need to remove remove unnecessary items from the system path (or delete the libraries installed at incorrect locations).
diff --git a/Docs/user_guide/getting_started.md b/Docs/user_guide/getting_started.md
index b05e43a4563..824f4308428 100644
--- a/Docs/user_guide/getting_started.md
+++ b/Docs/user_guide/getting_started.md
@@ -32,7 +32,7 @@ To download Slicer, click [here](http://download.slicer.org/).
**Notes:**
- The "Preview Release" of 3D Slicer is updated daily (process starts at 11pm ET and takes few hours to complete) and represents the latest development including new features and fixes.
- The "Stable Release" is usually updated a few times a year and is more rigorously tested.
-- Slicer is generally simple to install on all platforms. It is possible to install multiple versions of the application on the same user account and they will not interfere with each other. If you run into mysterious problems with your installation you can try deleting the [application settings files](settings.html#settings-file-location).
+- Slicer is generally simple to install on all platforms. It is possible to install multiple versions of the application on the same user account and they will not interfere with each other. If you run into mysterious problems with your installation you can try deleting the [application settings files](settings.md#settings-file-location).
- Only 64-bit Slicer installers are available to download. Developers can attempt to build 32-bit versions on their own if they need to run Slicer on a 32-bit operating system. That said, this should be carefully considered as many clinical research tasks, such as processing of large CT or MR volumetric datasets, require more memory than can be accommodated with a 32-bit program.
Once downloaded, follow the instructions below to complete installation:
@@ -126,7 +126,7 @@ You can customize views (show orientation marker, ruler, change orientation, tra
3D Slicer is built on a modular architecture. Choose a module to process or analyze your data. Most important modules are the followings (complete list is available in [Modules](modules/index) section):
-- [Welcome](modules/welcome): The default module when 3D Slicer is started. The panel features options for loading data and customizing 3D Slicer. Below those options are drop-down boxes that contain essential information for using 3D Slicer.
+- [Welcome](modules/welcome.md): The default module when 3D Slicer is started. The panel features options for loading data and customizing 3D Slicer. Below those options are drop-down boxes that contain essential information for using 3D Slicer.
- [Data](modules/data): acts as a central data-organizing hub. Lists all data currently in the scene and allows basic operations such as search, rename, delete and move.
- [DICOM](modules/dicom): Import and export DICOM objects, such as images, segmentations, strucutre sets, radiation therapy objects, etc.
- [Volumes](modules/volumes): Used for changing the appearance of various volume types.
@@ -134,7 +134,7 @@ You can customize views (show orientation marker, ruler, change orientation, tra
- [Segmentations](modules/segmentations): Edit display properties and import/export segmentations.
- [Segment Editor](modules/segmenteditor): Segment 3D volumes using various manual, semi-automatic, and automatic tools.
- [Markups](modules/markups): Allows the creation and editing of markups associated with a scene. Currently, lists of fiducially are supported as markups.
-- [Models](modules/models): Loads and adjusts display parameters of models. Allows the user to change the appearance of and organize 3D surface models.
+- [Models](modules/models.md): Loads and adjusts display parameters of models. Allows the user to change the appearance of and organize 3D surface models.
- [Transforms](modules/transforms): This module is used for creating and editing transformation matrices. You can establish these relations by moving nodes from the Transformable list to the Transformed list or by dragging the nodes under the Transformation nodes in the Data module.
#### Save data
diff --git a/Docs/user_guide/image_segmentation.md b/Docs/user_guide/image_segmentation.md
index 17f269bf539..43ba31a5a89 100644
--- a/Docs/user_guide/image_segmentation.md
+++ b/Docs/user_guide/image_segmentation.md
@@ -7,7 +7,7 @@
Segmentation of images (also known as contouring or annotation) is a procedure to delinate regions in the image, typically corresponding to anatomical structures, lesions, and various other object space.
It is a very common procedure in medical image computing, as it is required for visualization of certain structures, quantification (measuring volume, surface, shape properties), 3D printing, and masking (restricting processing or analysis to a specific region), etc.
-Segmentation may be performed manually, for example by iterating through all the slices of an image and drawing a contour at the boundary; but often semi-automatic or fully automatic methods are used. [Segment Editor](SegmentEditor) module offers a wide range of segmentation methods.
+Segmentation may be performed manually, for example by iterating through all the slices of an image and drawing a contour at the boundary; but often semi-automatic or fully automatic methods are used. [Segment Editor](modules/segmenteditor.md) module offers a wide range of segmentation methods.
Result of a segmentation is stored in `segmentation` node in 3D Slicer. A segmentation node consists of multiple segments.
diff --git a/Docs/user_guide/modules/acpctransform.md b/Docs/user_guide/modules/acpctransform.md
index ac5dc31c8d2..1b84f0a673c 100644
--- a/Docs/user_guide/modules/acpctransform.md
+++ b/Docs/user_guide/modules/acpctransform.md
@@ -1,6 +1,5 @@
-# ACPC Transform
-
-Calculate a transformation that aligns brain images to [Talairach coordinate system](https://en.wikipedia.org/wiki/Talairach_coordinates) (also known as stereotaxic or ACPC coordinate system) based on anatomical landmarks.
+```{include} ../../_moduledescriptions/ACPCTransformOverview.md
+```
## Tutorial
@@ -33,22 +32,7 @@ Calculate a transformation that aligns brain images to [Talairach coordinate sys
- `Reference volume` -> the original volume (should work well if `Center volume` option is disabled) or a standard Talairach volume (recommended if `Center volume` is enabled). Alternatively, output volume geometry can be specified using `Manual Output Parameters` section.
- `Transfrom Node` output transform of ACPC module as
-## Panels and their use
-
-- ACPC Line: markups line specified by the user, connecting a point at the anterior commissure with a point at the posterior commissure.
-- Midline: series of markups points (at least 3) placed by the user on the mid sagittal plane (the division between the hemispheres of the brain).
-- Center volume: If this option is enabled then the output transform will translate the AC point to the origin. If this option is disabled then the position of the volume will be preserved and transform will only change the orientation.
-- Output transform: transformation matrix (rigid translation and rotation) that the module computes from the input landmarks. If this transformation is applied to the volume then it will make the ACPC line "horizontal" (be in AP axis of the patient coordinate system), line up the mid sagittal plane "vertical" (fit on the AS plane of the patient coordinate system), and (if centering is enabled) then make the AC point the origin (the (0,0,0) coordinate in the patient coordiante system).
-
-## Contributors
-
-Authors:
-- Nicole Aucoin (SPL, BWH)
-- Ron Kikinis (SPL, BWH)
-- Andras Lasso (PerkLab, Queen's University)
-
-## Acknowledgements
-
-This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
+```{include} ../../_moduledescriptions/ACPCTransformParameters.md
+```

diff --git a/Docs/user_guide/modules/addscalarvolumes.md b/Docs/user_guide/modules/addscalarvolumes.md
new file mode 100644
index 00000000000..766ce1276b9
--- /dev/null
+++ b/Docs/user_guide/modules/addscalarvolumes.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/AddScalarVolumesOverview.md
+```
+
+```{include} ../../_moduledescriptions/AddScalarVolumesParameters.md
+```
diff --git a/Docs/user_guide/modules/annotations.md b/Docs/user_guide/modules/annotations.md
new file mode 100644
index 00000000000..e6925924c7e
--- /dev/null
+++ b/Docs/user_guide/modules/annotations.md
@@ -0,0 +1,70 @@
+# Annotations
+
+## Overview
+
+Create and edit annotations, supplementary information associated with a scene. Currently supported annotations are rulers and regions of interest (ROIs). Use the Markups module for fiducials.
+
+## Panels and their use
+
+- **Annotations**:
+ - **Edit**: Edit the properties of all the annotations in the scene
+ - **Select All Button**: Select all annotations and hierarchies
+ - **Unselect All Button**: Unselect all annotations and hierarchies
+ - **Visible Selected Button**: Toggle the visibility of highlighted Annotations. Hierarchies are ignored.
+ - **Lock Selected Button**: Lock or unlock highlighted annotations. Hierarchies are ignored.
+ - **Jump Slices Button**: Reset slice views to the first coordinate of the highlighted annotation.
+ - **Add HierarchyButton**: Create a new hierarchy. If a hierarchy is currently highlighted, the new one will be a sub-hierarchy
+ - **Generate Report**: Display a report of highlighted annotations
+ - **Delete Selected Button**: Delete highlighted annotations. Hierarchies have to single selected to get deleted.
+ - **Active list**: The name of the currently active list, which controls which list new annotations will be added to. If the top level All Annotations list is active, the module will automatically create separate lists for each type of annotation. Click on a hierarchy in the tree view to make it active and to add new annotations to it.
+ - **Visible Hierarchy Button**: Set all annotations in active list visible
+ - **Invisible Hierarchy Button**: Set all annotations in active list invisible
+ - **Lock Hierarchy Button**: Set all annotations in active list locked
+ - **Unlock Hierarchy Button**: Set all annotations in active list to be unlocked
+
+- **Annotation Hierarchy tree view**:
+ - **Selected**: Selected annotations can be passed to command line modules.
+ - **Vis**: Click on this button (eye open or closed) to toggle the visibility of the annotation in the 2D and 3D views
+ - **Lock**: Click on this button to make the annotation be locked into place, not responding to mouse events. This is useful if you need to manipulate other things in the scene.
+ - **Edit**: Click on the icon in the Edit column to bring up the Modify Annotation Properties dialog box
+ - **Value**: A useful value associated with the annotation (length for rulers)
+ - **Name**: The name of the annotation, usually kept short, one letter and a number, as it's displayed in the 3D and 2D windows.
+ - **Description**: A longer text describing this annotation
+
+- **Modify Annotation Hierarchy Properties**:
+ - **Type**: The class types for this annotation hierarchy
+ - **Color**: Click on this button to bring up a color picker widget to set the color used for all annotations in this hierarchy when the hierarchy is collapsed.
+ - **Apply to List**: If this checkbox is ticked, change the color on all the annotations in this hierarchy. Default true.
+ - **Visibility**: Click on this button (eye open or closed) to toggle the visibility of the annotations in this hierarchy in the 2D and 3D views.
+ - **Name**: The name of the annotation hierarchy, describing the annotations below it.
+ - **Description**: A longer text describing this annotation hierarchy
+ - **List Text Scale**: Set the annotation text scale for all annotations in this hierarchy. This slider is not initialized from the current annotation text scales but from the default text scale for a single annotation. Use the Default button to reset to this default value.
+ - **List Glyph Scale**: Set the annotation glyph scale for all annotations in this hierarchy. This slider is not initialized from the current annotation glyph scales but from the default glyph scale for a single annotation. Use the Default button to reset to this default value.
+ - **List Glyph Type**: Set the annotation glyph type for all annotations in this hierarchy. This menu is not initialized from the current annotation glyph types but from the default glyph type for a single annotation. Use the Default button to reset to this default value.
+
+- **Modify Annotation Properties**:
+ - **Type**: The class type for this annotation. For example, a ruler or a region of interest.
+ - **Color**: Click on this button to bring up a color picker widget to set the color for all parts of the annotation (text, glyphs, lines)
+ - **Visibility**: Click on this button (eye open or closed) to toggle the visibility of the annotation in the 2D and 3D views.
+ - **Lock**: Click on this button to make the annotation be locked into place, not responding to mouse events. This is useful if you need to manipulate other things in the scene.
+ - **Name**: The name of the annotation, usually kept short, one letter and a number, as it's displayed in the 3D and 2D windows.
+ - **Size**: Click on the Small, Medium, Large buttons to change the scaling of the annotation
+ - **RAS**: The world coordinates of this annotation in the default Slicer Right-Anterior-Superior coordinate system. Double click to edit the values
+ - **Description**: A longer text describing this annotation
+
+- **Modify Annotation Properties: Advanced**:
+ - **Text**: Set the descriptive text, text color, scale, opacity on this panel
+ - **Points**: Set the coordinate location of the point(s), glyph color, scale, glyph type, opacity, ambient, diffuse, specular material properites on this panel
+ - **Lines**: This panel is only enabled for rulers. Set the line color, label visibility, label position along the line (from 0-1), tick spacing, maximum number of ticks, opacity, ambient, diffuse, specular material properites on this panel
+ - **ROI**: This panel is only enabled for regions of interest. Set the ranges for the ROI along it's axes, LR, PA, IS. Toggle the ROI visibility and if it's updated interactively on this panel
+
+- **Toolbar**:
+ - **Annotations toolbar**: Create new annotations by selecting them from the drop down menu. Return to rotate mouse mode by clicking on the rotate icon. Toggle persistent placement of annotations by checking and unchecking the Persistent checkbox
+
+## Contributors
+
+Nicole Aucoin (SPL, BWH), Daniel Haehn (SPL, BWH), Kilian Pohl (UPenn), Yong Zhang (IBM), Wendy Plesniak (SPL, BWH)
+
+## Acknowledgements
+
+The research was funded by an ARRA supplement to NIH NCRR (P41 RR13218).This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
diff --git a/Docs/user_guide/modules/brainsdeface.md b/Docs/user_guide/modules/brainsdeface.md
new file mode 100644
index 00000000000..9de03672660
--- /dev/null
+++ b/Docs/user_guide/modules/brainsdeface.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSDefaceOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSDefaceParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsdwicleanup.md b/Docs/user_guide/modules/brainsdwicleanup.md
new file mode 100644
index 00000000000..8c4a8174093
--- /dev/null
+++ b/Docs/user_guide/modules/brainsdwicleanup.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSDWICleanupOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSDWICleanupParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsfit.md b/Docs/user_guide/modules/brainsfit.md
new file mode 100644
index 00000000000..5ce6221b4ba
--- /dev/null
+++ b/Docs/user_guide/modules/brainsfit.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSFitOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSFitParameters.md
+```
diff --git a/Docs/user_guide/modules/brainslabelstats.md b/Docs/user_guide/modules/brainslabelstats.md
new file mode 100644
index 00000000000..29832d0e176
--- /dev/null
+++ b/Docs/user_guide/modules/brainslabelstats.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSLabelStatsOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSLabelStatsParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsresample.md b/Docs/user_guide/modules/brainsresample.md
new file mode 100644
index 00000000000..ad1d5090e77
--- /dev/null
+++ b/Docs/user_guide/modules/brainsresample.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSResampleOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSResampleParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsresize.md b/Docs/user_guide/modules/brainsresize.md
new file mode 100644
index 00000000000..75af3678a58
--- /dev/null
+++ b/Docs/user_guide/modules/brainsresize.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSResizeOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSResizeParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsroiauto.md b/Docs/user_guide/modules/brainsroiauto.md
new file mode 100644
index 00000000000..40e94574e47
--- /dev/null
+++ b/Docs/user_guide/modules/brainsroiauto.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSROIAutoOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSROIAutoParameters.md
+```
diff --git a/Docs/user_guide/modules/brainsstriprotation.md b/Docs/user_guide/modules/brainsstriprotation.md
new file mode 100644
index 00000000000..fc92b1a32b9
--- /dev/null
+++ b/Docs/user_guide/modules/brainsstriprotation.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSStripRotationOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSStripRotationParameters.md
+```
diff --git a/Docs/user_guide/modules/brainstransformconvert.md b/Docs/user_guide/modules/brainstransformconvert.md
new file mode 100644
index 00000000000..f7ba435ee36
--- /dev/null
+++ b/Docs/user_guide/modules/brainstransformconvert.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/BRAINSTransformConvertOverview.md
+```
+
+```{include} ../../_moduledescriptions/BRAINSTransformConvertParameters.md
+```
diff --git a/Docs/user_guide/modules/cameras.md b/Docs/user_guide/modules/cameras.md
new file mode 100644
index 00000000000..bb8b0f80bb4
--- /dev/null
+++ b/Docs/user_guide/modules/cameras.md
@@ -0,0 +1,27 @@
+# Cameras
+
+## Overview
+
+Bring core support for multiple cameras and 3D views in Slicer.
+
+The multi views and cameras framework is available from the "Camera" modules. This module displays two pull-down menus. The first one, "View", lists all available views and lets the user create new 3D views. The second one, "Camera", lists all available cameras, and lets the user create new cameras.
+
+Only one camera is used by a view at a time. When a view is selected from the pull-down, the camera it is currently using is automatically selected in the second pull-down.
+
+- **Create a new camera:** In the "Camera" pull-down menu, select "Create New Camera": a new camera node is created (most likely named "Camera1", as opposed to the default "Camera" node), and automatically assigned to the currently selected view node (named "View" by default). Try interacting with the 3D view.
+- **Assign a camera to a view:** Select any camera node from the "Camera" pull-down menu: the camera is assigned to the currently selected view. For example, try selecting back the "Camera" node if you have created a "Camera1" node in the previous step, and you should notice the 3D view on the right update itself to reflect the different point of view. Note: a camera can not be shared between two views: selecting a camera already used by a view will effectively swap the cameras between the two views.
+- **Create a new view:** Select the "Tabbed 3D Layout" from the layout button in the toolbar. In the "View" pull-down menu, select "Create New View": a new view node is created (most likely named "View1", as opposed to the default "View" node), and displayed on the right under a new tab. You can select which view to display by clicking on the corresponding tab in the "Tabbed 3D Layout". Interacting in that view will automatically mark it as "active"; there can only be one "active" view at a time in Slicer, and it always feature a thin dark blue border. Since a view can not exist without a camera, a new camera node is automatically created and assigned to this new view (most likely named "Camera2" if you have created "Camera1" in the previous steps). At this point, you can assign any of the previously created cameras to this new view (say, the "Camera" or "Camera1" nodes).
+
+## Panels and their use
+
+- **Cameras**: List the 3D views and cameras in the scene.
+ - **View**: Select a 3D view. "View" is the main 3D view, additional 3D views are named "View_1", "View_2" etc. On selection, the view active camera is automatically selected in the Camera pull-down.
+ - **Camera**: Active camera of the current view.
+
+## Contributors
+
+Julien Finet (Kitware), Sebastien Barré (Kitware)
+
+## Acknowledgements
+
+This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
diff --git a/Docs/user_guide/modules/castscalarvolume.md b/Docs/user_guide/modules/castscalarvolume.md
new file mode 100644
index 00000000000..62e44bed9f9
--- /dev/null
+++ b/Docs/user_guide/modules/castscalarvolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/CastScalarVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/CastScalarVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/checkerboardfilter.md b/Docs/user_guide/modules/checkerboardfilter.md
new file mode 100644
index 00000000000..abef02f3eb9
--- /dev/null
+++ b/Docs/user_guide/modules/checkerboardfilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/CheckerBoardFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/CheckerBoardFilterParameters.md
+```
diff --git a/Docs/user_guide/modules/createdicomseries.md b/Docs/user_guide/modules/createdicomseries.md
new file mode 100644
index 00000000000..13832306200
--- /dev/null
+++ b/Docs/user_guide/modules/createdicomseries.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/CreateDICOMSeriesOverview.md
+```
+
+```{include} ../../_moduledescriptions/CreateDICOMSeriesParameters.md
+```
diff --git a/Docs/user_guide/modules/curvatureanisotropicdiffusion.md b/Docs/user_guide/modules/curvatureanisotropicdiffusion.md
new file mode 100644
index 00000000000..2f78dd2df9c
--- /dev/null
+++ b/Docs/user_guide/modules/curvatureanisotropicdiffusion.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/CurvatureAnisotropicDiffusionOverview.md
+```
+
+```{include} ../../_moduledescriptions/CurvatureAnisotropicDiffusionParameters.md
+```
diff --git a/Docs/user_guide/modules/data.md b/Docs/user_guide/modules/data.md
index 3322d172db9..279531bf63e 100644
--- a/Docs/user_guide/modules/data.md
+++ b/Docs/user_guide/modules/data.md
@@ -132,7 +132,7 @@ To create a C++ plugin, implement a child class of qSlicerSubjectHierarchyAbstra
## References
- Additional information on [Subject hierarchy labs page](https://www.slicer.org/wiki/Documentation/Labs/SubjectHierarchy)
-- Manual editing of segmentations can be done in the [Segment Editor module](SegmentEditor)
+- Manual editing of segmentations can be done in the [Segment Editor module](segmenteditor.md)
## Contributors
diff --git a/Docs/user_guide/modules/dicom.md b/Docs/user_guide/modules/dicom.md
index 4d602a6190b..d576d345371 100644
--- a/Docs/user_guide/modules/dicom.md
+++ b/Docs/user_guide/modules/dicom.md
@@ -53,7 +53,7 @@ Since DICOM files are often located in several folders, they can cross-reference
#### DICOM import
-1. Make sure that all required Slicer extensions are installed. Slicer core contains DICOM import plugin for importing images, but additional extensions may be needed for other information objects. For example, *SlicerRT extension is needed for importing/exporting radiation therapy information objects (RT structure set, dose, image, plan). Quantitative reporting extension is needed to import export DICOM segmentation objects, structured reports, and parametric maps.* See complete list in [supported data formats section](../data_loading_and_saving.html#supported-data-formats).
+1. Make sure that all required Slicer extensions are installed. Slicer core contains DICOM import plugin for importing images, but additional extensions may be needed for other information objects. For example, *SlicerRT extension is needed for importing/exporting radiation therapy information objects (RT structure set, dose, image, plan). Quantitative reporting extension is needed to import export DICOM segmentation objects, structured reports, and parametric maps.* See complete list in [supported data formats section](../data_loading_and_saving.md#supported-data-formats).
2. Go to DICOM module
3. Select folders that contain DICOM files
- Option A: Drag-and-drop the folder that contains DICOM files to the Slicer application window.
@@ -67,7 +67,11 @@ Since DICOM files are often located in several folders, they can cross-reference
2. Double-click on the patient, study, or series to load.
3. Click "Show DICOM database" button to toggle between the database browser (to load more data) and the viewer (to see what is loaded into the scene already)
-Note: Selected patients/studies/series can be loaded at once by first selecting items to load. Shift-click to select a range, Ctrl-click to select/unselect a single item. If an item in the patient or study list is selected then by default all series that belong to that item will be loaded. Click "Load" button to load selected items.
+:::{admonition} Tip
+
+Selected patients/studies/series can be loaded at once by first selecting items to load. Shift-click to select a range, Ctrl-click to select/unselect a single item. If an item in the patient or study list is selected then by default all series that belong to that item will be loaded. Click "Load" button to load selected items.
+
+:::
Advanced data loading: It is often possible to interpret DICOM data in different ways. If the application loaded data differently than expected then check "Advanced" checkbox, click "Examine" button, select all items in the list in the bottom (containing DICOM data, Reader, Warnings columns), and click "Load".
@@ -79,7 +83,7 @@ By right clicking on a Patient, Study, or Series, you can delete the entry from
Data in the scene can be exported to DICOM format, to be stored in DICOM database or exported to DICOM files:
-1. Make sure that all required Slicer extensions are installed. Slicer core contains DICOM export plugin for exporting images, but additional extensions may be needed for other information objects. *SlicerRT extension is needed for importing/exporting radiation therapy information objects (RT structure set, dose, image, plan). Quantitative reporting extension is needed to import export DICOM segmentation objects, structured reports, and parametric maps.* See complete list in [Supported data formats page](../supported_data_formats).
+1. Make sure that all required Slicer extensions are installed. Slicer core contains DICOM export plugin for exporting images, but additional extensions may be needed for other information objects. *SlicerRT extension is needed for importing/exporting radiation therapy information objects (RT structure set, dose, image, plan). Quantitative reporting extension is needed to import export DICOM segmentation objects, structured reports, and parametric maps.* See complete list in [Supported data formats page](../data_loading_and_saving.md#supported-data-formats).
2. Go to Data module or DICOM module.
3. Right-click on a data node in the data tree that will be converted to DICOM format.
4. Select the export type in the bottom left of the export dialog. This is necessary because there may be several DICOM information objects that can store the same kind of data. For example, segmentation can be stored as DICOM segmentation object (modern DICOM) or RT structure set (legacy representation, mostly used by radiation treatment planning).
@@ -190,7 +194,7 @@ If none of the above helps then check the Slicer error logs and report the error
DICOM is a complex way to represent data, and often scanners and other software will generate 'non-standard' files that claim to be DICOM but really aren't compliant with the specification. In addition, the specification itself has many variations and special formats that Slicer is not able to understand. Slicer is used most often with CT and MR DICOM objects, so these will typically work.
If you have trouble importing DICOM data here are some steps to try:
-- Make sure you are following the [DICOM](Module_DICOM) module documentation.
+- Make sure you are following the [DICOM loading instructions](#dicom-loading).
- We are constantly improving the application (new preview version is released every day), so there is a chance that the problem you encountered is addressed in a recent version. Try loading the data using the latest stable and the latest nightly versions of Slicer.
- Make sure the Slicer temporary folder is writeable. Temporary folder can be selected in menu: Edit / Application Settings / Modules / Temporary directory.
- Try moving the data and the database directory to a path that includes only US English characters (ASCII) to avoid possible parsing errors. No special, international characters are allowed.
diff --git a/Docs/user_guide/modules/dwiconvert.md b/Docs/user_guide/modules/dwiconvert.md
new file mode 100644
index 00000000000..9c356d07715
--- /dev/null
+++ b/Docs/user_guide/modules/dwiconvert.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/DWIConvertOverview.md
+```
+
+```{include} ../../_moduledescriptions/DWIConvertParameters.md
+```
diff --git a/Docs/user_guide/modules/editor.md b/Docs/user_guide/modules/editor.md
new file mode 100644
index 00000000000..3c116bfcca2
--- /dev/null
+++ b/Docs/user_guide/modules/editor.md
@@ -0,0 +1,3 @@
+# Editor
+
+This module is deprecated and will be removed from the application. It is replaced by [Segment editor module](segmenteditor.md), which offers many more features and significantly better performance.
diff --git a/Docs/user_guide/modules/executionmodeltour.md b/Docs/user_guide/modules/executionmodeltour.md
new file mode 100644
index 00000000000..47ee96a46b4
--- /dev/null
+++ b/Docs/user_guide/modules/executionmodeltour.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ExecutionModelTourOverview.md
+```
+
+```{include} ../../_moduledescriptions/ExecutionModelTourParameters.md
+```
diff --git a/Docs/user_guide/modules/expertautomatedregistration.md b/Docs/user_guide/modules/expertautomatedregistration.md
new file mode 100644
index 00000000000..5c7265c8361
--- /dev/null
+++ b/Docs/user_guide/modules/expertautomatedregistration.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ExpertAutomatedRegistrationOverview.md
+```
+
+```{include} ../../_moduledescriptions/ExpertAutomatedRegistrationParameters.md
+```
diff --git a/Docs/user_guide/modules/extractskeleton.md b/Docs/user_guide/modules/extractskeleton.md
new file mode 100644
index 00000000000..f1b85bcbb2f
--- /dev/null
+++ b/Docs/user_guide/modules/extractskeleton.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ExtractSkeletonOverview.md
+```
+
+```{include} ../../_moduledescriptions/ExtractSkeletonParameters.md
+```
diff --git a/Docs/user_guide/modules/fiducialregistration.md b/Docs/user_guide/modules/fiducialregistration.md
new file mode 100644
index 00000000000..ed5fe073f2f
--- /dev/null
+++ b/Docs/user_guide/modules/fiducialregistration.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/FiducialRegistrationOverview.md
+```
+
+```{include} ../../_moduledescriptions/FiducialRegistrationParameters.md
+```
diff --git a/Docs/user_guide/modules/gaussianblurimagefilter.md b/Docs/user_guide/modules/gaussianblurimagefilter.md
new file mode 100644
index 00000000000..6297f3bbbdb
--- /dev/null
+++ b/Docs/user_guide/modules/gaussianblurimagefilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/GaussianBlurImageFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/GaussianBlurImageFilterParameters.md
+```
diff --git a/Docs/user_guide/modules/gradientanisotropicdiffusion.md b/Docs/user_guide/modules/gradientanisotropicdiffusion.md
new file mode 100644
index 00000000000..c3a240a6cdc
--- /dev/null
+++ b/Docs/user_guide/modules/gradientanisotropicdiffusion.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/GradientAnisotropicDiffusionOverview.md
+```
+
+```{include} ../../_moduledescriptions/GradientAnisotropicDiffusionParameters.md
+```
diff --git a/Docs/user_guide/modules/grayscalefillholeimagefilter.md b/Docs/user_guide/modules/grayscalefillholeimagefilter.md
new file mode 100644
index 00000000000..04beacd8d77
--- /dev/null
+++ b/Docs/user_guide/modules/grayscalefillholeimagefilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/GrayscaleFillHoleImageFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/GrayscaleFillHoleImageFilterParameters.md
+```
diff --git a/Docs/user_guide/modules/grayscalegrindpeakimagefilter.md b/Docs/user_guide/modules/grayscalegrindpeakimagefilter.md
new file mode 100644
index 00000000000..c4b505d97ea
--- /dev/null
+++ b/Docs/user_guide/modules/grayscalegrindpeakimagefilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/GrayscaleGrindPeakImageFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/GrayscaleGrindPeakImageFilterParameters.md
+```
diff --git a/Docs/user_guide/modules/grayscalemodelmaker.md b/Docs/user_guide/modules/grayscalemodelmaker.md
new file mode 100644
index 00000000000..b9d720dfb38
--- /dev/null
+++ b/Docs/user_guide/modules/grayscalemodelmaker.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/GrayscaleModelMakerOverview.md
+```
+
+```{include} ../../_moduledescriptions/GrayscaleModelMakerParameters.md
+```
diff --git a/Docs/user_guide/modules/histogrammatching.md b/Docs/user_guide/modules/histogrammatching.md
new file mode 100644
index 00000000000..0c01bf7797d
--- /dev/null
+++ b/Docs/user_guide/modules/histogrammatching.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/HistogramMatchingOverview.md
+```
+
+```{include} ../../_moduledescriptions/HistogramMatchingParameters.md
+```
diff --git a/Docs/user_guide/modules/imagelabelcombine.md b/Docs/user_guide/modules/imagelabelcombine.md
new file mode 100644
index 00000000000..f15118426d7
--- /dev/null
+++ b/Docs/user_guide/modules/imagelabelcombine.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ImageLabelCombineOverview.md
+```
+
+```{include} ../../_moduledescriptions/ImageLabelCombineParameters.md
+```
diff --git a/Docs/user_guide/modules/index.md b/Docs/user_guide/modules/index.md
new file mode 100644
index 00000000000..a62987328ed
--- /dev/null
+++ b/Docs/user_guide/modules/index.md
@@ -0,0 +1,164 @@
+# Modules
+
+Module documentation is still a work in progress.
+You can find more module documentation in the
+[Slicer wiki](https://www.slicer.org/wiki/Documentation/Nightly).
+
+```{toctree}
+:maxdepth: 1
+data.md
+dicom.md
+markups.md
+models.md
+sceneviews.md
+segmentations.md
+segmenteditor.md
+segmentstatistics.md
+transforms.md
+viewcontrollers.md
+volumerendering.md
+volumes.md
+welcome.md
+```
+
+## Informatics
+```{toctree}
+:maxdepth: 1
+colors.md
+sampledata.md
+tables.md
+terminologies.md
+dataprobe.md
+```
+%comparevolumes.md
+
+## Registration
+```{toctree}
+:maxdepth: 1
+acpctransform.md
+brainsfit.md
+brainsresample.md
+brainsresize.md
+fiducialregistration.md
+landmarkregistration.md
+performmetrictest.md
+reformat.md
+```
+
+## Segmentation
+```{toctree}
+:maxdepth: 1
+simpleregiongrowingsegmentation.md
+brainsroiauto.md
+robuststatisticssegmenter.md
+```
+
+## Quantification
+```{toctree}
+:maxdepth: 1
+petstandarduptakevaluecomputation.md
+```
+
+## Sequences
+```{toctree}
+:maxdepth: 1
+sequences.md
+cropvolumesequences.md
+multivolumeimporter.md
+multivolumeexplorer.md
+```
+
+## Diffusion
+```{toctree}
+:maxdepth: 1
+dmriinstall.md
+dwiconvert.md
+brainsdwicleanup.md
+```
+
+## Filtering
+```{toctree}
+:maxdepth: 1
+addscalarvolumes.md
+castscalarvolume.md
+curvatureanisotropicdiffusion.md
+gaussianblurimagefilter.md
+gradientanisotropicdiffusion.md
+grayscalefillholeimagefilter.md
+grayscalegrindpeakimagefilter.md
+maskscalarvolume.md
+medianimagefilter.md
+multiplyscalarvolumes.md
+n4itkbiasfieldcorrection.md
+checkerboardfilter.md
+extractskeleton.md
+histogrammatching.md
+imagelabelcombine.md
+simplefilters.md
+subtractscalarvolumes.md
+thresholdscalarvolume.md
+votingbinaryholefillingimagefilter.md
+```
+
+## Utilities
+```{toctree}
+:maxdepth: 1
+brainsdeface.md
+brainsstriprotation.md
+brainstransformconvert.md
+dicompatcher.md
+endoscopy.md
+screencapture.md
+```
+
+## Surface Models
+```{toctree}
+:maxdepth: 1
+dynamicmodeler.md
+grayscalemodelmaker.md
+labelmapsmoothing.md
+mergemodels.md
+modelmaker.md
+modeltolabelmap.md
+probevolumewithmodel.md
+surfacetoolbox.md
+```
+
+## Converters
+```{toctree}
+:maxdepth: 1
+createdicomseries.md
+cropvolume.md
+orientscalarvolume.md
+vectortoscalarvolume.md
+resampledtivolume.md
+resamplescalarvectordwivolume.md
+resamplescalarvolume.md
+```
+
+## Developer Tools
+```{toctree}
+:maxdepth: 1
+cameras.md
+eventbroker.md
+executionmodeltour.md
+extensionwizard.md
+```
+
+## Legacy
+```{toctree}
+:maxdepth: 1
+annotations.md
+datastore.md
+editor.md
+expertautomatedregistration.md
+labelstatistics.md
+brainslabelstats.md
+```
+
+## Testing
+```{toctree}
+:maxdepth: 1
+performancetests.md
+selftests.md
+```
diff --git a/Docs/user_guide/modules/index.rst b/Docs/user_guide/modules/index.rst
deleted file mode 100644
index 315e3f2b8fe..00000000000
--- a/Docs/user_guide/modules/index.rst
+++ /dev/null
@@ -1,124 +0,0 @@
-.. _modules_index:
-
-#########
-Modules
-#########
-
-Module documentation is still a work in progress.
-You can find more module documentation in the
-`Slicer wiki `_.
-
-.. toctree::
-
- acpctransform.md
- annotations.md
- data.md
- dicom.md
- dicompatcher.md
- markups.md
- models.md
- sceneviews.md
- segmentations.md
- segmenteditor.md
- segmentstatistics.md
- transforms.md
- viewcontrollers.md
- volumerendering.md
- volumes.md
- welcome.md
-
-.. Modules to be documented:
- endoscopy.md
- screencapture.md
- comparevolumes.md
- - Conveters
- createdicomseries.md
- cropvolume.md
- orientscalarvolume.md
- vectortoscalarvolume.md
- resampledtivolume.md
- resamplescalarvectordwivolume.md
- resamplescalarvolume.md
- - Developer
- cameras.md
- eventbroker.md
- executionmodeltour.md
- extensionwizard.md
- - Diffusion
- dmriinstall.md
- dwiconvert.md
- brainsdwicleanup.md
- - Filtering
- n4itkbiasfieldcorrection.md
- checkerboardfilter.md
- extractskeleton.md
- histogrammatching.md
- imagelabelcombine.md
- simplefilters.md
- thresholdscalarvolume.md
- votingbinaryholefillingimagefilter.md
- islandremoval.md
- - Filtering/arithmetic
- addscalarvolumes.md
- castscalarvolume.md
- maskscalarvolume.md
- multiplyscalarvolumes.md
- subtractscalarvolumes.md
- - Filtering/denoising
- gradientanisotropicdiffusion.md
- curvatureanisotropicdiffusion.md
- gaussianblurimagefilter.md
- medianimagefilter.md
- - Filtering/morphology
- grayscalefillholeimagefilter.md
- grayscalegrindpeakimagefilter.md
- - Legacy
- annotations.md
- bsplinetodeformationfield.md
- datastore.md
- editor.md
- expertautomatedregistration.md
- labelstatistics.md
- brainslabelstats.md
- - Informatics
- colors.md
- sampledata.md
- tables.md
- terminologies.md
- dataprobe.md
- - Quantification
- petstandarduptakevaluecomputation.md
- - Registration
- brainsfit.md
- landmarkregistration.md
- performmetrictest.md
- brainsresample.md
- brainsresize.md
- brainsstriprotation.md
- brainstransformconvert.md
- - Registration/specialized
- brainsdemonwarp.md
- fiducialregistration.md
- reformat.md
- vbrainsdemonwarp.md
- - Segmentation
- simpleregiongrowingsegmentation.md
- brainsroiauto.md
- robuststatisticssegmenter.md
- - Sequences
- sequences.md
- sequences.md
- cropvolumesequences.md
- multivolumeimporter.md
- multivolumeexplorer.md
- - Models
- grayscalemodelmaker.md
- labelmapsmoothing.md
- mergemodels.md
- modelmaker.md
- modeltolabelmap.md
- probevolumewithmodel.md
- surfacetoolbox.md
- - Testing
- performancetests.md
- selftests.md
diff --git a/Docs/user_guide/modules/labelmapsmoothing.md b/Docs/user_guide/modules/labelmapsmoothing.md
new file mode 100644
index 00000000000..5622f7f79d9
--- /dev/null
+++ b/Docs/user_guide/modules/labelmapsmoothing.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/LabelMapSmoothingOverview.md
+```
+
+```{include} ../../_moduledescriptions/LabelMapSmoothingParameters.md
+```
diff --git a/Docs/user_guide/modules/markups.md b/Docs/user_guide/modules/markups.md
index 4d94939d90a..efc861dc4a4 100644
--- a/Docs/user_guide/modules/markups.md
+++ b/Docs/user_guide/modules/markups.md
@@ -128,8 +128,8 @@ See examples and other developer information in [Developer guide](../../develope
## Related modules
-- This module will replace [Annotations](Annotations) module.
-- [Endoscopy](Endoscopy) module uses fiducials
+- This module will replace [Annotations](annotations.md) module.
+- [Endoscopy](endoscopy.md) module uses fiducials
## Contributors
diff --git a/Docs/user_guide/modules/maskscalarvolume.md b/Docs/user_guide/modules/maskscalarvolume.md
new file mode 100644
index 00000000000..4d591c2a80a
--- /dev/null
+++ b/Docs/user_guide/modules/maskscalarvolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/MaskScalarVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/MaskScalarVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/medianimagefilter.md b/Docs/user_guide/modules/medianimagefilter.md
new file mode 100644
index 00000000000..9a83c845477
--- /dev/null
+++ b/Docs/user_guide/modules/medianimagefilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/MedianImageFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/MedianImageFilterParameters.md
+```
diff --git a/Docs/user_guide/modules/mergemodels.md b/Docs/user_guide/modules/mergemodels.md
new file mode 100644
index 00000000000..f28ee05a1cf
--- /dev/null
+++ b/Docs/user_guide/modules/mergemodels.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/MergeModelsOverview.md
+```
+
+```{include} ../../_moduledescriptions/MergeModelsParameters.md
+```
diff --git a/Docs/user_guide/modules/modelmaker.md b/Docs/user_guide/modules/modelmaker.md
new file mode 100644
index 00000000000..262d520a9ca
--- /dev/null
+++ b/Docs/user_guide/modules/modelmaker.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ModelMakerOverview.md
+```
+
+```{include} ../../_moduledescriptions/ModelMakerParameters.md
+```
diff --git a/Docs/user_guide/modules/models.md b/Docs/user_guide/modules/models.md
new file mode 100644
index 00000000000..2ec0ff78823
--- /dev/null
+++ b/Docs/user_guide/modules/models.md
@@ -0,0 +1,76 @@
+# Models
+
+## Overview
+
+This module is used for changing the appearance of and organizing 3d surface models.
+
+## Panels and their use
+
+- **Top level**:
+ - **Include Fibers**: Check this box to include fiber bundle nodes in the scene model hierarchy view.
+ - **Scroll to...**: Enter a model name to scroll the model hierarchy tree view to the first model matching that name.
+ - **Hide All Models**: Turn off the visibility flag on all models in the scene. If any hierarchies have the "Force color to children" checkbox selected, the hierarchy node visibility will override model visibility settings.
+ - **Show All Models**: Turn on the visibility flag on all models in the scene. If any hierarchies have the "Force color to children" checkbox selected, the hierarchy node visibility will override model visibility settings.
+
+- **Scene**:
+ - **Scene tree view**: The tree view of all models and model hierarchies in the current MRML scene. You can right click on the Scene element to add hierarchies and drag and drop models into them. Control the Visible property of the model via the eye icon (open/close to show/hide the models). The model color and opacity are also displayed on the same line as the model name. Model hierarchies allow you to group together models. When using the ModelMaker module, multiple models created from one label map are grouped under a model hierarchy node.
+
+- **Information Panel**:
+ - **Information**: Information about this surface model
+ - **Surface Area**: The calculated surface area of the model, in square millimetres
+ - **Volume**: The volume inside the surface model, in cubed millimetres
+ - **Number of Points**: Number of vertices in the surface model
+ - **Number of Cells**: Number of cells in the surface model
+ - **Number of Point Scalars**: Shows how many arrays of scalars are associated with the points of the surface model.
+ - **Number of Cell Scalars**: Shows how many arrays of scalars are associated with the cells of the surface model.
+ - **Filename**: Path to the file from which this surface model was loaded and/or where it will be saved.
+
+- **Display**: Control the display properties of the currently selected model in the Scene.
+ - **Visibility**: Control the visibility of the model.
+ - **Visible**: Is this surface model visible in the 3D view?
+ - **View**: In which views is this surface model visible? If none are checked, the model is visible in all 2D and 3D views.
+ - **Clip**: Is clipping (using the slice planes) enabled for this model? Once it is on, go to the Clipping pane to set the options.
+ - **Slice Intersections Visible**: If this flag is set, the intersections of the model with the slices will be computed and shown as an outline on the slice planes.
+ - **Slice Intersections Thickness**: Width in pixels of the intersection lines of the model with the slice planes.
+ - **Representation**: Control the representation of the model.
+ - **Representation**: Control the surface geometry representation (points, wireframe or surface) for the object. The default is Surface. Note that you can change the representation of all the models to wireframe with the 'w' key shortcut in the 3D view.
+ - **Point Size**: Set the diameter of the model points (if the model is a point cloud or if the representation is "Points".) The size is expressed in screen units. 1.0 by default.
+ - **Line Width**: Set the width of the model lines (if the model is a polyline or representation is "Wireframe"). The width is expressed in screen units. 1.0 by default.
+ - **Frontface culling**: Turn on/off culling of polygons based on the orientation of normals with respect to the camera. If frontface culling is on, polygons facing toward the camera are not drawn. Visible by default.
+ - **Backface culling**: Turn on/off culling of polygons based on orientation of normals with respect to the camera. If backface culling is on, polygons facing away from the camera are not drawn. This feature needs to be turned off if the inside of a model is viewed, either when clipping or when moving the camera inside an object. Hidden by default.
+ - **Color**: Control the colors of the model.
+ - **Color**: Control the color of the model. Note that the lighting can alter the color. Gray by default.
+ - **Opacity**: Control the opacity of the model. 1.0 is totally opaque and 0.0 is completely transparent. 1.0 by default.
+ - **Edge Visibility**: Turn on/off the visibility of the model edges (the wireframe). Hidden by default
+ - **Edge Color**: Control the color of the model edges (if Edge Visibility is enabled). Black by default.
+ - **Lighting**: Control the lighting effects on the model.
+ - **Lighting**: Control whether the model representation is impacted by the frontfacing light. If enabled, Ambient, Diffuse and Specular parameters are used to compute the lighting effect. Enabled by default.
+ - **Interpolation**: Control the shading interpolation method (Flat, Gouraud, Phong) for the model. Gouraud by default.
+ - **Shading**: Control the shading of the model. The material properties must be set if enabled.
+ - **Material Properties**: Material properties of the currently selected model
+ - **Ambient**: Controls the base lighting for the model.
+ - **Diffuse**: Controls the amount of light scattered from the model.
+ - **Specular**: Controls the highlights on the model.
+ - **Power**: The specular power.
+ - **Preview**: A rendering of a sphere using the current material properties, useful if the surface model isn't regular.
+ - **Scalars**: Scalar overlay properties
+ - **Visible**: Are the scalars visible on the surface model?
+ - **Active Scalar**: A drop down menu listing the current scalar overlays associated with this model and shows which one is currently active. Most models will have normals, Freesurfer surface models can have multiple scalar overlay files associated with them (e.g. lh.sulc, lh.curv).
+ - **Active Scalar Range**: Displays the numerical range spanned by the currently active scalar array (used when the Data scalar range type is selected).
+ - **Color Table**: Select a color look up table used to map the scalar overlay's values (usually in the range of 0.0 to 1.0) to colors. There are built in color maps that can be browsed in the Colors module. The most commonly used color map for FreeSurfer scalar overlays is the GreenRed one. Legacy color maps from Slicer2 include Grey, Iron, Rainbow, etc. Those color maps with "labels" in their names are usually discrete and don't work well for the continuous scalar overlay ranges.
+ - **Scalar Range Type**: Select which scalar range to use: range in the scalar data array, Color Table range, Display Scalar Range, range of the data type of the scalar array.
+ - **Display Scalar Range**: Set the scalar range on the model's display node.
+ - **Clipping Panel**:
+ - **Clipping**: Control the clipping properties for this surface model
+ - **Clipping Type**: When more than one slice plane is used, this option controls if it's the union or intersection of the positive and/or negative spaces that is used to clip the model. The parts of the model inside the selected space is kept, parts outside of the selection are clipped away.
+ - **Red Slice Clipping**: Use the positive or negative space defined by the Red slice plane to clip the model. Positive side is toward the Superior, negative is toward the Inferior. Keeps the part of the model in the selected space, clips away the rest.
+ - **Yellow Slice Clipping**: Use the positive or negative space defined by the Yellow slice plane to clip the model. Positive side is toward the Right, negative is toward the Left. Keeps the part of the model in the selected space, clips away the rest.
+ - **Green Slice Clipping**: Use the positive or negative space defined by the Green slice plane to clip the model. Positive side is toward the Anterior, negative is toward the Posterior. Keeps the part of the model in the selected space, clips away the rest.
+
+## Contributors
+
+Julien Finet (Kitware), Alex Yarmarkovich (Isomics), Nicole Aucoin (SPL, BWH)
+
+## Acknowledgements
+
+This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149. This work is partially supported by the Air Force Research Laboratories (AFRL).
diff --git a/Docs/user_guide/modules/modeltolabelmap.md b/Docs/user_guide/modules/modeltolabelmap.md
new file mode 100644
index 00000000000..391c245b07b
--- /dev/null
+++ b/Docs/user_guide/modules/modeltolabelmap.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ModelToLabelMapOverview.md
+```
+
+```{include} ../../_moduledescriptions/ModelToLabelMapParameters.md
+```
diff --git a/Docs/user_guide/modules/multiplyscalarvolumes.md b/Docs/user_guide/modules/multiplyscalarvolumes.md
new file mode 100644
index 00000000000..561df9700e0
--- /dev/null
+++ b/Docs/user_guide/modules/multiplyscalarvolumes.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/MultiplyScalarVolumesOverview.md
+```
+
+```{include} ../../_moduledescriptions/MultiplyScalarVolumesParameters.md
+```
diff --git a/Docs/user_guide/modules/n4itkbiasfieldcorrection.md b/Docs/user_guide/modules/n4itkbiasfieldcorrection.md
new file mode 100644
index 00000000000..73a2861d426
--- /dev/null
+++ b/Docs/user_guide/modules/n4itkbiasfieldcorrection.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/N4ITKBiasFieldCorrectionOverview.md
+```
+
+```{include} ../../_moduledescriptions/N4ITKBiasFieldCorrectionParameters.md
+```
diff --git a/Docs/user_guide/modules/orientscalarvolume.md b/Docs/user_guide/modules/orientscalarvolume.md
new file mode 100644
index 00000000000..62807b113ee
--- /dev/null
+++ b/Docs/user_guide/modules/orientscalarvolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/OrientScalarVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/OrientScalarVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/performmetrictest.md b/Docs/user_guide/modules/performmetrictest.md
new file mode 100644
index 00000000000..11bb8e262ac
--- /dev/null
+++ b/Docs/user_guide/modules/performmetrictest.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/PerformMetricTestOverview.md
+```
+
+```{include} ../../_moduledescriptions/PerformMetricTestParameters.md
+```
diff --git a/Docs/user_guide/modules/petstandarduptakevaluecomputation.md b/Docs/user_guide/modules/petstandarduptakevaluecomputation.md
new file mode 100644
index 00000000000..8ada1f9f089
--- /dev/null
+++ b/Docs/user_guide/modules/petstandarduptakevaluecomputation.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/PETStandardUptakeValueComputationOverview.md
+```
+
+```{include} ../../_moduledescriptions/PETStandardUptakeValueComputationParameters.md
+```
diff --git a/Docs/user_guide/modules/probevolumewithmodel.md b/Docs/user_guide/modules/probevolumewithmodel.md
new file mode 100644
index 00000000000..b863faa1ba3
--- /dev/null
+++ b/Docs/user_guide/modules/probevolumewithmodel.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ProbeVolumeWithModelOverview.md
+```
+
+```{include} ../../_moduledescriptions/ProbeVolumeWithModelParameters.md
+```
diff --git a/Docs/user_guide/modules/reformat.md b/Docs/user_guide/modules/reformat.md
new file mode 100644
index 00000000000..e1393db801b
--- /dev/null
+++ b/Docs/user_guide/modules/reformat.md
@@ -0,0 +1,31 @@
+# Reformat
+
+## Overview
+
+This module is used for changing the slice properties.
+
+## Panels and their use
+
+- **Slice**: Select the slice to operate on -- the module's Display interface will change to show slice parameters.
+
+- **Display - Edit**: Information about the selected slice. Fields can be edited to precisely set values to the slice.
+ - **Offset**: See and Set the current distance from the origin to the slice plane
+ - **Origin**: The location of the center of the slice. It is also related to the reformat widget origin associated to the selected slice.
+ - **Center**: This button will adjust the slice Origin so that the entire slice is centered around 0,0,0 in the volume space.
+ - **Normal**: Allow to set accurately the normal of the active slice.
+ - **Reset**: Reset the slice to transformation to the corresponding orientation -- The orientation could be either "Axial, "Sagittal", "Coronal" or "Reformat".
+ - **Normal X**: Set the normal to a x axis.
+ - **Normal Y**: Set the normal to a y axis.
+ - **Normal Z**: Set the normal to a z axis.
+ - **Normal to camera**: Set slice normal to the camera.
+ - **LR**: Rotate the slice on the X axis
+ - **PA**: Rotate the slice on the Y axis
+ - **IS**: Rotate the slice on the Z axis
+
+## Contributors
+
+Michael Jeulin-Lagarrigue (Kitware)
+
+## Acknowledgements
+
+This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
diff --git a/Docs/user_guide/modules/resampledtivolume.md b/Docs/user_guide/modules/resampledtivolume.md
new file mode 100644
index 00000000000..58b7807a8af
--- /dev/null
+++ b/Docs/user_guide/modules/resampledtivolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ResampleDTIVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/ResampleDTIVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/resamplescalarvectordwivolume.md b/Docs/user_guide/modules/resamplescalarvectordwivolume.md
new file mode 100644
index 00000000000..c12c3df8e0e
--- /dev/null
+++ b/Docs/user_guide/modules/resamplescalarvectordwivolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ResampleScalarVectorDWIVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/ResampleScalarVectorDWIVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/resamplescalarvolume.md b/Docs/user_guide/modules/resamplescalarvolume.md
new file mode 100644
index 00000000000..5660f7efe7a
--- /dev/null
+++ b/Docs/user_guide/modules/resamplescalarvolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ResampleScalarVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/ResampleScalarVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/robuststatisticssegmenter.md b/Docs/user_guide/modules/robuststatisticssegmenter.md
new file mode 100644
index 00000000000..4f37de77e12
--- /dev/null
+++ b/Docs/user_guide/modules/robuststatisticssegmenter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/RobustStatisticsSegmenterOverview.md
+```
+
+```{include} ../../_moduledescriptions/RobustStatisticsSegmenterParameters.md
+```
diff --git a/Docs/user_guide/modules/sceneviews.md b/Docs/user_guide/modules/sceneviews.md
new file mode 100644
index 00000000000..ec029ef696e
--- /dev/null
+++ b/Docs/user_guide/modules/sceneviews.md
@@ -0,0 +1,23 @@
+# Scene Views
+
+## Overview
+
+Scene views are a convenience tool for organizing multiple 'live views' of the data in your scene.\n\nYou can create any number of views and control parameters such as the 3D view, model visibility, window layout, and other parameters.\n\nThis can be used to set up a series of predefined starting points for looking at portions of your data in detail.\n\nFor example, you may have one overview scene which shows an external view of the body along with interior views with the skin surface turned off and slice planes visible to highlight a tumor location.
+
+## Panels and their use
+
+- **Scene Views**: Create, edit, restore scene views
+ - **Create and Edit**: Create and delete scene views, move them up and down
+ - **Create a 3D Slicer Scene View**: Click on the camera button to create a new scene view
+ - **Move selected view up**: Move the highlighted scene view in the table up one row
+ - **Move selected view down**: Move the highlighted scene view in the table down one row
+ - **Delete selected view**: Click on the garbage can button to delete the highlighted scene view
+ - **Table of Scene Views**: A table showing the scene views, one per line
+
+## Contributors
+
+Nicole Aucoin (SPL, BWH), Wendy Plesniak (SPL, BWH), Daniel Haehn (UPenn), Kilian Pohl (UPenn)
+
+## Acknowledgements
+
+This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
diff --git a/Docs/user_guide/modules/screencapture.md b/Docs/user_guide/modules/screencapture.md
new file mode 100644
index 00000000000..1e4087fb046
--- /dev/null
+++ b/Docs/user_guide/modules/screencapture.md
@@ -0,0 +1,101 @@
+# Screen Capture
+
+## Overview
+
+This module is for creating videos, image sequences, or lightbox image from 3D and slice view contents.
+
+## Panels and their use
+
+### Input
+
+- **Master view:** This view will be changed during the animation (rotated, sweeped, etc.).
+- **Capture all views:** If enabled then all the view in the view layout will be captured. If disabled then only the master view will be captured. By capturing all views, it is possible to see the animated view (such as a moving slice) in 3D and in other slice views.
+- **Animation mode:** specifies how the master view will be modified during capture.
+ - **3D rotation:** Acquire video of a rotating 3D view. For smooth repeated display of a 360-degree rotation it is recommended to choose 31 or 61 as "Number of images".
+ - **slice sweep:** Acquire video while going through selected range of image frames (for slice viewer only).
+ - **slice fade:** Acquire video while fading between the foreground and background image (for slice viewer only).).
+ - **sequence:** sequence: Acquire video while going through items in the selected sequence browser.
+
+ ````{list-table}
+ ---
+ header-rows: 1
+ ---
+ * - 3D rotation
+ - Slice sweep
+ - Slice fade
+ - Sequence
+ * - 
+ - 
+ - 
+ - 
+ ````
+
+### Output
+
+- **Output type:**
+ - **image series:** Save screnshots as separate image files (in jpg or png file format).
+ - **video:** Save animation as a compressed video file. Requires installation of *ffmpeg* video encoder.
+ - **lightbox image:** Save screnshots as separate jpg pr png files.
+
+ 
+
+- **Number of images:** Defines how many frames are generated in the specified range. Higher number results in smoother animation but larger video file. If **single** option is enabled then a single image is captured (and counter in the filename is automatically incremented, therefore it can be used to acquire many screenshots manually).
+- **Output directory:** Output image or video will be saved in this directory.
+- **Output file name:** Output file name for video and lightbox.
+- **Video format:**
+ - **H264:** modern compressed video format, compatible with current video players.
+ - **H264 (high-quality):** H264 with higher-quality setting, results in larger file.
+ - **MPEG4:** commonly used compressed video format, mostly compatible with older video players.
+ - **MPEG4 (high-quality):** MPEG4 with higher-quality setting, results in larger file.
+ - **Animated GIF:** file format that provides lower quality images and large files, but it is more compatible with some legacy image viewers and websites.
+ - **Animated GIF (grayscale):** animated GIF, saved as a grayscale image, resulting in slightly smaller files.
+- **Video length:** Set total replay time of the video by adjusting the video frame rate.
+- **Video frame rate:** Set replay frame rate of the video by adjusting video length.
+
+### Advanced
+
+- **Forward-backward:** After generating images by animating in forward direction, adds animation in reverse direction as well. It removes the "jump" at the end of the animation when it is played repeatedly in a loop.
+- **Repeat:** Repeat the entire animation the specified number of times. It is useful for making animations longer (e.g., for uploading to YouTube).
+- **ffmpeg executable:** Path to ffmpeg executable. Requires download and installation of [ffmpeg](##setting-up-ffmpeg). Only needed if video export is selected.
+- **Video extra options:** Options for ffmpeg that controls video format and quality. Only needed if video export is selected.
+ - These parameters are already specified by the module and therefore should not be included in the extra options: `-i (input files) -y (overwrite without asking) -r (frame rate) -start_number`.
+ - Information about available options:
+ - https://trac.ffmpeg.org/wiki/Encode/H.264
+ - https://trac.ffmpeg.org/wiki/Encode/MPEG-4
+ - https://trac.ffmpeg.org/wiki/Encode/YouTube
+ - https://ffmpeg.org/ffmpeg-all.html
+- **Image file name pattern:** Defines image file naming pattern. `%05d` will be replaced by the image number (5 numbers, padded with zeros). This is only used if image series output is selected.
+- **Lightbox image columns:** Number of columns in the generated lighbox image.
+- **Maximum number of images:** Specifies the maximum range of the "number of images" slider. Useful for creating very long animations.
+- **Output volume node:** If a single image output is selected then the output can be saved into the selected volume node.
+- **View controllers:** Show view controllers. If unchecked then view controllers will be temporarily hidden during screen capture.
+- **Transparent background:** If checked then images will be captured with transparent background.
+- **Watermark image:** Adds a watermark image to the captured images.
+
+ 
+
+ - **Position:** Position of the watermark image over the captured image.
+ - **Size:** Watermark image size, as percentage of the original size.
+ - **Opacity:** Watermark image opacity, larger value makes the watermark more visible (less transparent).
+
+## Related modules
+
+- [Animator](https://github.com/SlicerMorph/SlicerMorph/tree/master/Docs/Animator#readme) module in [SlicerMorph extension](https://slicermorph.github.io/) allows creating more complex animations, such as cutting through a volume (by changing region of interest), adjusting volume rendering transfer functions, or exploding view of complex model assembly.
+- [Scene Views](sceneviews.md) module can create snapshot of the entire scene content along with a screenshot, which are all saved in the scene.
+- [Annotations](annotations.md) module can create simple screenshots that are saved in the scene. Annotations module is being phased out and Screen Capture module's "Output volume node" feature can be used for saving screenshots in the scene.
+
+## Information for developers
+
+- This is a Python scripted module. Source code is available [here](https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/ScreenCapture/ScreenCapture.py).
+- Examples of capturing images are available in the [Script Repository](../../developer_guide/script_repository.md#screen-capture)
+
+## Contributors
+
+Andras Lasso (PerkLab, Queen's University)
+
+## Acknowledgements
+
+This work was was funded by Cancer Care Ontario and the Ontario Consortium for Adaptive Interventions in Radiation Oncology (OCAIRO)
+
+
+
diff --git a/Docs/user_guide/modules/segmentations.md b/Docs/user_guide/modules/segmentations.md
index a6eaf6c7d11..47924351c04 100644
--- a/Docs/user_guide/modules/segmentations.md
+++ b/Docs/user_guide/modules/segmentations.md
@@ -96,7 +96,7 @@ For exporting segmentation as NRRD or NIFTI file for external software that uses
- Set additional options (destination folder, compression, etc.) as needed
- Click `Export`
-Labelmap volumes can be created in any other formats by [exporting segmentation to labelmap volume](segmentations.html#export-segmentation-to-labelmap-volume) then in application menu, choose `File` / `Save`.
+Labelmap volumes can be created in any other formats by [exporting segmentation to labelmap volume](segmentations.md#export-segmentation-to-labelmap-volume) then in application menu, choose `File` / `Save`.
### Create new representation in segmentation (conversion)
@@ -120,13 +120,13 @@ See Script repository's [Segmentations section](https://www.slicer.org/wiki/Docu
- The master representation is used when exporting into DICOM, therefore you need to select a master volume, create binary labelmap representation and set it as master
- DICOM Segmentation Object export if `QuantitativeReporting` extension is installed
- Legacy DICOM RT structure set export is available if `SlicerRT` extension is installed. RT structure sets are not recommended for storing segmentations, as they cannot store arbitrarily complex 3D shapes.
-- Follow [these instructions](dicom.html#export-data-from-the-scene-to-dicom-database) for exporting data in DICOM format.
+- Follow [these instructions](dicom.md#export-data-from-the-scene-to-dicom-database) for exporting data in DICOM format.
## Panels and their use
- Segments table
- Add/remove segments
- - Edit selected: takes user to [Segment Editor](SegmentEditor) module
+ - Edit selected: takes user to [Segment Editor](segmenteditor.md) module
- Set visibility and per-segment display settings, opacity, color, segment name
- Display
- Segmentations-wide display settings (not per-segment!): visibility, opacity (will be multiplied with per-segment opacity for display)
diff --git a/Docs/user_guide/modules/segmenteditor.md b/Docs/user_guide/modules/segmenteditor.md
index 7e60ae55996..bc9e964d6cb 100644
--- a/Docs/user_guide/modules/segmenteditor.md
+++ b/Docs/user_guide/modules/segmenteditor.md
@@ -2,7 +2,7 @@
This is a module is for specifying segments (structures of interest) in 2D/3D/4D images. Some of the tools mimic a painting interface like photoshop or gimp, but work on 3D arrays of voxels rather than on 2D pixels. The module offers editing of overlapping segments, display in both 2D and 3D views, fine-grained visualization options, editing in 3D views, create segmentation by interpolating or extrapolating segmentation on a few slices, editing on slices in any orientation.
-Segment Editor does not edit labelmap volumes or models, but segmentations can be easily converted to/from labelmap volumes and models using the Import/Export section of [Segmentations](Segmentations) module.
+Segment Editor does not edit labelmap volumes or models, but segmentations can be easily converted to/from labelmap volumes and models using the Import/Export section of [Segmentations](segmentations.md) module.

@@ -255,7 +255,7 @@ Authors:
## Acknowledgements
This module is partly funded by an Applied Cancer Research Unit of Cancer Care Ontario with funds provided by the Ministry of Health and Long-Term Care and the Ontario Consortium for Adaptive Interventions in Radiation Oncology (OCAIRO) to provide free, open-source toolset for radiotherapy and related image-guided interventions.
-The work is part of the `National Alliance for Medical Image Computing `_ (NA-MIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
+The work is part of the [National Alliance for Medical Image Computing](http://www.na-mic.org/) (NA-MIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.


diff --git a/Docs/user_guide/modules/segmentstatistics.md b/Docs/user_guide/modules/segmentstatistics.md
index 82851f19c62..dac7bbfa3a9 100644
--- a/Docs/user_guide/modules/segmentstatistics.md
+++ b/Docs/user_guide/modules/segmentstatistics.md
@@ -43,8 +43,8 @@ Labelmap statistics are calculated using the binary labelmap representation of t
## Information for developers
-See examples for calculating statistics from your own modules in the `Slicer script repository `_.
-Additional plugins for computation of other statistical measurements may be registered by subclassing `SegmentStatisticsPluginBase.py `_, and registering the plugin with SegmentStatisticsLogic.
+See examples for calculating statistics from your own modules in the [Slicer script repository](https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Quantifying_segments).
+Additional plugins for computation of other statistical measurements may be registered by subclassing [SegmentStatisticsPluginBase.py](https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/SegmentStatistics/SegmentStatisticsPlugins/SegmentStatisticsPluginBase.py), and registering the plugin with SegmentStatisticsLogic.
## Contributors
@@ -58,7 +58,7 @@ Authors:
## Acknowledgements
This module is partly funded by an Applied Cancer Research Unit of Cancer Care Ontario with funds provided by the Ministry of Health and Long-Term Care and the Ontario Consortium for Adaptive Interventions in Radiation Oncology (OCAIRO) to provide free, open-source toolset for radiotherapy and related image-guided interventions.
-The work is part of the `National Alliance for Medical Image Computing `_ (NA-MIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
+The work is part of the [National Alliance for Medical Image Computing](http://www.na-mic.org/) (NA-MIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.


diff --git a/Docs/user_guide/modules/simpleregiongrowingsegmentation.md b/Docs/user_guide/modules/simpleregiongrowingsegmentation.md
new file mode 100644
index 00000000000..5f8196e488c
--- /dev/null
+++ b/Docs/user_guide/modules/simpleregiongrowingsegmentation.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/SimpleRegionGrowingSegmentationOverview.md
+```
+
+```{include} ../../_moduledescriptions/SimpleRegionGrowingSegmentationParameters.md
+```
diff --git a/Docs/user_guide/modules/subtractscalarvolumes.md b/Docs/user_guide/modules/subtractscalarvolumes.md
new file mode 100644
index 00000000000..4e1d1ba3cce
--- /dev/null
+++ b/Docs/user_guide/modules/subtractscalarvolumes.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/SubtractScalarVolumesOverview.md
+```
+
+```{include} ../../_moduledescriptions/SubtractScalarVolumesParameters.md
+```
diff --git a/Docs/user_guide/modules/tables.md b/Docs/user_guide/modules/tables.md
new file mode 100644
index 00000000000..808f61598f8
--- /dev/null
+++ b/Docs/user_guide/modules/tables.md
@@ -0,0 +1,29 @@
+# Tables
+
+## Overview
+
+The Tables module allows displaying and editing of spreadsheets.
+
+## Panels and their use
+
+- **Input**:
+ - **Active table**: Select the table node to edit/view.
+ - **Lock button**: Allows locking the table node to read-only. Only applies to the user interface. The node can eb still modified programmatically.
+
+- **Edit**:
+ - **Copy button**: Copy contents of selected cells to clipboard.
+ - **Paste button**: Paste contents of clipboard at the current position. Data that does not fit into the table is ignored.
+ - **Add column button**: Add an empty column at the end of the table.
+ - **Delete column button**: Delete all selected columns. If any cell is selected in a column the entire column will be removed.
+ - **Lock first column button**: Use the first column as row header. Header cannot be edited and is not copied to clipboard but still saved to file.
+ - **Add row button**: Add an empty row at the end of the table.
+ - **Delete row button**: Delete all selected rows. If any cell is selected in a row the entire row will be removed.
+ - **Lock first row button**: Use the first row as column header. Header cannot be edited and is not copied to clipboard but still saved to file.
+
+## Contributors
+
+Andras Lasso (PerkLab), Kevin Wang (PMH)
+
+## Acknowledgements
+
+This work was was partially funded by OCAIRO, the Applied Cancer Research Unit program of Cancer Care Ontario, and Department of Anesthesia and Critical Care Medicine, Children’s Hospital of Philadelphia.
diff --git a/Docs/user_guide/modules/thresholdscalarvolume.md b/Docs/user_guide/modules/thresholdscalarvolume.md
new file mode 100644
index 00000000000..7e1d90692a9
--- /dev/null
+++ b/Docs/user_guide/modules/thresholdscalarvolume.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/ThresholdScalarVolumeOverview.md
+```
+
+```{include} ../../_moduledescriptions/ThresholdScalarVolumeParameters.md
+```
diff --git a/Docs/user_guide/modules/transforms.md b/Docs/user_guide/modules/transforms.md
index 6da97cf155b..3deef29c1b0 100644
--- a/Docs/user_guide/modules/transforms.md
+++ b/Docs/user_guide/modules/transforms.md
@@ -52,11 +52,11 @@ If non-linear transform is hardened on a volume then the volume is resampled usi
- Change translation/rotation:
- linear transforms can be edited using translation and rotation sliders Transforms module's Edit section. "Translation in global or local reference frame" button controls if translation is performed in the parent coordinate system or the rotated coordinate system.
- translation and rotation of a linear transform can be interactively edited in 3D by enabling "Visible in 3D view" in Transform's module Display / Interaction section. See this **[short demonstration video](https://youtu.be/bbikx7Edv4g)**.
-- Edit warping transform: to specify/edit a warping transform that translates a set of points to specified positions, you can use [semi-automatic registration methods](../registration.html#semi-automatic-registration)
+- Edit warping transform: to specify/edit a warping transform that translates a set of points to specified positions, you can use [semi-automatic registration methods](../registration.md#semi-automatic-registration)
### Compute transform
-Transforms are usually computed using [spatial registration tools](../registration.html).
+Transforms are usually computed using [spatial registration tools](../registration.md).
### Save transform
@@ -78,7 +78,7 @@ A quick way to import a linear transform from another software or from text file
### Visualize transform
-Transforms can be visualized in both 2D and 3D views, as glyphs representing the displacement vectors as arrows, cones, or spheres; regular grids that are deformed by the transform; or contours that represent lines or surfaces where the displacement magnitude has a specific value. See documentation of [Display section](transforms.html#display] for details.
+Transforms can be visualized in both 2D and 3D views, as glyphs representing the displacement vectors as arrows, cones, or spheres; regular grids that are deformed by the transform; or contours that represent lines or surfaces where the displacement magnitude has a specific value. See documentation of [Display section](transforms.md#display] for details.
## Panels and their use
diff --git a/Docs/user_guide/modules/viewcontrollers.md b/Docs/user_guide/modules/viewcontrollers.md
new file mode 100644
index 00000000000..a723d953dc7
--- /dev/null
+++ b/Docs/user_guide/modules/viewcontrollers.md
@@ -0,0 +1,21 @@
+# View Controllers
+
+## Overview
+
+The View Controllers module provides access to nodes controlling multiple views within a single panel. A view is a display of data packed within layout. Slice Views and 3D Views are two types of views that can appear in a layout and whose controls can also be accessed from the View Controllers module. Each type of view has a separate section of the View Controllers panel. For example, the Slice Controllers are grouped together, followed by the 3D Controllers. An extra panel allows an alternative control over a Slice View.
+
+## Panels and their use
+
+- **Slice Controllers**: Slice Controllers for each of the Slice Views visible in the current layout. This is the same controller that is accessible from the bar at the top of a Slice View. It provides access to select the content (foreground, background, label) as well as control reformation, linking, visibility in the 3D view, lightbox, etc.
+
+- **3D View Controllers**: Controllers for each 3D View. This is the same controller that is accessible from the bar at the top of a 3D View. It provides access to the view direction, zooming, spinning, rocking, etc.
+
+- **Slice Information**: An alternative panel to control geometric parameters of a Slice View (field of view, lightbox, slice spacing).
+
+## Contributors
+
+Wendy Plesniak (SPL, BWH), Jim Miller (GE), Steve Pieper (Isomics), Ron Kikinis (SPL, BWH), Jean-Christophe Fillion-Robin (Kitware)
+
+## Acknowledgements
+
+This work is part of the National Alliance for Medical Image Computing (NAMIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.
diff --git a/Docs/user_guide/modules/volumerendering.md b/Docs/user_guide/modules/volumerendering.md
index 5b7af50f38d..c9f56f92295 100644
--- a/Docs/user_guide/modules/volumerendering.md
+++ b/Docs/user_guide/modules/volumerendering.md
@@ -44,7 +44,7 @@ Option B:
## Limitations
-- Only single-component scalar volumes can be used for volume rendering. [Vector to Scalar Volume](Module_VectorToScalarVolume) module can convert vector volume to scalar volume.
+- Only single-component scalar volumes can be used for volume rendering. [Vector to Scalar Volume](vectortoscalarvolume.md) module can convert vector volume to scalar volume.
- To render multiple overlapping volumes, select "VTK Multi-Volume" rendering in "Display" section. Currently, no cropping can be applied in this mode.
- To reduce staircase artifacts during rendering, choose enable "Surface smoothing" in Advanced/Techniques/Advanced rendering properties section, or choose "Normal" or "Maximum" as quality.
diff --git a/Docs/user_guide/modules/volumes.md b/Docs/user_guide/modules/volumes.md
index d3c7a84aefa..2fadf50fa06 100644
--- a/Docs/user_guide/modules/volumes.md
+++ b/Docs/user_guide/modules/volumes.md
@@ -27,17 +27,17 @@ Volumes module handles a 2D image as a single-slice 3D image. 4D volumes are rep
### Display volume
-Slice views: After loading a volume, it is displayed in slice views by default. If multiple volumes are loaded, `Data` module can be used to choose which one is displayed. [Slice view controls](../user_interface.html#slice-view) allow further customization of which volume is displayed in which view and how.
+Slice views: After loading a volume, it is displayed in slice views by default. If multiple volumes are loaded, `Data` module can be used to choose which one is displayed. [Slice view controls](../user_interface.md#slice-view) allow further customization of which volume is displayed in which view and how.
3D views: Volumes can be displayed in 3D views using [Volume rendering](volumerendering) module. If structures of interest cannot be distinguished from surrounding regions then it may be necessary to segment the image using [Segment Editor](segmenteditor) module and click `Show 3D` button.
### Overlay two volumes
-- [Load](data_loading_and_saving) two volumes
+- [Load](../data_loading_and_saving.md) two volumes
- Go to `Data` module
- Left-click on the "eye" icon of one of the volumes to show it as background volume
- Right-click on "eye" icon of the other volume and choose "Show in slice views as foreground"
-- Adjust transparency of the foreground volume using the vertical slider in [slice view controls](../user_interface.html#slice-view).
+- Adjust transparency of the foreground volume using the vertical slider in [slice view controls](../user_interface.md#slice-view).
Click `link` button to make all changes applied to all slice views (in the same view group)
### Load image file as labelmap volume
diff --git a/Docs/user_guide/modules/votingbinaryholefillingimagefilter.md b/Docs/user_guide/modules/votingbinaryholefillingimagefilter.md
new file mode 100644
index 00000000000..ca27b4720ef
--- /dev/null
+++ b/Docs/user_guide/modules/votingbinaryholefillingimagefilter.md
@@ -0,0 +1,5 @@
+```{include} ../../_moduledescriptions/VotingBinaryHoleFillingImageFilterOverview.md
+```
+
+```{include} ../../_moduledescriptions/VotingBinaryHoleFillingImageFilterParameters.md
+```
diff --git a/Modules/CLI/ACPCTransform/ACPCTransform.xml b/Modules/CLI/ACPCTransform/ACPCTransform.xml
index e626e276337..de7f69b0ec2 100644
--- a/Modules/CLI/ACPCTransform/ACPCTransform.xml
+++ b/Modules/CLI/ACPCTransform/ACPCTransform.xml
@@ -3,7 +3,7 @@
Registration.SpecializedACPC Transform1
- Calculate a transformation that aligns brain images to standard orientation based on anatomical landmarks.
The ACPC line extends between two points, one at the anterior commissure and one at the posterior commissure. The resulting transform will bring the line connecting the two points horizontal to the AP axis.
The midline is a series of points (at least 3) defining the division between the hemispheres of the brain (the mid sagittal plane). The resulting transform will result in the output volume having the mid sagittal plane lined up with the AS plane.
Use the Filtering module Resample Scalar/Vector/DWI Volume to apply the transformation to a volume.
]]>
+ Calculate a transformation that aligns brain images to Talairach coordinate system (also known as stereotaxic or ACPC coordinate system) based on anatomical landmarks.
The ACPC line extends between two points, one at the anterior commissure and one at the posterior commissure. The resulting transform will bring the line connecting the two points horizontal to the AP axis.
The midline is a series of points (at least 3) defining the division between the hemispheres of the brain (the mid sagittal plane). The resulting transform will result in the output volume having the mid sagittal plane lined up with the AS plane.
Use Resample Scalar/Vector/DWI Volume to apply the transformation to a volume.
The multi views and cameras framework is available from the "Camera" modules. This module displays two pull-down menus. The first one, "View", lists all available views and lets the user create new 3D views. The second one, "Camera", lists all available cameras, and lets the user create new cameras.
Only one camera is used by a view at a time. When a view is selected from the pull-down, the camera it is currently using is automatically selected in the second pull-down.
Create a new camera.
In the "Camera" pull-down menu, select "Create New Camera": a new camera node is created (most likely named "Camera1", as opposed to the default "Camera" node), and automatically assigned to the currently selected view node (named "View" by default). Try interacting with the 3D view.
Assign a camera to a view.
Select any camera node from the "Camera" pull-down menu: the camera is assigned to the currently selected view. For example, try selecting back the "Camera" node if you have created a "Camera1" node in the previous step, and you should notice the 3D view on the right update itself to reflect the different point of view. Note: a camera can not be shared between two views: selecting a camera already used by a view will effectively swap the cameras between the two views.
Create a new view
Select the "Tabbed 3D Layout" from the layout button in the toolbar. In the "View" pull-down menu, select "Create New View": a new view node is created (most likely named "View1", as opposed to the default "View" node), and displayed on the right under a new tab. You can select which view to display by clicking on the corresponding tab in the "Tabbed 3D Layout". Interacting in that view will automatically mark it as "active"; there can only be one "active" view at a time in Slicer, and it always feature a thin dark blue border. Since a view can not exist without a camera, a new camera node is automatically created and assigned to this new view (most likely named "Camera2" if you have created "Camera1" in the previous steps). At this point, you can assign any of the previously created cameras to this new view (say, the "Camera" or "Camera1" nodes).