https://www.slicer.org/w/api.php?action=feedcontributions&user=Pieper&feedformat=atomSlicer Wiki - User contributions [en]2024-03-29T08:50:55ZUser contributionsMediaWiki 1.33.0https://www.slicer.org/w/index.php?title=Documentation/4.10/Modules/SimpleFilters&diff=63722Documentation/4.10/Modules/SimpleFilters2021-07-25T14:03:01Z<p>Pieper: Add SimpleFilters code link to release</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
This work is supported by NLM, and the Slicer Community. This work is partially supported by...<br><br />
Author: Bradley Lowekamp, Steve Pieper, Jean-Cristophe Fillion Robin<br><br />
Contact: Bradley Lowekamp <email>blowekamp@mail.nih.gov</email> <br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-logo-gallery<br />
|{{collaborator|logo|nlm}}|{{collaborator|longname|nlm}}<br />
|{{collaborator|logo|namic}}|{{collaborator|longname|namic}}<br />
|Image:Itk-logo.jpg|Itk<br />
|{{collaborator|logo|kitware}}|{{collaborator|longname|kitware}}<br />
}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
<br />
The SimpleFilters module provides a simple interface to hundreds of basic and advanced filters from ITK.<br />
<br />
The algorithms available include binary morphology, grayscale morphology, denoising, thresholding, image intensity manipulation, region growing, FFT, and many advanced algorithms.<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
<br />
<br />
[[Image:SimpleFilters_pannel.png|thumb|300px|Panel for SimpleFilters]]<br />
<br />
The panned for SimpleFilters is modeled after the Command Line Modules.<br />
<br />
At the top the '''Filters''' section enable the selection of one of the hundred of filters available. The '''Search''' text box is use quickly find a filter based on it's name.<br />
<br />
The top of the '''Parameters''' section dynamically changes based of the Filter selected above, it presents a list of input filter and parameters which the filter needs. Along with the output image for the filter. The behavior of the output is modeled after the CLIs.<br />
<br />
At the bottom it features an '''Apply''' to run the filter, an '''Abort''' button to cancel a actively running filter, and a '''Restore Defaults''' to revert the parameters to their initial settings. Above that is the status and progress if a filter is active.<br />
<br />
<br />
==Tips==<br />
<br />
The output image for the filter can be a gray scale image or a label map. The "LabelMap" check box should be selected to ensure the image is displayed correctly with in Slicer.<br />
<br />
Most filters which take more than one image as input expect that the filters be the same pixel type, and the images occupy the same physical space.<br />
<br />
<br />
<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
<br />
<br />
<gallery><br />
Image:SimpleFilters1.png|MultiOtsuThreshold to LabelMap<br />
Image:SimpleFilters2.png|LaplacianRecursiveGaussian<br />
Image:SimpleFilters3.png|GrayscaleMorphologicalClosing<br />
</gallery><br />
<br />
There are a large number of filters available in the SimpleFilters module. The following is a table of the filter name, a brief description, and a link to the underlaying ITK filter. The ITK link contains additional detail about using the filter.<br />
<br />
<!-- BEGIN TABLE --><br />
{| border="1" style="border-collapse:collapse;"<br />
|-<br />
!Filter Name<br />
!Brief Description<br />
!ITK Class<br />
|-<br />
!AbsImageFilter<br />
!Computes the absolute value of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AbsImageFilter.html AbsImageFilter]<br />
|-<br />
!AbsoluteValueDifferenceImageFilter<br />
!Implements pixel-wise the computation of absolute value difference.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AbsoluteValueDifferenceImageFilter.html AbsoluteValueDifferenceImageFilter]<br />
|-<br />
!AcosImageFilter<br />
!Computes the inverse cosine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AcosImageFilter.html AcosImageFilter]<br />
|-<br />
!AdaptiveHistogramEqualizationImageFilter<br />
!Power Law Adaptive Histogram Equalization.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AdaptiveHistogramEqualizationImageFilter.html AdaptiveHistogramEqualizationImageFilter]<br />
|-<br />
!AddImageFilter<br />
!Pixel-wise addition of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AddImageFilter.html AddImageFilter]<br />
|-<br />
!AndImageFilter<br />
!Implements the AND bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AndImageFilter.html AndImageFilter]<br />
|-<br />
!AntiAliasBinaryImageFilter<br />
!A method for estimation of a surface from a binary volume.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AntiAliasBinaryImageFilter.html AntiAliasBinaryImageFilter]<br />
|-<br />
!ApproximateSignedDistanceMapImageFilter<br />
!Create a map of the approximate signed distance from the boundaries of a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ApproximateSignedDistanceMapImageFilter.html ApproximateSignedDistanceMapImageFilter]<br />
|-<br />
!AsinImageFilter<br />
!Computes the sine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AsinImageFilter.html AsinImageFilter]<br />
|-<br />
!Atan2ImageFilter<br />
!Computes two argument inverse tangent.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1Atan2ImageFilter.html Atan2ImageFilter]<br />
|-<br />
!AtanImageFilter<br />
!Computes the one-argument inverse tangent of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AtanImageFilter.html AtanImageFilter]<br />
|-<br />
!BilateralImageFilter<br />
!Blurs an image while preserving edges.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BilateralImageFilter.html BilateralImageFilter]<br />
|-<br />
!BinShrinkImageFilter<br />
!Reduce the size of an image by an integer factor in each dimension while performing averaging of an input neighborhood.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinShrinkImageFilter.html BinShrinkImageFilter]<br />
|-<br />
!BinaryClosingByReconstructionImageFilter<br />
!binary closing by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryClosingByReconstructionImageFilter.html BinaryClosingByReconstructionImageFilter]<br />
|-<br />
!BinaryContourImageFilter<br />
!Labels the pixels on the border of the objects in a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryContourImageFilter.html BinaryContourImageFilter]<br />
|-<br />
!BinaryDilateImageFilter<br />
!Fast binary dilation.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryDilateImageFilter.html BinaryDilateImageFilter]<br />
|-<br />
!BinaryErodeImageFilter<br />
!Fast binary erosion.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryErodeImageFilter.html BinaryErodeImageFilter]<br />
|-<br />
!BinaryFillholeImageFilter<br />
!Remove holes not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFillholeImageFilter.html BinaryFillholeImageFilter]<br />
|-<br />
!BinaryGrindPeakImageFilter<br />
!Remove the objects not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryGrindPeakImageFilter.html BinaryGrindPeakImageFilter]<br />
|-<br />
!BinaryMagnitudeImageFilter<br />
!Computes the square root of the sum of squares of corresponding input pixels.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMagnitudeImageFilter.html BinaryMagnitudeImageFilter]<br />
|-<br />
!BinaryMedianImageFilter<br />
!Applies a version of the median filter optimized for binary images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMedianImageFilter.html BinaryMedianImageFilter]<br />
|-<br />
!BinaryMinMaxCurvatureFlowImageFilter<br />
!Denoise a binary image using min/max curvature flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMinMaxCurvatureFlowImageFilter.html BinaryMinMaxCurvatureFlowImageFilter]<br />
|-<br />
!BinaryMorphologicalClosingImageFilter<br />
!binary morphological closing of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMorphologicalClosingImageFilter.html BinaryMorphologicalClosingImageFilter]<br />
|-<br />
!BinaryMorphologicalOpeningImageFilter<br />
!binary morphological opening of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMorphologicalOpeningImageFilter.html BinaryMorphologicalOpeningImageFilter]<br />
|-<br />
!BinaryNotImageFilter<br />
!Implements the BinaryNot logical operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryNotImageFilter.html BinaryNotImageFilter]<br />
|-<br />
!BinaryOpeningByReconstructionImageFilter<br />
!binary morphological closing of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryOpeningByReconstructionImageFilter.html BinaryOpeningByReconstructionImageFilter]<br />
|-<br />
!BinaryProjectionImageFilter<br />
!Binary projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryProjectionImageFilter.html BinaryProjectionImageFilter]<br />
|-<br />
!BinaryReconstructionByDilationImageFilter<br />
!binary reconstruction by dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryReconstructionByDilationImageFilter.html BinaryReconstructionByDilationImageFilter]<br />
|-<br />
!BinaryReconstructionByErosionImageFilter<br />
!binary reconstruction by erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryReconstructionByErosionImageFilter.html BinaryReconstructionByErosionImageFilter]<br />
|-<br />
!BinaryThinningImageFilter<br />
!This filter computes one-pixel-wide edges of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThinningImageFilter.html BinaryThinningImageFilter]<br />
|-<br />
!BinaryThresholdImageFilter<br />
!Binarize an input image by thresholding.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThresholdImageFilter.html BinaryThresholdImageFilter]<br />
|-<br />
!BinaryThresholdProjectionImageFilter<br />
!BinaryThreshold projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThresholdProjectionImageFilter.html BinaryThresholdProjectionImageFilter]<br />
|-<br />
!BinomialBlurImageFilter<br />
!Performs a separable blur on each dimension of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinomialBlurImageFilter.html BinomialBlurImageFilter]<br />
|-<br />
!BitwiseNotImageFilter<br />
!Implements pixel-wise generic operation on one image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1UnaryFunctorImageFilter.html UnaryFunctorImageFilter]<br />
|-<br />
!BlackTopHatImageFilter<br />
!Black top hat extract local minima that are smaller than the structuring element.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BlackTopHatImageFilter.html BlackTopHatImageFilter]<br />
|-<br />
!BoundedReciprocalImageFilter<br />
!Computes 1/(1+x) for each pixel in the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoundedReciprocalImageFilter.html BoundedReciprocalImageFilter]<br />
|-<br />
!BoxMeanImageFilter<br />
!Implements a fast rectangular mean filter using the accumulator approach.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoxMeanImageFilter.html BoxMeanImageFilter]<br />
|-<br />
!BoxSigmaImageFilter<br />
!Implements a fast rectangular sigma filter using the accumulator approach.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoxSigmaImageFilter.html BoxSigmaImageFilter]<br />
|-<br />
!CannyEdgeDetectionImageFilter<br />
!This filter is an implementation of a Canny edge detector for scalar-valued images. Based on John Canny's paper "A Computational Approach<br />
to Edge Detection"(IEEE Transactions on Pattern Analysis and Machine Intelligence, Vol. PAMI-8, No.6, November 1986), there are four major steps used in the edge-detection scheme: (1) Smooth the input image with Gaussian filter. (2) Calculate the second directional derivatives of the smoothed image. (3) Non-Maximum Suppression: the zero-crossings of 2nd derivative are found, and the sign of third derivative is used to find the correct extrema. (4) The hysteresis thresholding is applied to the gradient magnitude (multiplied with zero-crossings) of the smoothed image to find and link edges.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CannyEdgeDetectionImageFilter.html CannyEdgeDetectionImageFilter]<br />
|-<br />
!CastImageFilter<br />
!Casts input image's pixels to output pixel type.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CastImageFilter.html CastImageFilter]<br />
|-<br />
!CheckerBoardImageFilter<br />
!Combines two images in a checkerboard pattern.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CheckerBoardImageFilter.html CheckerBoardImageFilter]<br />
|-<br />
!ClampImageFilter<br />
!Casts input pixels to output pixel type and clamps the output pixel values to a specified range.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ClampImageFilter.html ClampImageFilter]<br />
|-<br />
!ClosingByReconstructionImageFilter<br />
!Closing by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ClosingByReconstructionImageFilter.html ClosingByReconstructionImageFilter]<br />
|-<br />
!ComplexToImaginaryImageFilter<br />
!Computes pixel-wise the imaginary part of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToImaginaryImageFilter.html ComplexToImaginaryImageFilter]<br />
|-<br />
!ComplexToModulusImageFilter<br />
!Computes pixel-wise the Modulus of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToModulusImageFilter.html ComplexToModulusImageFilter]<br />
|-<br />
!ComplexToPhaseImageFilter<br />
!Computes pixel-wise the modulus of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToPhaseImageFilter.html ComplexToPhaseImageFilter]<br />
|-<br />
!ComplexToRealImageFilter<br />
!Computes pixel-wise the real(x) part of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToRealImageFilter.html ComplexToRealImageFilter]<br />
|-<br />
!ComposeImageFilter<br />
!ComposeImageFiltercombine several scalar images into a multicomponent image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComposeImageFilter.html ComposeImageFilter]<br />
|-<br />
!ConfidenceConnectedImageFilter<br />
!Segment pixels with similar statistics using connectivity.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConfidenceConnectedImageFilter.html ConfidenceConnectedImageFilter]<br />
|-<br />
!ConnectedComponentImageFilter<br />
!Label the objects in a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConnectedComponentImageFilter.html ConnectedComponentImageFilter]<br />
|-<br />
!ConnectedThresholdImageFilter<br />
!Label pixels that are connected to a seed and lie within a range of values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConnectedThresholdImageFilter.html ConnectedThresholdImageFilter]<br />
|-<br />
!ConstantPadImageFilter<br />
!Increase the image size by padding with a constant value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConstantPadImageFilter.html ConstantPadImageFilter]<br />
|-<br />
!ConvolutionImageFilter<br />
!Convolve a given image with an arbitrary image kernel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConvolutionImageFilter.html ConvolutionImageFilter]<br />
|-<br />
!CosImageFilter<br />
!Computes the cosine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CosImageFilter.html CosImageFilter]<br />
|-<br />
!CropImageFilter<br />
!Decrease the image size by cropping the image by an itk::Sizeat both the upper and lower bounds of the largest possible region.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CropImageFilter.html CropImageFilter]<br />
|-<br />
!CurvatureAnisotropicDiffusionImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CurvatureAnisotropicDiffusionImageFilter.html CurvatureAnisotropicDiffusionImageFilter]<br />
|-<br />
!CurvatureFlowImageFilter<br />
!Denoise an image using curvature driven flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CurvatureFlowImageFilter.html CurvatureFlowImageFilter]<br />
|-<br />
!CyclicShiftImageFilter<br />
!Perform a cyclic spatial shift of image intensities on the image grid.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CyclicShiftImageFilter.html CyclicShiftImageFilter]<br />
|-<br />
!DanielssonDistanceMapImageFilter<br />
!This filter computes the distance map of the input image as an approximation with pixel accuracy to the Euclidean distance.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DanielssonDistanceMapImageFilter.html DanielssonDistanceMapImageFilter]<br />
|-<br />
!DerivativeImageFilter<br />
!Computes the directional derivative of an image. The directional derivative at each pixel location is computed by convolution with a derivative operator of user-specified order.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DerivativeImageFilter.html DerivativeImageFilter]<br />
|-<br />
!DilateObjectMorphologyImageFilter<br />
!dilation of an object in an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DilateObjectMorphologyImageFilter.html DilateObjectMorphologyImageFilter]<br />
|-<br />
!DiscreteGaussianDerivativeImageFilter<br />
!Calculates image derivatives using discrete derivative gaussian kernels. This filter calculates Gaussian derivative by separable convolution of an image and a discrete Gaussian derivative operator (kernel).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DiscreteGaussianDerivativeImageFilter.html DiscreteGaussianDerivativeImageFilter]<br />
|-<br />
!DiscreteGaussianImageFilter<br />
!Blurs an image by separable convolution with discrete gaussian kernels. This filter performs Gaussian blurring by separable convolution of an image and a discrete Gaussian operator (kernel).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DiscreteGaussianImageFilter.html DiscreteGaussianImageFilter]<br />
|-<br />
!DivideFloorImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!DivideImageFilter<br />
!Pixel-wise division of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DivideImageFilter.html DivideImageFilter]<br />
|-<br />
!DivideRealImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!DoubleThresholdImageFilter<br />
!Binarize an input image using double thresholding.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DoubleThresholdImageFilter.html DoubleThresholdImageFilter]<br />
|-<br />
!EdgePotentialImageFilter<br />
!Computes the edge potential of an image from the image gradient.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1EdgePotentialImageFilter.html EdgePotentialImageFilter]<br />
|-<br />
!EqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!ErodeObjectMorphologyImageFilter<br />
!Erosion of an object in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ErodeObjectMorphologyImageFilter.html ErodeObjectMorphologyImageFilter]<br />
|-<br />
!ExpImageFilter<br />
!Computes the exponential function of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpImageFilter.html ExpImageFilter]<br />
|-<br />
!ExpNegativeImageFilter<br />
!Computes the function exp(-K.x) for each input pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpNegativeImageFilter.html ExpNegativeImageFilter]<br />
|-<br />
!ExpandImageFilter<br />
!Expand the size of an image by an integer factor in each dimension.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpandImageFilter.html ExpandImageFilter]<br />
|-<br />
!ExtractImageFilter<br />
!Decrease the image size by cropping the image to the selected region bounds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExtractImageFilter.html ExtractImageFilter]<br />
|-<br />
!FFTConvolutionImageFilter<br />
!Convolve a given image with an arbitrary image kernel using multiplication in the Fourier domain.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTConvolutionImageFilter.html FFTConvolutionImageFilter]<br />
|-<br />
!FFTNormalizedCorrelationImageFilter<br />
!Calculate normalized cross correlation using FFTs.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTNormalizedCorrelationImageFilter.html FFTNormalizedCorrelationImageFilter]<br />
|-<br />
!FFTShiftImageFilter<br />
!Shift the zero-frequency components of a Fourier transfrom to the center of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTShiftImageFilter.html FFTShiftImageFilter]<br />
|-<br />
!FastApproximateRankImageFilter<br />
!A separable rank filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FastApproximateRankImageFilter.html FastApproximateRankImageFilter]<br />
|-<br />
!FastMarchingImageFilter<br />
!Solve an Eikonal equation using Fast Marching.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FastMarchingImageFilterBase.html FastMarchingImageFilterBase]<br />
|-<br />
!FlipImageFilter<br />
!Flips an image across user specified axes.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FlipImageFilter.html FlipImageFilter]<br />
|-<br />
!ForwardFFTImageFilter<br />
!Base class for forward Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ForwardFFTImageFilter.html ForwardFFTImageFilter]<br />
|-<br />
!GaborImageSource<br />
!Generate an n-dimensional image of a Gabor filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GaborImageSource.html GaborImageSource]<br />
|-<br />
!GaussianImageSource<br />
!Generate an n-dimensional image of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GaussianImageSource.html GaussianImageSource]<br />
|-<br />
!GeodesicActiveContourLevelSetImageFilter<br />
!Segments structures in images based on a user supplied edge potential map.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GeodesicActiveContourLevelSetImageFilter.html GeodesicActiveContourLevelSetImageFilter]<br />
|-<br />
!GradientAnisotropicDiffusionImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientAnisotropicDiffusionImageFilter.html GradientAnisotropicDiffusionImageFilter]<br />
|-<br />
!GradientImageFilter<br />
!Computes the gradient of an image using directional derivatives.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientImageFilter.html GradientImageFilter]<br />
|-<br />
!GradientMagnitudeImageFilter<br />
!Computes the gradient magnitude of an image region at each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientMagnitudeImageFilter.html GradientMagnitudeImageFilter]<br />
|-<br />
!GradientMagnitudeRecursiveGaussianImageFilter<br />
!Computes the Magnitude of the Gradient of an image by convolution with the first derivative of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientMagnitudeRecursiveGaussianImageFilter.html GradientMagnitudeRecursiveGaussianImageFilter]<br />
|-<br />
!GradientRecursiveGaussianImageFilter<br />
!Computes the gradient of an image by convolution with the first derivative of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientRecursiveGaussianImageFilter.html GradientRecursiveGaussianImageFilter]<br />
|-<br />
!GrayscaleConnectedClosingImageFilter<br />
!Enhance pixels associated with a dark object (identified by a seed pixel) where the dark object is surrounded by a brigher object.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleConnectedClosingImageFilter.html GrayscaleConnectedClosingImageFilter]<br />
|-<br />
!GrayscaleConnectedOpeningImageFilter<br />
!Enhance pixels associated with a bright object (identified by a seed pixel) where the bright object is surrounded by a darker object.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleConnectedOpeningImageFilter.html GrayscaleConnectedOpeningImageFilter]<br />
|-<br />
!GrayscaleDilateImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleDilateImageFilter.html GrayscaleDilateImageFilter]<br />
|-<br />
!GrayscaleErodeImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleErodeImageFilter.html GrayscaleErodeImageFilter]<br />
|-<br />
!GrayscaleFillholeImageFilter<br />
!Remove local minima not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleFillholeImageFilter.html GrayscaleFillholeImageFilter]<br />
|-<br />
!GrayscaleGeodesicDilateImageFilter<br />
!geodesic gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGeodesicDilateImageFilter.html GrayscaleGeodesicDilateImageFilter]<br />
|-<br />
!GrayscaleGeodesicErodeImageFilter<br />
!geodesic gray scale erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGeodesicErodeImageFilter.html GrayscaleGeodesicErodeImageFilter]<br />
|-<br />
!GrayscaleGrindPeakImageFilter<br />
!Remove local maxima not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGrindPeakImageFilter.html GrayscaleGrindPeakImageFilter]<br />
|-<br />
!GrayscaleMorphologicalClosingImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleMorphologicalClosingImageFilter.html GrayscaleMorphologicalClosingImageFilter]<br />
|-<br />
!GrayscaleMorphologicalOpeningImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleMorphologicalOpeningImageFilter.html GrayscaleMorphologicalOpeningImageFilter]<br />
|-<br />
!GreaterEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!GreaterImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!GridImageSource<br />
!Generate an n-dimensional image of a grid.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GridImageSource.html GridImageSource]<br />
|-<br />
!HConcaveImageFilter<br />
!Identify local minima whose depth below the baseline is greater than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HConcaveImageFilter.html HConcaveImageFilter]<br />
|-<br />
!HConvexImageFilter<br />
!Identify local maxima whose height above the baseline is greater than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HConvexImageFilter.html HConvexImageFilter]<br />
|-<br />
!HMaximaImageFilter<br />
!Suppress local maxima whose height above the baseline is less than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HMaximaImageFilter.html HMaximaImageFilter]<br />
|-<br />
!HMinimaImageFilter<br />
!Suppress local minima whose depth below the baseline is less than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HMinimaImageFilter.html HMinimaImageFilter]<br />
|-<br />
!HalfHermitianToRealInverseFFTImageFilter<br />
!Base class for specialized complex-to-real inverse Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HalfHermitianToRealInverseFFTImageFilter.html HalfHermitianToRealInverseFFTImageFilter]<br />
|-<br />
!HausdorffDistanceImageFilter<br />
!Computes the Hausdorff distance between the set of non-zero pixels of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HausdorffDistanceImageFilter.html HausdorffDistanceImageFilter]<br />
|-<br />
!HistogramMatchingImageFilter<br />
!Normalize the grayscale values between two images by histogram matching.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HistogramMatchingImageFilter.html HistogramMatchingImageFilter]<br />
|-<br />
!HuangThresholdImageFilter<br />
!Threshold an image using the Huang Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HuangThresholdImageFilter.html HuangThresholdImageFilter]<br />
|-<br />
!IntensityWindowingImageFilter<br />
!Applies a linear transformation to the intensity levels of the input Imagethat are inside a user-defined interval. Values below this interval are mapped to a constant. Values over the interval are mapped to another constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IntensityWindowingImageFilter.html IntensityWindowingImageFilter]<br />
|-<br />
!IntermodesThresholdImageFilter<br />
!Threshold an image using the Intermodes Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IntermodesThresholdImageFilter.html IntermodesThresholdImageFilter]<br />
|-<br />
!InverseDeconvolutionImageFilter<br />
!The direct linear inverse deconvolution filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InverseDeconvolutionImageFilter.html InverseDeconvolutionImageFilter]<br />
|-<br />
!InverseFFTImageFilter<br />
!Base class for inverse Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InverseFFTImageFilter.html InverseFFTImageFilter]<br />
|-<br />
!InvertIntensityImageFilter<br />
!Invert the intensity of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InvertIntensityImageFilter.html InvertIntensityImageFilter]<br />
|-<br />
!IsoContourDistanceImageFilter<br />
!Compute an approximate distance from an interpolated isocontour to the close grid points.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsoContourDistanceImageFilter.html IsoContourDistanceImageFilter]<br />
|-<br />
!IsoDataThresholdImageFilter<br />
!Threshold an image using the IsoData Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsoDataThresholdImageFilter.html IsoDataThresholdImageFilter]<br />
|-<br />
!IsolatedConnectedImageFilter<br />
!Label pixels that are connected to one set of seeds but not another.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsolatedConnectedImageFilter.html IsolatedConnectedImageFilter]<br />
|-<br />
!IsolatedWatershedImageFilter<br />
!Isolate watershed basins using two seeds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsolatedWatershedImageFilter.html IsolatedWatershedImageFilter]<br />
|-<br />
!JoinSeriesImageFilter<br />
!Join N-D images into an (N+1)-D image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1JoinSeriesImageFilter.html JoinSeriesImageFilter]<br />
|-<br />
!KittlerIllingworthThresholdImageFilter<br />
!Threshold an image using the KittlerIllingworth Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1KittlerIllingworthThresholdImageFilter.html KittlerIllingworthThresholdImageFilter]<br />
|-<br />
!LabelContourImageFilter<br />
!Labels the pixels on the border of the objects in a labeled image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelContourImageFilter.html LabelContourImageFilter]<br />
|-<br />
!LabelOverlayImageFilter<br />
!Apply a colormap to a label image and put it on top of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelOverlayImageFilter.html LabelOverlayImageFilter]<br />
|-<br />
!LabelToRGBImageFilter<br />
!Apply a colormap to a label image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelToRGBImageFilter.html LabelToRGBImageFilter]<br />
|-<br />
!LabelVotingImageFilter<br />
!This filter performs pixelwise voting among an arbitrary number of input images, where each of them represents a segmentation of the same scene (i.e., image).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelVotingImageFilter.html LabelVotingImageFilter]<br />
|-<br />
!LandweberDeconvolutionImageFilter<br />
!Deconvolve an image using the Landweber deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LandweberDeconvolutionImageFilter.html LandweberDeconvolutionImageFilter]<br />
|-<br />
!LaplacianImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianImageFilter.html LaplacianImageFilter]<br />
|-<br />
!LaplacianRecursiveGaussianImageFilter<br />
!Computes the Laplacian of Gaussian (LoG) of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianRecursiveGaussianImageFilter.html LaplacianRecursiveGaussianImageFilter]<br />
|-<br />
!LaplacianSegmentationLevelSetImageFilter<br />
!Segments structures in images based on a second derivative image features.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianSegmentationLevelSetImageFilter.html LaplacianSegmentationLevelSetImageFilter]<br />
|-<br />
!LaplacianSharpeningImageFilter<br />
!This filter sharpens an image using a Laplacian. LaplacianSharpening highlights regions of rapid intensity change and therefore highlights or enhances the edges. The result is an image that appears more in focus.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianSharpeningImageFilter.html LaplacianSharpeningImageFilter]<br />
|-<br />
!LessEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!LessImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!LiThresholdImageFilter<br />
!Threshold an image using the Li Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LiThresholdImageFilter.html LiThresholdImageFilter]<br />
|-<br />
!Log10ImageFilter<br />
!Computes the log10 of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1Log10ImageFilter.html Log10ImageFilter]<br />
|-<br />
!LogImageFilter<br />
!Computes the log() of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LogImageFilter.html LogImageFilter]<br />
|-<br />
!MagnitudeAndPhaseToComplexImageFilter<br />
!Implements pixel-wise conversion of magnitude and phase data into complex voxels.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MagnitudeAndPhaseToComplexImageFilter.html MagnitudeAndPhaseToComplexImageFilter]<br />
|-<br />
!MaskImageFilter<br />
!Mask an image with a mask.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskImageFilter.html MaskImageFilter]<br />
|-<br />
!MaskNegatedImageFilter<br />
!Mask an image with the negative of a mask.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskNegatedImageFilter.html MaskNegatedImageFilter]<br />
|-<br />
!MaskedFFTNormalizedCorrelationImageFilter<br />
!Calculate masked normalized cross correlation using FFTs.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskedFFTNormalizedCorrelationImageFilter.html MaskedFFTNormalizedCorrelationImageFilter]<br />
|-<br />
!MaximumEntropyThresholdImageFilter<br />
!Threshold an image using the MaximumEntropy Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumEntropyThresholdImageFilter.html MaximumEntropyThresholdImageFilter]<br />
|-<br />
!MaximumImageFilter<br />
!Implements a pixel-wise operator Max(a,b) between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumImageFilter.html MaximumImageFilter]<br />
|-<br />
!MaximumProjectionImageFilter<br />
!Maximum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumProjectionImageFilter.html MaximumProjectionImageFilter]<br />
|-<br />
!MeanImageFilter<br />
!Applies an averaging filter to an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MeanImageFilter.html MeanImageFilter]<br />
|-<br />
!MeanProjectionImageFilter<br />
!Mean projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MeanProjectionImageFilter.html MeanProjectionImageFilter]<br />
|-<br />
!MedianImageFilter<br />
!Applies a median filter to an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MedianImageFilter.html MedianImageFilter]<br />
|-<br />
!MedianProjectionImageFilter<br />
!Median projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MedianProjectionImageFilter.html MedianProjectionImageFilter]<br />
|-<br />
!MinMaxCurvatureFlowImageFilter<br />
!Denoise an image using min/max curvature flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinMaxCurvatureFlowImageFilter.html MinMaxCurvatureFlowImageFilter]<br />
|-<br />
!MinimumImageFilter<br />
!Implements a pixel-wise operator Min(a,b) between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumImageFilter.html MinimumImageFilter]<br />
|-<br />
!MinimumMaximumImageFilter<br />
!Computes the minimum and the maximum intensity values of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumMaximumImageFilter.html MinimumMaximumImageFilter]<br />
|-<br />
!MinimumProjectionImageFilter<br />
!Minimum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumProjectionImageFilter.html MinimumProjectionImageFilter]<br />
|-<br />
!MirrorPadImageFilter<br />
!Increase the image size by padding with replicants of the input image value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MirrorPadImageFilter.html MirrorPadImageFilter]<br />
|-<br />
!ModulusImageFilter<br />
!Computes the modulus (x % dividend) pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ModulusImageFilter.html ModulusImageFilter]<br />
|-<br />
!MomentsThresholdImageFilter<br />
!Threshold an image using the Moments Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MomentsThresholdImageFilter.html MomentsThresholdImageFilter]<br />
|-<br />
!MorphologicalGradientImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalGradientImageFilter.html MorphologicalGradientImageFilter]<br />
|-<br />
!MorphologicalWatershedFromMarkersImageFilter<br />
!Morphological watershed transform from markers.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalWatershedFromMarkersImageFilter.html MorphologicalWatershedFromMarkersImageFilter]<br />
|-<br />
!MorphologicalWatershedImageFilter<br />
!TODO.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalWatershedImageFilter.html MorphologicalWatershedImageFilter]<br />
|-<br />
!MultiplyImageFilter<br />
!Pixel-wise multiplication of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MultiplyImageFilter.html MultiplyImageFilter]<br />
|-<br />
!N4BiasFieldCorrectionImageFilter<br />
!Implementation of the N4 bias field correction algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1N4BiasFieldCorrectionImageFilter.html N4BiasFieldCorrectionImageFilter]<br />
|-<br />
!NaryAddImageFilter<br />
!Pixel-wise addition of N images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NaryAddImageFilter.html NaryAddImageFilter]<br />
|-<br />
!NaryMaximumImageFilter<br />
!Computes the pixel-wise maximum of several images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NaryMaximumImageFilter.html NaryMaximumImageFilter]<br />
|-<br />
!NeighborhoodConnectedImageFilter<br />
!Label pixels that are connected to a seed and lie within a neighborhood.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NeighborhoodConnectedImageFilter.html NeighborhoodConnectedImageFilter]<br />
|-<br />
!NoiseImageFilter<br />
!Calculate the local noise in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NoiseImageFilter.html NoiseImageFilter]<br />
|-<br />
!NormalizeImageFilter<br />
!Normalize an image by setting its mean to zero and variance to one.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizeImageFilter.html NormalizeImageFilter]<br />
|-<br />
!NormalizeToConstantImageFilter<br />
!Scales image pixel intensities to make the sum of all pixels equal a user-defined constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizeToConstantImageFilter.html NormalizeToConstantImageFilter]<br />
|-<br />
!NormalizedCorrelationImageFilter<br />
!Computes the normalized correlation of an image and a template.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizedCorrelationImageFilter.html NormalizedCorrelationImageFilter]<br />
|-<br />
!NotEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!NotImageFilter<br />
!Implements the NOT logical operator pixel-wise on an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NotImageFilter.html NotImageFilter]<br />
|-<br />
!OpeningByReconstructionImageFilter<br />
!Opening by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OpeningByReconstructionImageFilter.html OpeningByReconstructionImageFilter]<br />
|-<br />
!OrImageFilter<br />
!Implements the OR bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OrImageFilter.html OrImageFilter]<br />
|-<br />
!OtsuMultipleThresholdsImageFilter<br />
!Threshold an image using multiple Otsu Thresholds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OtsuMultipleThresholdsImageFilter.html OtsuMultipleThresholdsImageFilter]<br />
|-<br />
!OtsuThresholdImageFilter<br />
!Threshold an image using the Otsu Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OtsuThresholdImageFilter.html OtsuThresholdImageFilter]<br />
|-<br />
!PasteImageFilter<br />
!Paste an image into another image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PasteImageFilter.html PasteImageFilter]<br />
|-<br />
!PatchBasedDenoisingImageFilter<br />
!Derived class implementing a specific patch-based denoising algorithm, as detailed below.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PatchBasedDenoisingImageFilter.html PatchBasedDenoisingImageFilter]<br />
|-<br />
!PermuteAxesImageFilter<br />
!Permutes the image axes according to a user specified order.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PermuteAxesImageFilter.html PermuteAxesImageFilter]<br />
|-<br />
!PhysicalPointImageSource<br />
!Generate an image of the physical locations of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PhysicalPointImageSource.html PhysicalPointImageSource]<br />
|-<br />
!PowImageFilter<br />
!Computes the powers of 2 images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PowImageFilter.html PowImageFilter]<br />
|-<br />
!ProjectedLandweberDeconvolutionImageFilter<br />
!Deconvolve an image using the projected Landweber deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ProjectedLandweberDeconvolutionImageFilter.html ProjectedLandweberDeconvolutionImageFilter]<br />
|-<br />
!RankImageFilter<br />
!Rank filter of a greyscale image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RankImageFilter.html RankImageFilter]<br />
|-<br />
!RealAndImaginaryToComplexImageFilter<br />
!ComposeImageFiltercombine several scalar images into a multicomponent image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComposeImageFilter.html ComposeImageFilter]<br />
|-<br />
!RealToHalfHermitianForwardFFTImageFilter<br />
!Base class for specialized real-to-complex forward Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RealToHalfHermitianForwardFFTImageFilter.html RealToHalfHermitianForwardFFTImageFilter]<br />
|-<br />
!ReconstructionByDilationImageFilter<br />
!grayscale reconstruction by dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ReconstructionByDilationImageFilter.html ReconstructionByDilationImageFilter]<br />
|-<br />
!ReconstructionByErosionImageFilter<br />
!grayscale reconstruction by erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ReconstructionByErosionImageFilter.html ReconstructionByErosionImageFilter]<br />
|-<br />
!RecursiveGaussianImageFilter<br />
!Base class for computing IIR convolution with an approximation of a Gaussian kernel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RecursiveGaussianImageFilter.html RecursiveGaussianImageFilter]<br />
|-<br />
!RegionOfInterestImageFilter<br />
!Extract a region of interest from the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionOfInterestImageFilter.html RegionOfInterestImageFilter]<br />
|-<br />
!RegionalMaximaImageFilter<br />
!Produce a binary image where foreground is the regional maxima of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionalMaximaImageFilter.html RegionalMaximaImageFilter]<br />
|-<br />
!RegionalMinimaImageFilter<br />
!Produce a binary image where foreground is the regional minima of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionalMinimaImageFilter.html RegionalMinimaImageFilter]<br />
|-<br />
!RelabelComponentImageFilter<br />
!Relabel the components in an image such that consecutive labels are used.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RelabelComponentImageFilter.html RelabelComponentImageFilter]<br />
|-<br />
!RenyiEntropyThresholdImageFilter<br />
!Threshold an image using the RenyiEntropy Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RenyiEntropyThresholdImageFilter.html RenyiEntropyThresholdImageFilter]<br />
|-<br />
!ResampleImageFilter<br />
!Resample an image via a coordinate transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ResampleImageFilter.html ResampleImageFilter]<br />
|-<br />
!RescaleIntensityImageFilter<br />
!Applies a linear transformation to the intensity levels of the input Image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RescaleIntensityImageFilter.html RescaleIntensityImageFilter]<br />
|-<br />
!RichardsonLucyDeconvolutionImageFilter<br />
!Deconvolve an image using the Richardson-Lucy deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RichardsonLucyDeconvolutionImageFilter.html RichardsonLucyDeconvolutionImageFilter]<br />
|-<br />
!STAPLEImageFilter<br />
!The STAPLE filter implements the Simultaneous Truth and Performance Level Estimation algorithm for generating ground truth volumes from a set of binary expert segmentations.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1STAPLEImageFilter.html STAPLEImageFilter]<br />
|-<br />
!ScalarChanAndVeseDenseLevelSetImageFilter<br />
!Dense implementation of the Chan and Vese multiphase level set image filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarChanAndVeseDenseLevelSetImageFilter.html ScalarChanAndVeseDenseLevelSetImageFilter]<br />
|-<br />
!ScalarConnectedComponentImageFilter<br />
!A connected components filter that labels the objects in an arbitrary image. Two pixels are similar if they are within threshold of each other. Uses ConnectedComponentFunctorImageFilter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarConnectedComponentImageFilter.html ScalarConnectedComponentImageFilter]<br />
|-<br />
!ScalarImageKmeansImageFilter<br />
!Classifies the intensity values of a scalar image using the K-Means algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarImageKmeansImageFilter.html ScalarImageKmeansImageFilter]<br />
|-<br />
!ScalarToRGBColormapImageFilter<br />
!Implements pixel-wise intensity->rgb mapping operation on one image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarToRGBColormapImageFilter.html ScalarToRGBColormapImageFilter]<br />
|-<br />
!ShanbhagThresholdImageFilter<br />
!Threshold an image using the Shanbhag Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShanbhagThresholdImageFilter.html ShanbhagThresholdImageFilter]<br />
|-<br />
!ShapeDetectionLevelSetImageFilter<br />
!Segments structures in images based on a user supplied edge potential map.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShapeDetectionLevelSetImageFilter.html ShapeDetectionLevelSetImageFilter]<br />
|-<br />
!ShiftScaleImageFilter<br />
!Shift and scale the pixels in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShiftScaleImageFilter.html ShiftScaleImageFilter]<br />
|-<br />
!ShrinkImageFilter<br />
!Reduce the size of an image by an integer factor in each dimension.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShrinkImageFilter.html ShrinkImageFilter]<br />
|-<br />
!SigmoidImageFilter<br />
!Computes the sigmoid function pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SigmoidImageFilter.html SigmoidImageFilter]<br />
|-<br />
!SignedDanielssonDistanceMapImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SignedDanielssonDistanceMapImageFilter.html SignedDanielssonDistanceMapImageFilter]<br />
|-<br />
!SignedMaurerDistanceMapImageFilter<br />
!This filter calculates the Euclidean distance transform of a binary image in linear time for arbitrary dimensions.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SignedMaurerDistanceMapImageFilter.html SignedMaurerDistanceMapImageFilter]<br />
|-<br />
!SimilarityIndexImageFilter<br />
!Measures the similarity between the set of non-zero pixels of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SimilarityIndexImageFilter.html SimilarityIndexImageFilter]<br />
|-<br />
!SimpleContourExtractorImageFilter<br />
!Computes an image of contours which will be the contour of the first image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SimpleContourExtractorImageFilter.html SimpleContourExtractorImageFilter]<br />
|-<br />
!SinImageFilter<br />
!Computes the sine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SinImageFilter.html SinImageFilter]<br />
|-<br />
!SliceImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SliceImageFilter.html SliceImageFilter]<br />
|-<br />
!SmoothingRecursiveGaussianImageFilter<br />
!Computes the smoothing of an image by convolution with the Gaussian kernels implemented as IIR filters.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SmoothingRecursiveGaussianImageFilter.html SmoothingRecursiveGaussianImageFilter]<br />
|-<br />
!SobelEdgeDetectionImageFilter<br />
!A 2D or 3D edge detection using the Sobel operator.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SobelEdgeDetectionImageFilter.html SobelEdgeDetectionImageFilter]<br />
|-<br />
!SqrtImageFilter<br />
!Computes the square root of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SqrtImageFilter.html SqrtImageFilter]<br />
|-<br />
!SquareImageFilter<br />
!Computes the square of the intensity values pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SquareImageFilter.html SquareImageFilter]<br />
|-<br />
!SquaredDifferenceImageFilter<br />
!Implements pixel-wise the computation of squared difference.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SquaredDifferenceImageFilter.html SquaredDifferenceImageFilter]<br />
|-<br />
!StandardDeviationProjectionImageFilter<br />
!Mean projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1StandardDeviationProjectionImageFilter.html StandardDeviationProjectionImageFilter]<br />
|-<br />
!SubtractImageFilter<br />
!Pixel-wise subtraction of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SubtractImageFilter.html SubtractImageFilter]<br />
|-<br />
!SumProjectionImageFilter<br />
!Sum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SumProjectionImageFilter.html SumProjectionImageFilter]<br />
|-<br />
!TanImageFilter<br />
!Computes the tangent of each input pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TanImageFilter.html TanImageFilter]<br />
|-<br />
!TernaryAddImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryAddImageFilter.html TernaryAddImageFilter]<br />
|-<br />
!TernaryMagnitudeImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryMagnitudeImageFilter.html TernaryMagnitudeImageFilter]<br />
|-<br />
!TernaryMagnitudeSquaredImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryMagnitudeSquaredImageFilter.html TernaryMagnitudeSquaredImageFilter]<br />
|-<br />
!ThresholdImageFilter<br />
!Set image values to a user-specified value if they are below, above, or between simple threshold values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdImageFilter.html ThresholdImageFilter]<br />
|-<br />
!ThresholdMaximumConnectedComponentsImageFilter<br />
!Finds the threshold value of an image based on maximizing the number of objects in the image that are larger than a given minimal size.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdMaximumConnectedComponentsImageFilter.html ThresholdMaximumConnectedComponentsImageFilter]<br />
|-<br />
!ThresholdSegmentationLevelSetImageFilter<br />
!Segments structures in images based on intensity values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdSegmentationLevelSetImageFilter.html ThresholdSegmentationLevelSetImageFilter]<br />
|-<br />
!TikhonovDeconvolutionImageFilter<br />
!An inverse deconvolution filter regularized in the Tikhonov sense.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TikhonovDeconvolutionImageFilter.html TikhonovDeconvolutionImageFilter]<br />
|-<br />
!TileImageFilter<br />
!Tile multiple input images into a single output image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TileImageFilter.html TileImageFilter]<br />
|-<br />
!TriangleThresholdImageFilter<br />
!Threshold an image using the Triangle Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TriangleThresholdImageFilter.html TriangleThresholdImageFilter]<br />
|-<br />
!UnaryMinusImageFilter<br />
!Computes the negative of each pixel.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1UnaryFunctorImageFilter.html UnaryFunctorImageFilter]<br />
|-<br />
!ValuedRegionalMaximaImageFilter<br />
!Transforms the image so that any pixel that is not a regional maxima is set to the minimum value for the pixel type. Pixels that are regional maxima retain their value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ValuedRegionalMaximaImageFilter.html ValuedRegionalMaximaImageFilter]<br />
|-<br />
!ValuedRegionalMinimaImageFilter<br />
!Transforms the image so that any pixel that is not a regional minima is set to the maximum value for the pixel type. Pixels that are regional minima retain their value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ValuedRegionalMinimaImageFilter.html ValuedRegionalMinimaImageFilter]<br />
|-<br />
!VectorConfidenceConnectedImageFilter<br />
!Segment pixels with similar statistics using connectivity.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorConfidenceConnectedImageFilter.html VectorConfidenceConnectedImageFilter]<br />
|-<br />
!VectorConnectedComponentImageFilter<br />
!A connected components filter that labels the objects in a vector image. Two vectors are pointing similar directions if one minus their dot product is less than a threshold. Vectors that are 180 degrees out of phase are similar. Assumes that vectors are normalized.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorConnectedComponentImageFilter.html VectorConnectedComponentImageFilter]<br />
|-<br />
!VectorIndexSelectionCastImageFilter<br />
!Extracts the selected index of the vector that is the input pixel type.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorIndexSelectionCastImageFilter.html VectorIndexSelectionCastImageFilter]<br />
|-<br />
!VectorMagnitudeImageFilter<br />
!Take an image of vectors as input and produce an image with the magnitude of those vectors.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorMagnitudeImageFilter.html VectorMagnitudeImageFilter]<br />
|-<br />
!VotingBinaryHoleFillingImageFilter<br />
!Fills in holes and cavities by applying a voting operation on each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryHoleFillingImageFilter.html VotingBinaryHoleFillingImageFilter]<br />
|-<br />
!VotingBinaryImageFilter<br />
!Applies a voting operation in a neighborhood of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryImageFilter.html VotingBinaryImageFilter]<br />
|-<br />
!VotingBinaryIterativeHoleFillingImageFilter<br />
!Fills in holes and cavities by iteratively applying a voting operation.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryIterativeHoleFillingImageFilter.html VotingBinaryIterativeHoleFillingImageFilter]<br />
|-<br />
!WarpImageFilter<br />
!Warps an image using an input displacement field.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WarpImageFilter.html WarpImageFilter]<br />
|-<br />
!WhiteTopHatImageFilter<br />
!White top hat extract local maxima that are larger than the structuring element.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WhiteTopHatImageFilter.html WhiteTopHatImageFilter]<br />
|-<br />
!WienerDeconvolutionImageFilter<br />
!The Wiener deconvolution image filter is designed to restore an image convolved with a blurring kernel while keeping noise enhancement to a minimum.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WienerDeconvolutionImageFilter.html WienerDeconvolutionImageFilter]<br />
|-<br />
!WrapPadImageFilter<br />
!Increase the image size by padding with replicants of the input image value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WrapPadImageFilter.html WrapPadImageFilter]<br />
|-<br />
!XorImageFilter<br />
!Computes the XOR bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1XorImageFilter.html XorImageFilter]<br />
|-<br />
!YenThresholdImageFilter<br />
!Threshold an image using the Yen Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1YenThresholdImageFilter.html YenThresholdImageFilter]<br />
|-<br />
!ZeroCrossingBasedEdgeDetectionImageFilter<br />
!This filter implements a zero-crossing based edge detecor.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroCrossingBasedEdgeDetectionImageFilter.html ZeroCrossingBasedEdgeDetectionImageFilter]<br />
|-<br />
!ZeroCrossingImageFilter<br />
!This filter finds the closest pixel to the zero-crossings (sign changes) in a signed itk::Image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroCrossingImageFilter.html ZeroCrossingImageFilter]<br />
|-<br />
!ZeroFluxNeumannPadImageFilter<br />
!Increase the image size by padding according to the zero-flux Neumann boundary condition.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroFluxNeumannPadImageFilter.html ZeroFluxNeumannPadImageFilter]<br />
|}<br />
<!-- END TABLE --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}github.com/SimpleITK/SlicerSimpleFilters<br />
<br />
<!-- ---------------------------- --><br />
<br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/SimpleFilters&diff=63721Documentation/Nightly/Modules/SimpleFilters2021-07-25T14:02:11Z<p>Pieper: Add SimpleFilters code link</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
This work is supported by NLM, and the Slicer Community. This work is partially supported by...<br><br />
Author: Bradley Lowekamp, Steve Pieper, Jean-Cristophe Fillion Robin<br><br />
Contact: Bradley Lowekamp <email>blowekamp@mail.nih.gov</email> <br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-logo-gallery<br />
|{{collaborator|logo|nlm}}|{{collaborator|longname|nlm}}<br />
|{{collaborator|logo|namic}}|{{collaborator|longname|namic}}<br />
|Image:Itk-logo.jpg|Itk<br />
|{{collaborator|logo|kitware}}|{{collaborator|longname|kitware}}<br />
}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
<br />
The SimpleFilters module provides a simple interface to hundreds of basic and advanced filters from ITK.<br />
<br />
The algorithms available include binary morphology, grayscale morphology, denoising, thresholding, image intensity manipulation, region growing, FFT, and many advanced algorithms.<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
<br />
<br />
[[Image:SimpleFilters_pannel.png|thumb|300px|Panel for SimpleFilters]]<br />
<br />
The panned for SimpleFilters is modeled after the Command Line Modules.<br />
<br />
At the top the '''Filters''' section enable the selection of one of the hundred of filters available. The '''Search''' text box is use quickly find a filter based on it's name.<br />
<br />
The top of the '''Parameters''' section dynamically changes based of the Filter selected above, it presents a list of input filter and parameters which the filter needs. Along with the output image for the filter. The behavior of the output is modeled after the CLIs.<br />
<br />
At the bottom it features an '''Apply''' to run the filter, an '''Abort''' button to cancel a actively running filter, and a '''Restore Defaults''' to revert the parameters to their initial settings. Above that is the status and progress if a filter is active.<br />
<br />
<br />
==Tips==<br />
<br />
The output image for the filter can be a gray scale image or a label map. The "LabelMap" check box should be selected to ensure the image is displayed correctly with in Slicer.<br />
<br />
Most filters which take more than one image as input expect that the filters be the same pixel type, and the images occupy the same physical space.<br />
<br />
<br />
<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
<br />
<br />
<gallery><br />
Image:SimpleFilters1.png|MultiOtsuThreshold to LabelMap<br />
Image:SimpleFilters2.png|LaplacianRecursiveGaussian<br />
Image:SimpleFilters3.png|GrayscaleMorphologicalClosing<br />
</gallery><br />
<br />
There are a large number of filters available in the SimpleFilters module. The following is a table of the filter name, a brief description, and a link to the underlaying ITK filter. The ITK link contains additional detail about using the filter.<br />
<br />
<!-- BEGIN TABLE --><br />
{| border="1" style="border-collapse:collapse;"<br />
|-<br />
!Filter Name<br />
!Brief Description<br />
!ITK Class<br />
|-<br />
!AbsImageFilter<br />
!Computes the absolute value of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AbsImageFilter.html AbsImageFilter]<br />
|-<br />
!AbsoluteValueDifferenceImageFilter<br />
!Implements pixel-wise the computation of absolute value difference.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AbsoluteValueDifferenceImageFilter.html AbsoluteValueDifferenceImageFilter]<br />
|-<br />
!AcosImageFilter<br />
!Computes the inverse cosine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AcosImageFilter.html AcosImageFilter]<br />
|-<br />
!AdaptiveHistogramEqualizationImageFilter<br />
!Power Law Adaptive Histogram Equalization.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AdaptiveHistogramEqualizationImageFilter.html AdaptiveHistogramEqualizationImageFilter]<br />
|-<br />
!AddImageFilter<br />
!Pixel-wise addition of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AddImageFilter.html AddImageFilter]<br />
|-<br />
!AndImageFilter<br />
!Implements the AND bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AndImageFilter.html AndImageFilter]<br />
|-<br />
!AntiAliasBinaryImageFilter<br />
!A method for estimation of a surface from a binary volume.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AntiAliasBinaryImageFilter.html AntiAliasBinaryImageFilter]<br />
|-<br />
!ApproximateSignedDistanceMapImageFilter<br />
!Create a map of the approximate signed distance from the boundaries of a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ApproximateSignedDistanceMapImageFilter.html ApproximateSignedDistanceMapImageFilter]<br />
|-<br />
!AsinImageFilter<br />
!Computes the sine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AsinImageFilter.html AsinImageFilter]<br />
|-<br />
!Atan2ImageFilter<br />
!Computes two argument inverse tangent.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1Atan2ImageFilter.html Atan2ImageFilter]<br />
|-<br />
!AtanImageFilter<br />
!Computes the one-argument inverse tangent of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1AtanImageFilter.html AtanImageFilter]<br />
|-<br />
!BilateralImageFilter<br />
!Blurs an image while preserving edges.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BilateralImageFilter.html BilateralImageFilter]<br />
|-<br />
!BinShrinkImageFilter<br />
!Reduce the size of an image by an integer factor in each dimension while performing averaging of an input neighborhood.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinShrinkImageFilter.html BinShrinkImageFilter]<br />
|-<br />
!BinaryClosingByReconstructionImageFilter<br />
!binary closing by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryClosingByReconstructionImageFilter.html BinaryClosingByReconstructionImageFilter]<br />
|-<br />
!BinaryContourImageFilter<br />
!Labels the pixels on the border of the objects in a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryContourImageFilter.html BinaryContourImageFilter]<br />
|-<br />
!BinaryDilateImageFilter<br />
!Fast binary dilation.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryDilateImageFilter.html BinaryDilateImageFilter]<br />
|-<br />
!BinaryErodeImageFilter<br />
!Fast binary erosion.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryErodeImageFilter.html BinaryErodeImageFilter]<br />
|-<br />
!BinaryFillholeImageFilter<br />
!Remove holes not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFillholeImageFilter.html BinaryFillholeImageFilter]<br />
|-<br />
!BinaryGrindPeakImageFilter<br />
!Remove the objects not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryGrindPeakImageFilter.html BinaryGrindPeakImageFilter]<br />
|-<br />
!BinaryMagnitudeImageFilter<br />
!Computes the square root of the sum of squares of corresponding input pixels.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMagnitudeImageFilter.html BinaryMagnitudeImageFilter]<br />
|-<br />
!BinaryMedianImageFilter<br />
!Applies a version of the median filter optimized for binary images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMedianImageFilter.html BinaryMedianImageFilter]<br />
|-<br />
!BinaryMinMaxCurvatureFlowImageFilter<br />
!Denoise a binary image using min/max curvature flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMinMaxCurvatureFlowImageFilter.html BinaryMinMaxCurvatureFlowImageFilter]<br />
|-<br />
!BinaryMorphologicalClosingImageFilter<br />
!binary morphological closing of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMorphologicalClosingImageFilter.html BinaryMorphologicalClosingImageFilter]<br />
|-<br />
!BinaryMorphologicalOpeningImageFilter<br />
!binary morphological opening of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryMorphologicalOpeningImageFilter.html BinaryMorphologicalOpeningImageFilter]<br />
|-<br />
!BinaryNotImageFilter<br />
!Implements the BinaryNot logical operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryNotImageFilter.html BinaryNotImageFilter]<br />
|-<br />
!BinaryOpeningByReconstructionImageFilter<br />
!binary morphological closing of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryOpeningByReconstructionImageFilter.html BinaryOpeningByReconstructionImageFilter]<br />
|-<br />
!BinaryProjectionImageFilter<br />
!Binary projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryProjectionImageFilter.html BinaryProjectionImageFilter]<br />
|-<br />
!BinaryReconstructionByDilationImageFilter<br />
!binary reconstruction by dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryReconstructionByDilationImageFilter.html BinaryReconstructionByDilationImageFilter]<br />
|-<br />
!BinaryReconstructionByErosionImageFilter<br />
!binary reconstruction by erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryReconstructionByErosionImageFilter.html BinaryReconstructionByErosionImageFilter]<br />
|-<br />
!BinaryThinningImageFilter<br />
!This filter computes one-pixel-wide edges of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThinningImageFilter.html BinaryThinningImageFilter]<br />
|-<br />
!BinaryThresholdImageFilter<br />
!Binarize an input image by thresholding.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThresholdImageFilter.html BinaryThresholdImageFilter]<br />
|-<br />
!BinaryThresholdProjectionImageFilter<br />
!BinaryThreshold projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryThresholdProjectionImageFilter.html BinaryThresholdProjectionImageFilter]<br />
|-<br />
!BinomialBlurImageFilter<br />
!Performs a separable blur on each dimension of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinomialBlurImageFilter.html BinomialBlurImageFilter]<br />
|-<br />
!BitwiseNotImageFilter<br />
!Implements pixel-wise generic operation on one image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1UnaryFunctorImageFilter.html UnaryFunctorImageFilter]<br />
|-<br />
!BlackTopHatImageFilter<br />
!Black top hat extract local minima that are smaller than the structuring element.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BlackTopHatImageFilter.html BlackTopHatImageFilter]<br />
|-<br />
!BoundedReciprocalImageFilter<br />
!Computes 1/(1+x) for each pixel in the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoundedReciprocalImageFilter.html BoundedReciprocalImageFilter]<br />
|-<br />
!BoxMeanImageFilter<br />
!Implements a fast rectangular mean filter using the accumulator approach.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoxMeanImageFilter.html BoxMeanImageFilter]<br />
|-<br />
!BoxSigmaImageFilter<br />
!Implements a fast rectangular sigma filter using the accumulator approach.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BoxSigmaImageFilter.html BoxSigmaImageFilter]<br />
|-<br />
!CannyEdgeDetectionImageFilter<br />
!This filter is an implementation of a Canny edge detector for scalar-valued images. Based on John Canny's paper "A Computational Approach<br />
to Edge Detection"(IEEE Transactions on Pattern Analysis and Machine Intelligence, Vol. PAMI-8, No.6, November 1986), there are four major steps used in the edge-detection scheme: (1) Smooth the input image with Gaussian filter. (2) Calculate the second directional derivatives of the smoothed image. (3) Non-Maximum Suppression: the zero-crossings of 2nd derivative are found, and the sign of third derivative is used to find the correct extrema. (4) The hysteresis thresholding is applied to the gradient magnitude (multiplied with zero-crossings) of the smoothed image to find and link edges.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CannyEdgeDetectionImageFilter.html CannyEdgeDetectionImageFilter]<br />
|-<br />
!CastImageFilter<br />
!Casts input image's pixels to output pixel type.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CastImageFilter.html CastImageFilter]<br />
|-<br />
!CheckerBoardImageFilter<br />
!Combines two images in a checkerboard pattern.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CheckerBoardImageFilter.html CheckerBoardImageFilter]<br />
|-<br />
!ClampImageFilter<br />
!Casts input pixels to output pixel type and clamps the output pixel values to a specified range.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ClampImageFilter.html ClampImageFilter]<br />
|-<br />
!ClosingByReconstructionImageFilter<br />
!Closing by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ClosingByReconstructionImageFilter.html ClosingByReconstructionImageFilter]<br />
|-<br />
!ComplexToImaginaryImageFilter<br />
!Computes pixel-wise the imaginary part of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToImaginaryImageFilter.html ComplexToImaginaryImageFilter]<br />
|-<br />
!ComplexToModulusImageFilter<br />
!Computes pixel-wise the Modulus of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToModulusImageFilter.html ComplexToModulusImageFilter]<br />
|-<br />
!ComplexToPhaseImageFilter<br />
!Computes pixel-wise the modulus of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToPhaseImageFilter.html ComplexToPhaseImageFilter]<br />
|-<br />
!ComplexToRealImageFilter<br />
!Computes pixel-wise the real(x) part of a complex image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComplexToRealImageFilter.html ComplexToRealImageFilter]<br />
|-<br />
!ComposeImageFilter<br />
!ComposeImageFiltercombine several scalar images into a multicomponent image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComposeImageFilter.html ComposeImageFilter]<br />
|-<br />
!ConfidenceConnectedImageFilter<br />
!Segment pixels with similar statistics using connectivity.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConfidenceConnectedImageFilter.html ConfidenceConnectedImageFilter]<br />
|-<br />
!ConnectedComponentImageFilter<br />
!Label the objects in a binary image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConnectedComponentImageFilter.html ConnectedComponentImageFilter]<br />
|-<br />
!ConnectedThresholdImageFilter<br />
!Label pixels that are connected to a seed and lie within a range of values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConnectedThresholdImageFilter.html ConnectedThresholdImageFilter]<br />
|-<br />
!ConstantPadImageFilter<br />
!Increase the image size by padding with a constant value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConstantPadImageFilter.html ConstantPadImageFilter]<br />
|-<br />
!ConvolutionImageFilter<br />
!Convolve a given image with an arbitrary image kernel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ConvolutionImageFilter.html ConvolutionImageFilter]<br />
|-<br />
!CosImageFilter<br />
!Computes the cosine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CosImageFilter.html CosImageFilter]<br />
|-<br />
!CropImageFilter<br />
!Decrease the image size by cropping the image by an itk::Sizeat both the upper and lower bounds of the largest possible region.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CropImageFilter.html CropImageFilter]<br />
|-<br />
!CurvatureAnisotropicDiffusionImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CurvatureAnisotropicDiffusionImageFilter.html CurvatureAnisotropicDiffusionImageFilter]<br />
|-<br />
!CurvatureFlowImageFilter<br />
!Denoise an image using curvature driven flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CurvatureFlowImageFilter.html CurvatureFlowImageFilter]<br />
|-<br />
!CyclicShiftImageFilter<br />
!Perform a cyclic spatial shift of image intensities on the image grid.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1CyclicShiftImageFilter.html CyclicShiftImageFilter]<br />
|-<br />
!DanielssonDistanceMapImageFilter<br />
!This filter computes the distance map of the input image as an approximation with pixel accuracy to the Euclidean distance.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DanielssonDistanceMapImageFilter.html DanielssonDistanceMapImageFilter]<br />
|-<br />
!DerivativeImageFilter<br />
!Computes the directional derivative of an image. The directional derivative at each pixel location is computed by convolution with a derivative operator of user-specified order.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DerivativeImageFilter.html DerivativeImageFilter]<br />
|-<br />
!DilateObjectMorphologyImageFilter<br />
!dilation of an object in an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DilateObjectMorphologyImageFilter.html DilateObjectMorphologyImageFilter]<br />
|-<br />
!DiscreteGaussianDerivativeImageFilter<br />
!Calculates image derivatives using discrete derivative gaussian kernels. This filter calculates Gaussian derivative by separable convolution of an image and a discrete Gaussian derivative operator (kernel).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DiscreteGaussianDerivativeImageFilter.html DiscreteGaussianDerivativeImageFilter]<br />
|-<br />
!DiscreteGaussianImageFilter<br />
!Blurs an image by separable convolution with discrete gaussian kernels. This filter performs Gaussian blurring by separable convolution of an image and a discrete Gaussian operator (kernel).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DiscreteGaussianImageFilter.html DiscreteGaussianImageFilter]<br />
|-<br />
!DivideFloorImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!DivideImageFilter<br />
!Pixel-wise division of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DivideImageFilter.html DivideImageFilter]<br />
|-<br />
!DivideRealImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!DoubleThresholdImageFilter<br />
!Binarize an input image using double thresholding.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1DoubleThresholdImageFilter.html DoubleThresholdImageFilter]<br />
|-<br />
!EdgePotentialImageFilter<br />
!Computes the edge potential of an image from the image gradient.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1EdgePotentialImageFilter.html EdgePotentialImageFilter]<br />
|-<br />
!EqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!ErodeObjectMorphologyImageFilter<br />
!Erosion of an object in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ErodeObjectMorphologyImageFilter.html ErodeObjectMorphologyImageFilter]<br />
|-<br />
!ExpImageFilter<br />
!Computes the exponential function of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpImageFilter.html ExpImageFilter]<br />
|-<br />
!ExpNegativeImageFilter<br />
!Computes the function exp(-K.x) for each input pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpNegativeImageFilter.html ExpNegativeImageFilter]<br />
|-<br />
!ExpandImageFilter<br />
!Expand the size of an image by an integer factor in each dimension.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExpandImageFilter.html ExpandImageFilter]<br />
|-<br />
!ExtractImageFilter<br />
!Decrease the image size by cropping the image to the selected region bounds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ExtractImageFilter.html ExtractImageFilter]<br />
|-<br />
!FFTConvolutionImageFilter<br />
!Convolve a given image with an arbitrary image kernel using multiplication in the Fourier domain.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTConvolutionImageFilter.html FFTConvolutionImageFilter]<br />
|-<br />
!FFTNormalizedCorrelationImageFilter<br />
!Calculate normalized cross correlation using FFTs.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTNormalizedCorrelationImageFilter.html FFTNormalizedCorrelationImageFilter]<br />
|-<br />
!FFTShiftImageFilter<br />
!Shift the zero-frequency components of a Fourier transfrom to the center of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FFTShiftImageFilter.html FFTShiftImageFilter]<br />
|-<br />
!FastApproximateRankImageFilter<br />
!A separable rank filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FastApproximateRankImageFilter.html FastApproximateRankImageFilter]<br />
|-<br />
!FastMarchingImageFilter<br />
!Solve an Eikonal equation using Fast Marching.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FastMarchingImageFilterBase.html FastMarchingImageFilterBase]<br />
|-<br />
!FlipImageFilter<br />
!Flips an image across user specified axes.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1FlipImageFilter.html FlipImageFilter]<br />
|-<br />
!ForwardFFTImageFilter<br />
!Base class for forward Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ForwardFFTImageFilter.html ForwardFFTImageFilter]<br />
|-<br />
!GaborImageSource<br />
!Generate an n-dimensional image of a Gabor filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GaborImageSource.html GaborImageSource]<br />
|-<br />
!GaussianImageSource<br />
!Generate an n-dimensional image of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GaussianImageSource.html GaussianImageSource]<br />
|-<br />
!GeodesicActiveContourLevelSetImageFilter<br />
!Segments structures in images based on a user supplied edge potential map.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GeodesicActiveContourLevelSetImageFilter.html GeodesicActiveContourLevelSetImageFilter]<br />
|-<br />
!GradientAnisotropicDiffusionImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientAnisotropicDiffusionImageFilter.html GradientAnisotropicDiffusionImageFilter]<br />
|-<br />
!GradientImageFilter<br />
!Computes the gradient of an image using directional derivatives.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientImageFilter.html GradientImageFilter]<br />
|-<br />
!GradientMagnitudeImageFilter<br />
!Computes the gradient magnitude of an image region at each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientMagnitudeImageFilter.html GradientMagnitudeImageFilter]<br />
|-<br />
!GradientMagnitudeRecursiveGaussianImageFilter<br />
!Computes the Magnitude of the Gradient of an image by convolution with the first derivative of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientMagnitudeRecursiveGaussianImageFilter.html GradientMagnitudeRecursiveGaussianImageFilter]<br />
|-<br />
!GradientRecursiveGaussianImageFilter<br />
!Computes the gradient of an image by convolution with the first derivative of a Gaussian.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GradientRecursiveGaussianImageFilter.html GradientRecursiveGaussianImageFilter]<br />
|-<br />
!GrayscaleConnectedClosingImageFilter<br />
!Enhance pixels associated with a dark object (identified by a seed pixel) where the dark object is surrounded by a brigher object.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleConnectedClosingImageFilter.html GrayscaleConnectedClosingImageFilter]<br />
|-<br />
!GrayscaleConnectedOpeningImageFilter<br />
!Enhance pixels associated with a bright object (identified by a seed pixel) where the bright object is surrounded by a darker object.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleConnectedOpeningImageFilter.html GrayscaleConnectedOpeningImageFilter]<br />
|-<br />
!GrayscaleDilateImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleDilateImageFilter.html GrayscaleDilateImageFilter]<br />
|-<br />
!GrayscaleErodeImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleErodeImageFilter.html GrayscaleErodeImageFilter]<br />
|-<br />
!GrayscaleFillholeImageFilter<br />
!Remove local minima not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleFillholeImageFilter.html GrayscaleFillholeImageFilter]<br />
|-<br />
!GrayscaleGeodesicDilateImageFilter<br />
!geodesic gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGeodesicDilateImageFilter.html GrayscaleGeodesicDilateImageFilter]<br />
|-<br />
!GrayscaleGeodesicErodeImageFilter<br />
!geodesic gray scale erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGeodesicErodeImageFilter.html GrayscaleGeodesicErodeImageFilter]<br />
|-<br />
!GrayscaleGrindPeakImageFilter<br />
!Remove local maxima not connected to the boundary of the image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleGrindPeakImageFilter.html GrayscaleGrindPeakImageFilter]<br />
|-<br />
!GrayscaleMorphologicalClosingImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleMorphologicalClosingImageFilter.html GrayscaleMorphologicalClosingImageFilter]<br />
|-<br />
!GrayscaleMorphologicalOpeningImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GrayscaleMorphologicalOpeningImageFilter.html GrayscaleMorphologicalOpeningImageFilter]<br />
|-<br />
!GreaterEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!GreaterImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!GridImageSource<br />
!Generate an n-dimensional image of a grid.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1GridImageSource.html GridImageSource]<br />
|-<br />
!HConcaveImageFilter<br />
!Identify local minima whose depth below the baseline is greater than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HConcaveImageFilter.html HConcaveImageFilter]<br />
|-<br />
!HConvexImageFilter<br />
!Identify local maxima whose height above the baseline is greater than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HConvexImageFilter.html HConvexImageFilter]<br />
|-<br />
!HMaximaImageFilter<br />
!Suppress local maxima whose height above the baseline is less than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HMaximaImageFilter.html HMaximaImageFilter]<br />
|-<br />
!HMinimaImageFilter<br />
!Suppress local minima whose depth below the baseline is less than h.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HMinimaImageFilter.html HMinimaImageFilter]<br />
|-<br />
!HalfHermitianToRealInverseFFTImageFilter<br />
!Base class for specialized complex-to-real inverse Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HalfHermitianToRealInverseFFTImageFilter.html HalfHermitianToRealInverseFFTImageFilter]<br />
|-<br />
!HausdorffDistanceImageFilter<br />
!Computes the Hausdorff distance between the set of non-zero pixels of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HausdorffDistanceImageFilter.html HausdorffDistanceImageFilter]<br />
|-<br />
!HistogramMatchingImageFilter<br />
!Normalize the grayscale values between two images by histogram matching.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HistogramMatchingImageFilter.html HistogramMatchingImageFilter]<br />
|-<br />
!HuangThresholdImageFilter<br />
!Threshold an image using the Huang Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1HuangThresholdImageFilter.html HuangThresholdImageFilter]<br />
|-<br />
!IntensityWindowingImageFilter<br />
!Applies a linear transformation to the intensity levels of the input Imagethat are inside a user-defined interval. Values below this interval are mapped to a constant. Values over the interval are mapped to another constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IntensityWindowingImageFilter.html IntensityWindowingImageFilter]<br />
|-<br />
!IntermodesThresholdImageFilter<br />
!Threshold an image using the Intermodes Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IntermodesThresholdImageFilter.html IntermodesThresholdImageFilter]<br />
|-<br />
!InverseDeconvolutionImageFilter<br />
!The direct linear inverse deconvolution filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InverseDeconvolutionImageFilter.html InverseDeconvolutionImageFilter]<br />
|-<br />
!InverseFFTImageFilter<br />
!Base class for inverse Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InverseFFTImageFilter.html InverseFFTImageFilter]<br />
|-<br />
!InvertIntensityImageFilter<br />
!Invert the intensity of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1InvertIntensityImageFilter.html InvertIntensityImageFilter]<br />
|-<br />
!IsoContourDistanceImageFilter<br />
!Compute an approximate distance from an interpolated isocontour to the close grid points.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsoContourDistanceImageFilter.html IsoContourDistanceImageFilter]<br />
|-<br />
!IsoDataThresholdImageFilter<br />
!Threshold an image using the IsoData Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsoDataThresholdImageFilter.html IsoDataThresholdImageFilter]<br />
|-<br />
!IsolatedConnectedImageFilter<br />
!Label pixels that are connected to one set of seeds but not another.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsolatedConnectedImageFilter.html IsolatedConnectedImageFilter]<br />
|-<br />
!IsolatedWatershedImageFilter<br />
!Isolate watershed basins using two seeds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1IsolatedWatershedImageFilter.html IsolatedWatershedImageFilter]<br />
|-<br />
!JoinSeriesImageFilter<br />
!Join N-D images into an (N+1)-D image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1JoinSeriesImageFilter.html JoinSeriesImageFilter]<br />
|-<br />
!KittlerIllingworthThresholdImageFilter<br />
!Threshold an image using the KittlerIllingworth Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1KittlerIllingworthThresholdImageFilter.html KittlerIllingworthThresholdImageFilter]<br />
|-<br />
!LabelContourImageFilter<br />
!Labels the pixels on the border of the objects in a labeled image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelContourImageFilter.html LabelContourImageFilter]<br />
|-<br />
!LabelOverlayImageFilter<br />
!Apply a colormap to a label image and put it on top of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelOverlayImageFilter.html LabelOverlayImageFilter]<br />
|-<br />
!LabelToRGBImageFilter<br />
!Apply a colormap to a label image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelToRGBImageFilter.html LabelToRGBImageFilter]<br />
|-<br />
!LabelVotingImageFilter<br />
!This filter performs pixelwise voting among an arbitrary number of input images, where each of them represents a segmentation of the same scene (i.e., image).<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LabelVotingImageFilter.html LabelVotingImageFilter]<br />
|-<br />
!LandweberDeconvolutionImageFilter<br />
!Deconvolve an image using the Landweber deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LandweberDeconvolutionImageFilter.html LandweberDeconvolutionImageFilter]<br />
|-<br />
!LaplacianImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianImageFilter.html LaplacianImageFilter]<br />
|-<br />
!LaplacianRecursiveGaussianImageFilter<br />
!Computes the Laplacian of Gaussian (LoG) of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianRecursiveGaussianImageFilter.html LaplacianRecursiveGaussianImageFilter]<br />
|-<br />
!LaplacianSegmentationLevelSetImageFilter<br />
!Segments structures in images based on a second derivative image features.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianSegmentationLevelSetImageFilter.html LaplacianSegmentationLevelSetImageFilter]<br />
|-<br />
!LaplacianSharpeningImageFilter<br />
!This filter sharpens an image using a Laplacian. LaplacianSharpening highlights regions of rapid intensity change and therefore highlights or enhances the edges. The result is an image that appears more in focus.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LaplacianSharpeningImageFilter.html LaplacianSharpeningImageFilter]<br />
|-<br />
!LessEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!LessImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!LiThresholdImageFilter<br />
!Threshold an image using the Li Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LiThresholdImageFilter.html LiThresholdImageFilter]<br />
|-<br />
!Log10ImageFilter<br />
!Computes the log10 of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1Log10ImageFilter.html Log10ImageFilter]<br />
|-<br />
!LogImageFilter<br />
!Computes the log() of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1LogImageFilter.html LogImageFilter]<br />
|-<br />
!MagnitudeAndPhaseToComplexImageFilter<br />
!Implements pixel-wise conversion of magnitude and phase data into complex voxels.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MagnitudeAndPhaseToComplexImageFilter.html MagnitudeAndPhaseToComplexImageFilter]<br />
|-<br />
!MaskImageFilter<br />
!Mask an image with a mask.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskImageFilter.html MaskImageFilter]<br />
|-<br />
!MaskNegatedImageFilter<br />
!Mask an image with the negative of a mask.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskNegatedImageFilter.html MaskNegatedImageFilter]<br />
|-<br />
!MaskedFFTNormalizedCorrelationImageFilter<br />
!Calculate masked normalized cross correlation using FFTs.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaskedFFTNormalizedCorrelationImageFilter.html MaskedFFTNormalizedCorrelationImageFilter]<br />
|-<br />
!MaximumEntropyThresholdImageFilter<br />
!Threshold an image using the MaximumEntropy Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumEntropyThresholdImageFilter.html MaximumEntropyThresholdImageFilter]<br />
|-<br />
!MaximumImageFilter<br />
!Implements a pixel-wise operator Max(a,b) between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumImageFilter.html MaximumImageFilter]<br />
|-<br />
!MaximumProjectionImageFilter<br />
!Maximum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MaximumProjectionImageFilter.html MaximumProjectionImageFilter]<br />
|-<br />
!MeanImageFilter<br />
!Applies an averaging filter to an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MeanImageFilter.html MeanImageFilter]<br />
|-<br />
!MeanProjectionImageFilter<br />
!Mean projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MeanProjectionImageFilter.html MeanProjectionImageFilter]<br />
|-<br />
!MedianImageFilter<br />
!Applies a median filter to an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MedianImageFilter.html MedianImageFilter]<br />
|-<br />
!MedianProjectionImageFilter<br />
!Median projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MedianProjectionImageFilter.html MedianProjectionImageFilter]<br />
|-<br />
!MinMaxCurvatureFlowImageFilter<br />
!Denoise an image using min/max curvature flow.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinMaxCurvatureFlowImageFilter.html MinMaxCurvatureFlowImageFilter]<br />
|-<br />
!MinimumImageFilter<br />
!Implements a pixel-wise operator Min(a,b) between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumImageFilter.html MinimumImageFilter]<br />
|-<br />
!MinimumMaximumImageFilter<br />
!Computes the minimum and the maximum intensity values of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumMaximumImageFilter.html MinimumMaximumImageFilter]<br />
|-<br />
!MinimumProjectionImageFilter<br />
!Minimum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MinimumProjectionImageFilter.html MinimumProjectionImageFilter]<br />
|-<br />
!MirrorPadImageFilter<br />
!Increase the image size by padding with replicants of the input image value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MirrorPadImageFilter.html MirrorPadImageFilter]<br />
|-<br />
!ModulusImageFilter<br />
!Computes the modulus (x % dividend) pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ModulusImageFilter.html ModulusImageFilter]<br />
|-<br />
!MomentsThresholdImageFilter<br />
!Threshold an image using the Moments Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MomentsThresholdImageFilter.html MomentsThresholdImageFilter]<br />
|-<br />
!MorphologicalGradientImageFilter<br />
!gray scale dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalGradientImageFilter.html MorphologicalGradientImageFilter]<br />
|-<br />
!MorphologicalWatershedFromMarkersImageFilter<br />
!Morphological watershed transform from markers.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalWatershedFromMarkersImageFilter.html MorphologicalWatershedFromMarkersImageFilter]<br />
|-<br />
!MorphologicalWatershedImageFilter<br />
!TODO.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MorphologicalWatershedImageFilter.html MorphologicalWatershedImageFilter]<br />
|-<br />
!MultiplyImageFilter<br />
!Pixel-wise multiplication of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1MultiplyImageFilter.html MultiplyImageFilter]<br />
|-<br />
!N4BiasFieldCorrectionImageFilter<br />
!Implementation of the N4 bias field correction algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1N4BiasFieldCorrectionImageFilter.html N4BiasFieldCorrectionImageFilter]<br />
|-<br />
!NaryAddImageFilter<br />
!Pixel-wise addition of N images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NaryAddImageFilter.html NaryAddImageFilter]<br />
|-<br />
!NaryMaximumImageFilter<br />
!Computes the pixel-wise maximum of several images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NaryMaximumImageFilter.html NaryMaximumImageFilter]<br />
|-<br />
!NeighborhoodConnectedImageFilter<br />
!Label pixels that are connected to a seed and lie within a neighborhood.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NeighborhoodConnectedImageFilter.html NeighborhoodConnectedImageFilter]<br />
|-<br />
!NoiseImageFilter<br />
!Calculate the local noise in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NoiseImageFilter.html NoiseImageFilter]<br />
|-<br />
!NormalizeImageFilter<br />
!Normalize an image by setting its mean to zero and variance to one.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizeImageFilter.html NormalizeImageFilter]<br />
|-<br />
!NormalizeToConstantImageFilter<br />
!Scales image pixel intensities to make the sum of all pixels equal a user-defined constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizeToConstantImageFilter.html NormalizeToConstantImageFilter]<br />
|-<br />
!NormalizedCorrelationImageFilter<br />
!Computes the normalized correlation of an image and a template.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NormalizedCorrelationImageFilter.html NormalizedCorrelationImageFilter]<br />
|-<br />
!NotEqualImageFilter<br />
!Implements pixel-wise generic operation of two images, or of an image and a constant.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1BinaryFunctorImageFilter.html BinaryFunctorImageFilter]<br />
|-<br />
!NotImageFilter<br />
!Implements the NOT logical operator pixel-wise on an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1NotImageFilter.html NotImageFilter]<br />
|-<br />
!OpeningByReconstructionImageFilter<br />
!Opening by reconstruction of an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OpeningByReconstructionImageFilter.html OpeningByReconstructionImageFilter]<br />
|-<br />
!OrImageFilter<br />
!Implements the OR bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OrImageFilter.html OrImageFilter]<br />
|-<br />
!OtsuMultipleThresholdsImageFilter<br />
!Threshold an image using multiple Otsu Thresholds.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OtsuMultipleThresholdsImageFilter.html OtsuMultipleThresholdsImageFilter]<br />
|-<br />
!OtsuThresholdImageFilter<br />
!Threshold an image using the Otsu Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1OtsuThresholdImageFilter.html OtsuThresholdImageFilter]<br />
|-<br />
!PasteImageFilter<br />
!Paste an image into another image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PasteImageFilter.html PasteImageFilter]<br />
|-<br />
!PatchBasedDenoisingImageFilter<br />
!Derived class implementing a specific patch-based denoising algorithm, as detailed below.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PatchBasedDenoisingImageFilter.html PatchBasedDenoisingImageFilter]<br />
|-<br />
!PermuteAxesImageFilter<br />
!Permutes the image axes according to a user specified order.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PermuteAxesImageFilter.html PermuteAxesImageFilter]<br />
|-<br />
!PhysicalPointImageSource<br />
!Generate an image of the physical locations of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PhysicalPointImageSource.html PhysicalPointImageSource]<br />
|-<br />
!PowImageFilter<br />
!Computes the powers of 2 images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1PowImageFilter.html PowImageFilter]<br />
|-<br />
!ProjectedLandweberDeconvolutionImageFilter<br />
!Deconvolve an image using the projected Landweber deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ProjectedLandweberDeconvolutionImageFilter.html ProjectedLandweberDeconvolutionImageFilter]<br />
|-<br />
!RankImageFilter<br />
!Rank filter of a greyscale image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RankImageFilter.html RankImageFilter]<br />
|-<br />
!RealAndImaginaryToComplexImageFilter<br />
!ComposeImageFiltercombine several scalar images into a multicomponent image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ComposeImageFilter.html ComposeImageFilter]<br />
|-<br />
!RealToHalfHermitianForwardFFTImageFilter<br />
!Base class for specialized real-to-complex forward Fast Fourier Transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RealToHalfHermitianForwardFFTImageFilter.html RealToHalfHermitianForwardFFTImageFilter]<br />
|-<br />
!ReconstructionByDilationImageFilter<br />
!grayscale reconstruction by dilation of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ReconstructionByDilationImageFilter.html ReconstructionByDilationImageFilter]<br />
|-<br />
!ReconstructionByErosionImageFilter<br />
!grayscale reconstruction by erosion of an image<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ReconstructionByErosionImageFilter.html ReconstructionByErosionImageFilter]<br />
|-<br />
!RecursiveGaussianImageFilter<br />
!Base class for computing IIR convolution with an approximation of a Gaussian kernel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RecursiveGaussianImageFilter.html RecursiveGaussianImageFilter]<br />
|-<br />
!RegionOfInterestImageFilter<br />
!Extract a region of interest from the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionOfInterestImageFilter.html RegionOfInterestImageFilter]<br />
|-<br />
!RegionalMaximaImageFilter<br />
!Produce a binary image where foreground is the regional maxima of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionalMaximaImageFilter.html RegionalMaximaImageFilter]<br />
|-<br />
!RegionalMinimaImageFilter<br />
!Produce a binary image where foreground is the regional minima of the input image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RegionalMinimaImageFilter.html RegionalMinimaImageFilter]<br />
|-<br />
!RelabelComponentImageFilter<br />
!Relabel the components in an image such that consecutive labels are used.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RelabelComponentImageFilter.html RelabelComponentImageFilter]<br />
|-<br />
!RenyiEntropyThresholdImageFilter<br />
!Threshold an image using the RenyiEntropy Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RenyiEntropyThresholdImageFilter.html RenyiEntropyThresholdImageFilter]<br />
|-<br />
!ResampleImageFilter<br />
!Resample an image via a coordinate transform.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ResampleImageFilter.html ResampleImageFilter]<br />
|-<br />
!RescaleIntensityImageFilter<br />
!Applies a linear transformation to the intensity levels of the input Image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RescaleIntensityImageFilter.html RescaleIntensityImageFilter]<br />
|-<br />
!RichardsonLucyDeconvolutionImageFilter<br />
!Deconvolve an image using the Richardson-Lucy deconvolution algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1RichardsonLucyDeconvolutionImageFilter.html RichardsonLucyDeconvolutionImageFilter]<br />
|-<br />
!STAPLEImageFilter<br />
!The STAPLE filter implements the Simultaneous Truth and Performance Level Estimation algorithm for generating ground truth volumes from a set of binary expert segmentations.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1STAPLEImageFilter.html STAPLEImageFilter]<br />
|-<br />
!ScalarChanAndVeseDenseLevelSetImageFilter<br />
!Dense implementation of the Chan and Vese multiphase level set image filter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarChanAndVeseDenseLevelSetImageFilter.html ScalarChanAndVeseDenseLevelSetImageFilter]<br />
|-<br />
!ScalarConnectedComponentImageFilter<br />
!A connected components filter that labels the objects in an arbitrary image. Two pixels are similar if they are within threshold of each other. Uses ConnectedComponentFunctorImageFilter.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarConnectedComponentImageFilter.html ScalarConnectedComponentImageFilter]<br />
|-<br />
!ScalarImageKmeansImageFilter<br />
!Classifies the intensity values of a scalar image using the K-Means algorithm.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarImageKmeansImageFilter.html ScalarImageKmeansImageFilter]<br />
|-<br />
!ScalarToRGBColormapImageFilter<br />
!Implements pixel-wise intensity->rgb mapping operation on one image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ScalarToRGBColormapImageFilter.html ScalarToRGBColormapImageFilter]<br />
|-<br />
!ShanbhagThresholdImageFilter<br />
!Threshold an image using the Shanbhag Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShanbhagThresholdImageFilter.html ShanbhagThresholdImageFilter]<br />
|-<br />
!ShapeDetectionLevelSetImageFilter<br />
!Segments structures in images based on a user supplied edge potential map.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShapeDetectionLevelSetImageFilter.html ShapeDetectionLevelSetImageFilter]<br />
|-<br />
!ShiftScaleImageFilter<br />
!Shift and scale the pixels in an image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShiftScaleImageFilter.html ShiftScaleImageFilter]<br />
|-<br />
!ShrinkImageFilter<br />
!Reduce the size of an image by an integer factor in each dimension.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ShrinkImageFilter.html ShrinkImageFilter]<br />
|-<br />
!SigmoidImageFilter<br />
!Computes the sigmoid function pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SigmoidImageFilter.html SigmoidImageFilter]<br />
|-<br />
!SignedDanielssonDistanceMapImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SignedDanielssonDistanceMapImageFilter.html SignedDanielssonDistanceMapImageFilter]<br />
|-<br />
!SignedMaurerDistanceMapImageFilter<br />
!This filter calculates the Euclidean distance transform of a binary image in linear time for arbitrary dimensions.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SignedMaurerDistanceMapImageFilter.html SignedMaurerDistanceMapImageFilter]<br />
|-<br />
!SimilarityIndexImageFilter<br />
!Measures the similarity between the set of non-zero pixels of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SimilarityIndexImageFilter.html SimilarityIndexImageFilter]<br />
|-<br />
!SimpleContourExtractorImageFilter<br />
!Computes an image of contours which will be the contour of the first image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SimpleContourExtractorImageFilter.html SimpleContourExtractorImageFilter]<br />
|-<br />
!SinImageFilter<br />
!Computes the sine of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SinImageFilter.html SinImageFilter]<br />
|-<br />
!SliceImageFilter<br />
!<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SliceImageFilter.html SliceImageFilter]<br />
|-<br />
!SmoothingRecursiveGaussianImageFilter<br />
!Computes the smoothing of an image by convolution with the Gaussian kernels implemented as IIR filters.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SmoothingRecursiveGaussianImageFilter.html SmoothingRecursiveGaussianImageFilter]<br />
|-<br />
!SobelEdgeDetectionImageFilter<br />
!A 2D or 3D edge detection using the Sobel operator.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SobelEdgeDetectionImageFilter.html SobelEdgeDetectionImageFilter]<br />
|-<br />
!SqrtImageFilter<br />
!Computes the square root of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SqrtImageFilter.html SqrtImageFilter]<br />
|-<br />
!SquareImageFilter<br />
!Computes the square of the intensity values pixel-wise.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SquareImageFilter.html SquareImageFilter]<br />
|-<br />
!SquaredDifferenceImageFilter<br />
!Implements pixel-wise the computation of squared difference.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SquaredDifferenceImageFilter.html SquaredDifferenceImageFilter]<br />
|-<br />
!StandardDeviationProjectionImageFilter<br />
!Mean projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1StandardDeviationProjectionImageFilter.html StandardDeviationProjectionImageFilter]<br />
|-<br />
!SubtractImageFilter<br />
!Pixel-wise subtraction of two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SubtractImageFilter.html SubtractImageFilter]<br />
|-<br />
!SumProjectionImageFilter<br />
!Sum projection.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1SumProjectionImageFilter.html SumProjectionImageFilter]<br />
|-<br />
!TanImageFilter<br />
!Computes the tangent of each input pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TanImageFilter.html TanImageFilter]<br />
|-<br />
!TernaryAddImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryAddImageFilter.html TernaryAddImageFilter]<br />
|-<br />
!TernaryMagnitudeImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryMagnitudeImageFilter.html TernaryMagnitudeImageFilter]<br />
|-<br />
!TernaryMagnitudeSquaredImageFilter<br />
!Pixel-wise addition of three images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TernaryMagnitudeSquaredImageFilter.html TernaryMagnitudeSquaredImageFilter]<br />
|-<br />
!ThresholdImageFilter<br />
!Set image values to a user-specified value if they are below, above, or between simple threshold values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdImageFilter.html ThresholdImageFilter]<br />
|-<br />
!ThresholdMaximumConnectedComponentsImageFilter<br />
!Finds the threshold value of an image based on maximizing the number of objects in the image that are larger than a given minimal size.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdMaximumConnectedComponentsImageFilter.html ThresholdMaximumConnectedComponentsImageFilter]<br />
|-<br />
!ThresholdSegmentationLevelSetImageFilter<br />
!Segments structures in images based on intensity values.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ThresholdSegmentationLevelSetImageFilter.html ThresholdSegmentationLevelSetImageFilter]<br />
|-<br />
!TikhonovDeconvolutionImageFilter<br />
!An inverse deconvolution filter regularized in the Tikhonov sense.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TikhonovDeconvolutionImageFilter.html TikhonovDeconvolutionImageFilter]<br />
|-<br />
!TileImageFilter<br />
!Tile multiple input images into a single output image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TileImageFilter.html TileImageFilter]<br />
|-<br />
!TriangleThresholdImageFilter<br />
!Threshold an image using the Triangle Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1TriangleThresholdImageFilter.html TriangleThresholdImageFilter]<br />
|-<br />
!UnaryMinusImageFilter<br />
!Computes the negative of each pixel.<br />
<br />
![http://www.itk.org/Doxygen/html/classitk_1_1UnaryFunctorImageFilter.html UnaryFunctorImageFilter]<br />
|-<br />
!ValuedRegionalMaximaImageFilter<br />
!Transforms the image so that any pixel that is not a regional maxima is set to the minimum value for the pixel type. Pixels that are regional maxima retain their value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ValuedRegionalMaximaImageFilter.html ValuedRegionalMaximaImageFilter]<br />
|-<br />
!ValuedRegionalMinimaImageFilter<br />
!Transforms the image so that any pixel that is not a regional minima is set to the maximum value for the pixel type. Pixels that are regional minima retain their value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ValuedRegionalMinimaImageFilter.html ValuedRegionalMinimaImageFilter]<br />
|-<br />
!VectorConfidenceConnectedImageFilter<br />
!Segment pixels with similar statistics using connectivity.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorConfidenceConnectedImageFilter.html VectorConfidenceConnectedImageFilter]<br />
|-<br />
!VectorConnectedComponentImageFilter<br />
!A connected components filter that labels the objects in a vector image. Two vectors are pointing similar directions if one minus their dot product is less than a threshold. Vectors that are 180 degrees out of phase are similar. Assumes that vectors are normalized.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorConnectedComponentImageFilter.html VectorConnectedComponentImageFilter]<br />
|-<br />
!VectorIndexSelectionCastImageFilter<br />
!Extracts the selected index of the vector that is the input pixel type.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorIndexSelectionCastImageFilter.html VectorIndexSelectionCastImageFilter]<br />
|-<br />
!VectorMagnitudeImageFilter<br />
!Take an image of vectors as input and produce an image with the magnitude of those vectors.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VectorMagnitudeImageFilter.html VectorMagnitudeImageFilter]<br />
|-<br />
!VotingBinaryHoleFillingImageFilter<br />
!Fills in holes and cavities by applying a voting operation on each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryHoleFillingImageFilter.html VotingBinaryHoleFillingImageFilter]<br />
|-<br />
!VotingBinaryImageFilter<br />
!Applies a voting operation in a neighborhood of each pixel.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryImageFilter.html VotingBinaryImageFilter]<br />
|-<br />
!VotingBinaryIterativeHoleFillingImageFilter<br />
!Fills in holes and cavities by iteratively applying a voting operation.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1VotingBinaryIterativeHoleFillingImageFilter.html VotingBinaryIterativeHoleFillingImageFilter]<br />
|-<br />
!WarpImageFilter<br />
!Warps an image using an input displacement field.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WarpImageFilter.html WarpImageFilter]<br />
|-<br />
!WhiteTopHatImageFilter<br />
!White top hat extract local maxima that are larger than the structuring element.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WhiteTopHatImageFilter.html WhiteTopHatImageFilter]<br />
|-<br />
!WienerDeconvolutionImageFilter<br />
!The Wiener deconvolution image filter is designed to restore an image convolved with a blurring kernel while keeping noise enhancement to a minimum.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WienerDeconvolutionImageFilter.html WienerDeconvolutionImageFilter]<br />
|-<br />
!WrapPadImageFilter<br />
!Increase the image size by padding with replicants of the input image value.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1WrapPadImageFilter.html WrapPadImageFilter]<br />
|-<br />
!XorImageFilter<br />
!Computes the XOR bitwise operator pixel-wise between two images.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1XorImageFilter.html XorImageFilter]<br />
|-<br />
!YenThresholdImageFilter<br />
!Threshold an image using the Yen Threshold.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1YenThresholdImageFilter.html YenThresholdImageFilter]<br />
|-<br />
!ZeroCrossingBasedEdgeDetectionImageFilter<br />
!This filter implements a zero-crossing based edge detecor.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroCrossingBasedEdgeDetectionImageFilter.html ZeroCrossingBasedEdgeDetectionImageFilter]<br />
|-<br />
!ZeroCrossingImageFilter<br />
!This filter finds the closest pixel to the zero-crossings (sign changes) in a signed itk::Image.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroCrossingImageFilter.html ZeroCrossingImageFilter]<br />
|-<br />
!ZeroFluxNeumannPadImageFilter<br />
!Increase the image size by padding according to the zero-flux Neumann boundary condition.<br />
![http://www.itk.org/Doxygen/html/classitk_1_1ZeroFluxNeumannPadImageFilter.html ZeroFluxNeumannPadImageFilter]<br />
|}<br />
<!-- END TABLE --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}github.com/SimpleITK/SlicerSimpleFilters<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=63551Documentation/Nightly/ScriptRepository2021-04-02T20:59:45Z<p>Pieper: /* Switch to a different module */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Get node object from the scene from node name or ID==<br />
<br />
Examples in the script repository commonly use <code>slicer.util.getNode()</code> function for getting a node object from the scene. This method is only recommended for testing and interactive debugging. <br />
<br />
*<code>slicer.util.getNode()</code> is recommended **only for interactive debugging** in the Python console/Jupyter notebook<br />
**its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as <code>*</code>), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module<br />
**throws an exception so that the developer knows immediately that there was a typo or other unexpected error<br />
*<code>slicer.mrmlScene.GetNodeByID()</code> is optimized for usage in modules:<br />
**its behavior is more predictable: it only accepts node ID as input. <code>slicer.mrmlScene.GetFirstNodeByName()</code> 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.<br />
**if node is not found it returns <code>None</code> (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<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})<br />
</pre><br />
<br />
Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
<br />
==Load volume from URL==<br />
<br />
Download a volume from a URL and load it into the scene using the code snippet below. Downloaded data is temporarily preserved in the application's cache folder and if the checksum of the already downloaded data matches the specified checksum (<algo>:<digest>) then the file is retrieved from the cache instead of being downloaded again. To compute digest with algo ''SHA256'', you can run <code>slicer.util.computeChecksum("SHA256", "path/to/file")</code>.<br />
<br />
<pre><br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')[0]<br />
</pre><br />
<br />
With interruptible progress reporting using a progress bar:<br />
<br />
<pre><br />
import SampleData<br />
<br />
def reportProgress(msg, level=None):<br />
# Print progress in the console<br />
print("Loading... {0}%".format(sampleDataLogic.downloadPercent))<br />
# Abort download if cancel is clicked in progress bar<br />
if slicer.progressWindow.wasCanceled:<br />
raise Exception('download aborted')<br />
# Update progress window<br />
slicer.progressWindow.show()<br />
slicer.progressWindow.activateWindow()<br />
slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))<br />
slicer.progressWindow.setLabelText("Downloading...")<br />
# Process events to allow screen to refresh<br />
slicer.app.processEvents() <br />
<br />
try:<br />
volumeNode = None<br />
slicer.progressWindow = slicer.util.createProgressDialog()<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
sampleDataLogic.logMessage = reportProgress<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')<br />
volumeNode = loadedNodes[0]<br />
finally:<br />
slicer.progressWindow.close()<br />
</pre><br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
<pre><br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
</pre><br />
<br />
===How to import DICOM files into the application's DICOM database===<br />
<br />
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.<br />
<br />
<pre><br />
# instantiate a new DICOM browser<br />
slicer.util.selectModule("DICOM")<br />
dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser<br />
# use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)<br />
dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)<br />
# wait for import to finish before proceeding (optional, if removed then import runs in the background)<br />
dicomBrowser.waitForImportFinished()<br />
</pre><br />
<br />
===How to import DICOM files using DICOMweb===<br />
<br />
Download and import DICOM data set using DICOMweb from [https://kheops.online/ Kheops], Google Health API, etc.<br />
<br />
How to obtain accessToken:<br />
<br />
*Google Cloud: Execute <code>gcloud auth print-access-token</code> once you have logged in<br />
*Kheops: create an album, create a sharing link (somethin like <code>https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf</code>), the token is the string after the last slash<br />
<br />
<pre><br />
slicer.util.selectModule("DICOM") # ensure DICOM database is initialized and <br />
slicer.app.processEvents()<br />
from DICOMLib import DICOMUtils<br />
DICOMUtils.importFromDICOMWeb(<br />
dicomWebEndpoint="http://demo.kheops.online/api",<br />
studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",<br />
accessToken="TfYXwbKAW7JYbAgZ7MyISf")<br />
</pre><br />
<br />
===How to access top level tags of DICOM images imported into Slicer?===<br />
<br />
For example, to print the first patient's first study's first series' "0020,0032" field:<br />
<br />
<pre><br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
# Get tag group,number from dicom dictionary<br />
import pydicom as dicom<br />
tagName = "StudyDate"<br />
tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(' ','')<br />
print(db.fileValue(fileList[0],tagStr))<br />
</pre><br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Export a segmentation to DICOM segmentation object===<br />
<br />
<pre><br />
segmentationNode = ...<br />
referenceVolumeNode = ...<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Associate segmentation node with a reference volume node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)<br />
studyShItem = shNode.GetItemParent(referenceVolumeShItem)<br />
segmentationShItem = shNode.GetItemByDataNode(segmentationNode)<br />
shNode.SetItemParent(segmentationShItem, studyShItem)<br />
<br />
# Export to DICOM<br />
import DICOMSegmentationPlugin<br />
exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()<br />
exportables = exporter.examineForExport(segmentationShItem)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
Documentation of methods for changing DICOM browser columns: https://github.com/commontk/CTK/blob/master/Libs/DICOM/Core/ctkDICOMDatabase.h#L354-L375<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser<br />
dicomDatabase = dicomBrowser.database()<br />
<br />
# Print list of available columns<br />
print(dicomDatabase.patientFieldNames)<br />
print(dicomDatabase.studyFieldNames)<br />
print(dicomDatabase.seriesFieldNames)<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsComments', True)<br />
dicomDatabase.setWeightForField('Patients', 'PatientsComments', 8)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Change column width to manual<br />
dicomDatabase.setFormatForField('Series', 'SeriesDescription', '{"resizeMode":"interactive"}')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
<br />
# Force database views update<br />
dicomDatabase.closeDatabase()<br />
dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)<br />
</pre><br />
<br />
===Query and retrieve data from a PACS using classic DIMSE DICOM networking===<br />
<br />
<pre><br />
# Query<br />
dicomQuery = ctk.ctkDICOMQuery()<br />
dicomQuery.callingAETitle = "SLICER"<br />
dicomQuery.calledAETitle = "ANYAE"<br />
dicomQuery.host = "dicomserver.co.uk"<br />
dicomQuery.port = 11112<br />
dicomQuery.preferCGET = True<br />
dicomQuery.filters = {'Name':'Anon', 'Modalities':'MR'}<br />
# temporary in-memory database for storing query results<br />
tempDb = ctk.ctkDICOMDatabase()<br />
tempDb.openDatabase('')<br />
dicomQuery.query(tempDb)<br />
<br />
# Retrieve<br />
dicomRetrieve = ctk.ctkDICOMRetrieve()<br />
dicomRetrieve.callingAETitle = dicomQuery.callingAETitle<br />
dicomRetrieve.calledAETitle = dicomQuery.calledAETitle<br />
dicomRetrieve.host = dicomQuery.host<br />
dicomRetrieve.port = dicomQuery.port<br />
dicomRetrieve.setMoveDestinationAETitle("SLICER");<br />
dicomRetrieve.setDatabase(slicer.dicomDatabase)<br />
for study in dicomQuery.studyInstanceUIDQueried:<br />
print(f"ctkDICOMRetrieveTest2: Retrieving {study}")<br />
slicer.app.processEvents()<br />
if dicomQuery.preferCGET:<br />
success = dicomRetrieve.getStudy(study)<br />
else:<br />
success = dicomRetrieve.moveStudy(study)<br />
print(f" - {'success' if success else 'failed'}")<br />
slicer.dicomDatabase.updateDisplayedFields()<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Switch to a different module==<br />
<br />
This utility function can be used to open a different module:<br />
<br />
<pre><br />
slicer.util.selectModule('DICOM')<br />
</pre><br />
<br />
==Set a new default module at startup==<br />
<br />
Instead of the default Welcome module:<br />
<br />
<pre><br />
qt.QSettings().setValue("Modules/HomeModule", "Data")<br />
</pre><br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
===How to define/edit a circular region of interest in a slice viewer?===<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
===Specify a sphere by multiple of markups points===<br />
<br />
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.<br />
<br />
<pre><br />
# Get markup node from scene<br />
markups = slicer.util.getNode('F')<br />
<br />
from scipy.optimize import least_squares<br />
import numpy<br />
<br />
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))):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Uses scipy's least squares optimisor to fit a sphere to a set<br />
of 3D Points<br />
:return: x: an array containing the four fitted parameters<br />
:return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the<br />
solution was found.<br />
:param: (x,y,z) three arrays of equal length containing the x, y, and z<br />
coordinates.<br />
:param: an array containing four initial values (centre, and radius)<br />
"""<br />
return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method='trf', jac='3-point', args=(x_values, y_values, z_values))<br />
<br />
def _calculate_residual_sphere(parameters, x_values, y_values, z_values):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Calculates the residual error for an x,y,z coordinates, fitted<br />
to a sphere with centre and radius defined by the parameters tuple<br />
:return: The residual error<br />
:param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]<br />
:param: arrays containing the x,y, and z coordinates.<br />
"""<br />
#extract the parameters<br />
x_centre, y_centre, z_centre, radius = parameters<br />
#use numpy's sqrt function here, which works by element on arrays<br />
distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)<br />
return distance_from_centre - radius<br />
<br />
# Fit a sphere to the markups fidicual points<br />
markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)<br />
import numpy as np<br />
# initial guess<br />
center0 = np.mean(markupsPositions, 0)<br />
radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0<br />
fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])<br />
[centerX, centerY, centerZ, radius] = fittingResult['x']<br />
<br />
# Create a sphere using the fitted parameters<br />
sphere = vtk.vtkSphereSource()<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.SetCenter(centerX, centerY, centerZ)<br />
sphere.SetRadius(radius)<br />
sphere.Update()<br />
<br />
# Add the sphere to the scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup planes==<br />
<br />
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
planeNodeNames = ['P', 'P_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
planeNormalVectors = []<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNormalVector = [0.0, 0.0, 0.0]<br />
planeNode.GetNormalWorld(planeNormalVector)<br />
planeNormalVectors.append(planeNormalVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))<br />
<br />
# Observe plane node changes<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup lines==<br />
<br />
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
lineNodeNames = ['L', 'L_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
import numpy as np<br />
lineDirectionVectors = []<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineStartPos = np.zeros(3)<br />
lineEndPos = np.zeros(3)<br />
lineNode.GetNthControlPointPositionWorld(0, lineStartPos)<br />
lineNode.GetNthControlPointPositionWorld(1, lineEndPos)<br />
lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)<br />
lineDirectionVectors.append(lineDirectionVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))<br />
<br />
# Observe line node changes<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Extract randomly oriented slabs of given shape from a volume==<br />
<br />
Returns a numpy array of sliceCount random tiles.<br />
<br />
<pre><br />
def randomSlices(volume, sliceCount, sliceShape):<br />
layoutManager = slicer.app.layoutManager()<br />
redWidget = layoutManager.sliceWidget('Red')<br />
sliceNode = redWidget.mrmlSliceNode()<br />
sliceNode.SetDimensions(*sliceShape, 1)<br />
sliceNode.SetFieldOfView(*sliceShape, 1)<br />
bounds = [0]*6<br />
volume.GetRASBounds(bounds)<br />
imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()<br />
<br />
sliceSize = sliceShape[0] * sliceShape[1]<br />
X = numpy.zeros([sliceCount, sliceSize])<br />
<br />
for sliceIndex in range(sliceCount):<br />
position = numpy.random.rand(3) * 2 - 1<br />
position = [bounds[0] + bounds[1]-bounds[0] * position[0],<br />
bounds[2] + bounds[3]-bounds[2] * position[1],<br />
bounds[4] + bounds[5]-bounds[4] * position[2]]<br />
normal = numpy.random.rand(3) * 2 - 1<br />
normal = normal / numpy.linalg.norm(normal)<br />
transverse = numpy.cross(normal, [0,0,1])<br />
orientation = 0<br />
sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2], <br />
transverse[0], transverse[1], transverse[2], <br />
position[0], position[1], position[2],<br />
orientation) <br />
if sliceIndex % 100 == 0:<br />
slicer.app.processEvents()<br />
imageReslice.Update()<br />
imageData = imageReslice.GetOutputDataObject(0)<br />
array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())<br />
X[sliceIndex] = array<br />
return X<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligned coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.<br />
<br />
<pre><br />
<br />
def viewContextMenuActions(self):<br />
return [self.doSomething]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData):<br />
if not itemID:<br />
logging.error('Invalid item for view context menu ' + str(itemID))<br />
return<br />
<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
shNode = pluginHandlerSingleton.subjectHierarchyNode()<br />
if shNode is None:<br />
logging.error('Failed to access subject hierarchy node')<br />
return<br />
<br />
associatedNode = shNode.GetItemDataNode(itemID)<br />
if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):<br />
return<br />
<br />
self.viewMenuEventData = eventData<br />
self.viewMenuEventData['NodeID'] = associatedNode.GetID()<br />
<br />
def onDoSomething(self):<br />
nodeID = self.viewMenuEventData['NodeID']<br />
markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)<br />
if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):<br />
logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))<br />
return<br />
<br />
componentIndex = self.viewMenuEventData['ComponentIndex']<br />
markupID = markupsNode.GetNthMarkupID(componentIndex)<br />
<br />
# Do something with the clicked fiducial<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# Create model node<br />
planeSource = vtk.vtkPlaneSource()<br />
planeSource.SetOrigin(-50.0, -50.0, 0.0)<br />
planeSource.SetPoint1(50.0, -50.0, 0.0)<br />
planeSource.SetPoint2(-50.0, 50.0, 0.0)<br />
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())<br />
<br />
# Tune display properties<br />
modelDisplay = model.GetDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
<br />
# Add texture (just use image of an ellipsoid)<br />
e = vtk.vtkImageEllipsoidSource()<br />
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Apply VTK filter on a model node==<br />
<br />
<pre><br />
modelNode = getNode('tip')<br />
<br />
# Compute curvature<br />
curv = vtk.vtkCurvatures()<br />
curv.SetInputData(modelNode.GetPolyData())<br />
modelNode.SetPolyDataConnection(curv.GetOutputPort())<br />
<br />
# Set up coloring by Curvature<br />
modelNode.GetDisplayNode().SetActiveScalar("Gauss_Curvature", vtk.vtkAssignAttribute.POINT_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("Viridis")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Load volume from .vti file==<br />
<br />
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:<br />
<br />
<pre><br />
reader=vtk.vtkXMLImageDataReader()<br />
reader.SetFileName("/path/to/file.vti")<br />
reader.Update()<br />
imageData = reader.GetOutput()<br />
spacing = imageData.GetSpacing()<br />
origin = imageData.GetOrigin()<br />
imageData.SetOrigin(0,0,0)<br />
imageData.SetSpacing(1,1,1)<br />
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.SetSpacing(spacing)<br />
volumeNode.SetOrigin(origin)<br />
slicer.util.setSliceViewerLayers(volumeNode, fit=True)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array('Volume')<br />
label = array('Volume-label')<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt('values.txt', values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Show comparison view of all model files a folder==<br />
<br />
<pre><br />
# Inputs<br />
modelDir = "c:/some/folder/containing/models"<br />
modelFileExt = "stl"<br />
numberOfColumns = 4<br />
<br />
import math<br />
import os<br />
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))<br />
<br />
# Create a custom layout<br />
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))<br />
customLayoutId=567 # we pick a random id that is not used by others<br />
slicer.app.setRenderPaused(True)<br />
customLayout = '<layout type="vertical">'<br />
viewIndex = 0<br />
for rowIndex in range(numberOfRows):<br />
customLayout += '<item><layout type="horizontal">'<br />
for colIndex in range(numberOfColumns):<br />
name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)<br />
customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name<br />
customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'<br />
viewIndex += 1<br />
customLayout += '</layout></item>'<br />
<br />
customLayout += '</layout>'<br />
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):<br />
slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)<br />
<br />
slicer.app.layoutManager().setLayout(customLayoutId)<br />
<br />
# Load and show each model in a view<br />
for modelIndex, modelFile in enumerate(modelFiles):<br />
# Show only one model in each view<br />
name = os.path.basename(modelFile)<br />
viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")<br />
viewNode.LinkedControlOn()<br />
modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)<br />
modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())<br />
<br />
slicer.app.setRenderPaused(False)<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Override default scene save dialog==<br />
<br />
Place this class in the scripted module file to override<br />
<br />
<pre><br />
class MyModuleFileDialog ():<br />
"""This specially named class is detected by the scripted loadable<br />
module and is the target for optional drag and drop operations.<br />
See: Base/QTGUI/qSlicerScriptedFileDialog.h.<br />
<br />
This class is used for overriding default scene save dialog<br />
with simple saving the scene without asking anything.<br />
"""<br />
<br />
def __init__(self,qSlicerFileDialog ):<br />
self.qSlicerFileDialog = qSlicerFileDialog<br />
qSlicerFileDialog.fileType = 'NoFile'<br />
qSlicerFileDialog.description = 'Save scene'<br />
qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write<br />
<br />
def execDialog(self):<br />
# Implement custom scene save operation here.<br />
# Return True if saving completed successfully,<br />
# return False if saving was cancelled.<br />
...<br />
return saved<br />
</pre><br />
<br />
==Override application close behavior==<br />
<br />
When application close is requested then by default confirmation popup is displayed.<br />
To customize this behavior (for example, allow application closing without displaying default confirmation popup)<br />
an event filter can be installed for the close event on the main window:<br />
<br />
<pre><br />
class CloseApplicationEventFilter(qt.QWidget):<br />
def eventFilter(self, object, event):<br />
if event.type() == qt.QEvent.Close:<br />
event.accept()<br />
return True<br />
return False<br />
<br />
filter = CloseApplicationEventFilter()<br />
slicer.util.mainWindow().installEventFilter(filter)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
view=slicer.app.layoutManager().sliceWidget("Red").sliceView()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Customize keyboard/mouse gestures in viewers==<br />
<br />
Example for making the 3D view rotate using right-click-and-drag:<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLCameraDisplayableManager')<br />
cameraWidget = cameraDisplayableManager.GetCameraWidget()<br />
<br />
# Remove old mapping from right-click-and-drag<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)<br />
<br />
# Make right-click-and-drag rotate the view<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode.UnRegister(None) # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Add keyboard shortcut to jump to center or world coordinate system==<br />
<br />
You can copy-paste this into the Python console to jump slice views to (0,0,0) position on (Ctrl+e):<br />
<br />
<pre><br />
shortcut = qt.QShortcut(qt.QKeySequence('Ctrl+e'), slicer.util.mainWindow())<br />
shortcut.connect('activated()',<br />
lambda: slicer.modules.markups.logic().JumpSlicesToLocation(0,0,0, True))<br />
</pre><br />
<br />
==Display mouse pointer coordinates in alternative coordinate system==<br />
<br />
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 [https://discourse.slicer.org/t/setting-an-mni-origo-to-a-volume/16164/4 here].<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid):<br />
mniToWorldTransformNode = getNode('LinearTransform_3') # replace this by the name of your actual MNI to world transform<br />
worldToMniTransform = vtk.vtkGeneralTransform()<br />
mniToWorldTransformNode.GetTransformToWorld(worldToMniTransform)<br />
ras=[0,0,0]<br />
mni=[0,0,0] <br />
crosshairNode.GetCursorPositionRAS(ras)<br />
worldToMniTransform.TransformPoint(ras, mni)<br />
_ras = "; ".join([str(k) for k in ras])<br />
_mni = "; ".join([str(k) for k in mni])<br />
slicer.util.showStatusMessage(f"RAS={_ras} MNI={_mni}")<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair')<br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# Run this to stop displaying values:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show orientation marker in all views==<br />
<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLAbstractViewNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
# 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<br />
layoutName = "TestSlice1"<br />
layoutLabel = "TS1"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewLogic = slicer.vtkMRMLSliceLogic()<br />
viewLogic.SetMRMLScene(slicer.mrmlScene)<br />
viewNode = viewLogic.AddSliceNode(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
sliceLogics = slicer.app.applicationLogic().GetSliceLogics()<br />
viewWidget.setSliceLogics(sliceLogics)<br />
sliceLogics.AddItem(viewWidget.sliceLogic())<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
# 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<br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewLogic = slicer.vtkMRMLViewLogic()<br />
viewLogic.SetMRMLScene(slicer.mrmlScene)<br />
viewNode = viewLogic.AddViewNode(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLModelDisplayableManager')<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get axial slice as numpy array==<br />
<br />
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:<br />
<br />
<pre><br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
sliceIndex = 12<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array<br />
slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print(voxels.shape)<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Add noise to image==<br />
<br />
This example shows how to add simulated noise to a volume.<br />
<br />
<pre><br />
import SampleData<br />
import numpy as np<br />
<br />
# Get a sample input volume node<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Get volume as numpy array and add noise<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)<br />
slicer.util.arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
<br />
==Mask volume using segmentation==<br />
<br />
This example shows how to blank out voxels of a volume outside all segments.<br />
<br />
<pre><br />
# Input nodes<br />
volumeNode = getNode('MRHead')<br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Write segmentation to labelmap volume node with a geometry that matches the volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)<br />
<br />
# Masking<br />
import numpy as np<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
mask = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
maskedVoxels = np.copy(voxels) # we don't want to modify the original volume<br />
maskedVoxels[mask==0] = 0<br />
<br />
# Write masked volume to volume node and show it<br />
maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")<br />
slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)<br />
slicer.util.setSliceViewerLayers(maskedVolumeNode)<br />
</pre><br />
<br />
==Apply random deformations to image==<br />
<br />
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.<br />
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.<br />
Thin-plate spline transform is computed from the original and transformed point list.<br />
<br />
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940<br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Access voxels of a 4D volume as numpy array===<br />
<br />
<pre><br />
# Get sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
# Alternatively, get the first sequence node in the scene:<br />
# sequenceNode = slicer.util.getNodesByClass('vtkMRMLSequenceNode')[0]<br />
<br />
# Get voxels of itemIndex'th volume as numpy array<br />
itemIndex = 5<br />
voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))<br />
</pre><br />
<br />
===Get index value===<br />
<br />
<pre><br />
print("Index value of {0}th item: {1} = {2} {3}".format(<br />
itemIndex,<br />
sequenceNode.GetIndexName(),<br />
sequenceNode.GetNthIndexValue(itemIndex),<br />
sequenceNode.GetIndexUnit()))<br />
</pre><br />
<br />
===Browse a sequence and access currently displayed nodes===<br />
<br />
<pre><br />
# Get a sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
<br />
# Find corresponding sequence browser node<br />
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)<br />
<br />
# Print sequence information<br />
print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))<br />
print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))<br />
<br />
# Jump to a selected sequence item<br />
browserNode.SetSelectedItemNumber(5)<br />
<br />
# Get currently displayed volume node voxels as numpy array<br />
volumeNode = browserNode.GetProxyNode(sequenceNode)<br />
voxelArray = slicer.util.arrayFromVolume(volumeNode)<br />
```<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export labelmap matching reference geometry of the segmentation:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)<br />
</pre><br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export a selection of segments (identified by their names):<br />
<br />
<pre><br />
segmentNames = ["Prostate", "Urethra"]<br />
segmentIds = vtk.vtkStringArray()<br />
for segmentName in segmentNames:<br />
segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)<br />
segmentIds.InsertNextValue(segmentId)<br />
slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export to file by pressing Ctrl+Shift+S key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
</pre><br />
<br />
===Create a hollow model from boundary of solid segment===<br />
<br />
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:<br />
<br />
Example using Dynamic Modeler module (allows real-time update of parameters, using GUI in Dynamic Modeler module):<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
<br />
# Export segments to models<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
segmentModels = vtk.vtkCollection()<br />
shNode.GetDataNodesInBranch(exportFolderItemId, segmentModels)<br />
# Get exported model of first segment<br />
modelNode = segmentModels.GetItemAsObject(0)<br />
<br />
# Set up Hollow tool<br />
hollowModeler = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLDynamicModelerNode")<br />
hollowModeler.SetToolName("Hollow")<br />
hollowModeler.SetNodeReferenceID("Hollow.InputModel", modelNode.GetID())<br />
hollowedModelNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode") # this node will store the hollow model<br />
hollowModeler.SetNodeReferenceID("Hollow.OutputModel", hollowedModelNode.GetID())<br />
hollowModeler.SetAttribute("ShellThickness", "2.5") # grow outside<br />
hollowModeler.SetContinuousUpdate(True) # auto-update output model if input parameters are changed<br />
<br />
# Hide inputs, show output<br />
segmentation.GetDisplayNode().SetVisibility(False)<br />
modelNode.GetDisplayNode().SetVisibility(False)<br />
hollowedModelNode.GetDisplayNode().SetOpacity(0.5)<br />
</pre><br />
<br />
Example using VTK filters:<br />
<br />
<pre><br />
# Get closed surface representation of the segment<br />
shellThickness = 3.0 # mm<br />
segmentationNode = getNode('Segmentation')<br />
segmentationNode.CreateClosedSurfaceRepresentation()<br />
polyData = segmentationNode.GetClosedSurfaceInternalRepresentation('Segment_1')<br />
<br />
# Create shell<br />
extrude = vtk.vtkLinearExtrusionFilter()<br />
extrude.SetInputData(polyData)<br />
extrude.SetExtrusionTypeToNormalExtrusion()<br />
extrude.SetScaleFactor(shellThickness)<br />
<br />
# Compute consistent surface normals<br />
triangle_filter = vtk.vtkTriangleFilter()<br />
triangle_filter.SetInputConnection(extrude.GetOutputPort())<br />
normals = vtk.vtkPolyDataNormals()<br />
normals.SetInputConnection(triangle_filter.GetOutputPort())<br />
normals.FlipNormalsOn()<br />
<br />
# Save result into new model node<br />
slicer.modules.models.logic().AddModel(normals.GetOutputPort())<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = slicer.vtkOrientedImageData()<br />
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Create keyboard shortcut for toggling sphere brush for paint and erase effects===<br />
<br />
<pre><br />
def toggleSphereBrush():<br />
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor<br />
paintEffect = segmentEditorWidget.effectByName("Paint")<br />
isSphere = paintEffect.integerParameter('BrushSphere')<br />
# BrushSphere is "common" parameter (shared between paint and erase)<br />
paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence("s"))<br />
shortcut.connect('activated()', toggleSphereBrush)<br />
</pre><br />
<br />
===Customize list of displayed Segment editor effects===<br />
<br />
Only show Paint and Erase effects:<br />
<br />
<pre><br />
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor<br />
segmentEditorWidget.setEffectNameOrder(['Paint', 'Erase'])<br />
segmentEditorWidget.unorderedEffectsVisible = False<br />
</pre><br />
<br />
Show list of all available effect names:<br />
<br />
<pre><br />
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor<br />
print(segmentEditorWidget.availableEffectNames())<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===Get histogram of a segmented region===<br />
<br />
<pre><br />
# Generate input data<br />
################################################<br />
<br />
# Load master volume<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()<br />
<br />
# Create segmentation<br />
segmentationNode = slicer.vtkMRMLSegmentationNode()<br />
slicer.mrmlScene.AddNode(segmentationNode)<br />
segmentationNode.CreateDefaultDisplayNodes() # only needed for display<br />
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)<br />
<br />
# Create segment<br />
tumorSeed = vtk.vtkSphereSource()<br />
tumorSeed.SetCenter(-6, 30, 28)<br />
tumorSeed.SetRadius(25)<br />
tumorSeed.Update()<br />
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])<br />
<br />
# Compute histogram<br />
################################################<br />
<br />
labelValue = 1 # label value of first segment<br />
<br />
# Get segmentation as labelmap volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)<br />
<br />
# Extract all voxels of the segment as numpy array<br />
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)<br />
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
segmentVoxels = volumeArray[labelArray==labelValue]<br />
<br />
# Compute histogram<br />
import numpy as np<br />
histogram = np.histogram(segmentVoxels, bins=50)<br />
<br />
# Plot histogram<br />
################################################<br />
<br />
slicer.util.plot(histogram, xColumnIndex = 1)<br />
</pre><br />
<br />
===Get segments visible at a selected position===<br />
<br />
Show in the console names of segments visible at a markups fiducial position:<br />
<br />
<pre><br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
sliceViewLabel = "Red" # any slice view where segmentation node is visible works<br />
<br />
def printSegmentNames(unused1=None, unused2=None):<br />
<br />
sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)<br />
segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName('vtkMRMLSegmentationsDisplayableManager2D')<br />
ras = [0,0,0]<br />
markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)<br />
for idIndex in range(segmentIds.GetNumberOfValues()):<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))<br />
print('Segment found at position {0}: {1}'.format(ras, segment.GetName()))<br />
<br />
# Observe markup node changes<br />
markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)<br />
printSegmentNames()<br />
</pre><br />
<br />
===Set default segmentation options===<br />
<br />
Allow segments to overlap each other by default:<br />
<br />
<pre><br />
defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()<br />
defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)<br />
slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)<br />
</pre><br />
<br />
To always make this the default, add the lines above to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
===How to run segment editor effects from a script===<br />
<br />
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.<br />
<br />
====Use Segment editor effects from script (qMRMLSegmentEditorWidget)====<br />
<br />
Examples:<br />
<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]<br />
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]<br />
<br />
Description of effect parameters are available [https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmenteditor.html#effect-parameters here].<br />
<br />
====Use logic of effect from a script====<br />
<br />
This example shows how to perform operations on segmentations using VTK filters ''extracted'' from an effect:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
===Process segment using a VTK filter===<br />
<br />
This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = "Segment_1"<br />
kernelSize = [3,1,5]<br />
<br />
# Export segment as vtkImageData (via temporary labelmap volume node)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentIds.InsertNextValue(segmentId)<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)<br />
<br />
# Process segmentation<br />
segmentImageData = labelmapVolumeNode.GetImageData()<br />
erodeDilate = vtk.vtkImageDilateErode3D()<br />
erodeDilate.SetInputData(segmentImageData)<br />
erodeDilate.SetDilateValue(1)<br />
erodeDilate.SetErodeValue(0)<br />
erodeDilate.SetKernelSize(*kernelSize)<br />
erodeDilate.Update()<br />
segmentImageData.DeepCopy(erodeDilate.GetOutput())<br />
<br />
# Import segment from vtkImageData<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)<br />
<br />
# Cleanup temporary nodes<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
===Get information from segmentation nrrd file header===<br />
<br />
You can use this code snippet to get information from segmentation (.seg.nrrd), for example when creating numpy arrays for generating training data for deep learning networks. This script can be used in any Python environment, not just inside Slicer.<br />
<br />
<pre><br />
# pip_install('pynrrd')<br />
<br />
def read_segmentation_info(filename):<br />
import nrrd<br />
header = nrrd.read_header(filename)<br />
segmentation_info = {}<br />
segments = []<br />
segment_index = 0<br />
while True:<br />
prefix = "Segment{0}_".format(segment_index)<br />
if not prefix + "ID" in header.keys():<br />
break<br />
segment = {}<br />
segment["index"] = segment_index<br />
segment["color"] = [float(i) for i in header[prefix + "Color"].split(" ")] # Segment0_Color:=0.501961 0.682353 0.501961<br />
segment["colorAutoGenerated"] = int(header[prefix + "ColorAutoGenerated"]) != 0 # Segment0_ColorAutoGenerated:=1<br />
segment["extent"] = [int(i) for i in header[prefix + "Extent"].split(" ")] # Segment0_Extent:=68 203 53 211 24 118<br />
segment["id"] = header[prefix + "ID"] # Segment0_ID:=Segment_1<br />
segment["labelValue"] = int(header[prefix + "LabelValue"]) # Segment0_LabelValue:=1<br />
segment["layer"] = int(header[prefix + "Layer"]) # Segment0_Layer:=0<br />
segment["name"] = header[prefix + "Name"] # Segment0_Name:=Segment_1<br />
segment["nameAutoGenerated"] = int(header[prefix + "NameAutoGenerated"]) != 0 # Segment0_NameAutoGenerated:=1<br />
# Segment0_Tags:=Segmentation.Status:inprogress|TerminologyEntry:Segmentation category and type - 3D Slicer General Anatomy list<br />
# ~SCT^85756007^Tissue~SCT^85756007^Tissue~^^~Anatomic codes - DICOM master list~^^~^^|<br />
tags = {}<br />
tags_str = header[prefix + "Tags"].split("|")<br />
for tag_str in tags_str:<br />
tag_str = tag_str.strip()<br />
if not tag_str:<br />
continue<br />
key, value = tag_str.split(":", maxsplit=1)<br />
tags[key] = value<br />
segment["tags"] = tags<br />
segments.append(segment)<br />
segment_index += 1<br />
segmentation_info["segments"] = segments<br />
return segmentation_info<br />
<br />
def segment_from_name(segmentation_info, segment_name):<br />
for segment in segmentation_info["segments"]:<br />
if segment_name == segment["name"]:<br />
return segment<br />
raise KeyError('segment not found by name ' + segment_name)<br />
<br />
def segment_names(segmentation_info):<br />
names = []<br />
for segment in segmentation_info["segments"]:<br />
names.append(segment["name"])<br />
return names<br />
<br />
def extract_segments(voxels, header, segmentation_info, segment_names_to_label_values):<br />
import numpy as np<br />
# Create empty array from last 3 dimensions (output will be flattened to a 3D array)<br />
output_voxels = np.zeros(voxels.shape[-3:])<br />
# Copy non-segmentation fields to the extracted header<br />
output_header = {}<br />
for key in header.keys():<br />
if not re.match("^Segment[0-9]+_.+", key):<br />
output_header[key] = header[key]<br />
# Copy extracted segments<br />
dims = len(voxels.shape)<br />
for output_segment_index, segment_name_to_label_value in enumerate(segment_names_to_label_values):<br />
# Copy relabeled voxel data<br />
segment = segment_from_name(segmentation_info, segment_name_to_label_value[0])<br />
input_label_value = segment["labelValue"]<br />
output_label_value = segment_name_to_label_value[1]<br />
if dims == 3:<br />
output_voxels[voxels == input_label_value] = output_label_value<br />
elif dims == 4:<br />
inputLayer = segment["layer"]<br />
output_voxels[voxels[inputLayer,:,:,:] == input_label_value] = output_label_value<br />
else:<br />
raise ValueError("Voxel array dimension is invalid")<br />
# Copy all segment fields corresponding to this segment<br />
for key in header.keys():<br />
prefix = "Segment{0}_".format(segment["index"])<br />
matched = re.match("^"+prefix+"(.+)", key)<br />
if matched:<br />
field_name = matched.groups()[0]<br />
if field_name == "LabelValue":<br />
value = output_label_value<br />
elif field_name == "Layer":<br />
# output is a single layer (3D volume)<br />
value = 0<br />
else:<br />
value = header[key]<br />
output_header["Segment{0}_".format(output_segment_index) + field_name] = value<br />
# Remove unnecessary 4th dimension (volume is collapsed into 3D)<br />
if dims == 4:<br />
# Remove "none" from "none (0,1,0) (0,0,-1) (-1.2999954223632812,0,0)"<br />
output_header["space directions"] = output_header["space directions"][-3:,:]<br />
# Remove "list" from "list domain domain domain"<br />
output_header["kinds"] = output_header["kinds"][-3:]<br />
return output_voxels, output_header<br />
<br />
# Read segmentation and show some information about segments<br />
filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationOverlapping.seg.nrrd"<br />
segmentation_info = read_segmentation_info(filename)<br />
number_of_segments = len(segmentation_info["segments"])<br />
names = segment_names(segmentation_info)<br />
label0 = segment_from_name(segmentation_info, names[0])["labelValue"]<br />
print("Number of segments: " + str())<br />
print("Segment names: " + str(names))<br />
print("Label value of {0}: {1}".format(names[0], label0))<br />
<br />
# Extract selected segments with chosen label values<br />
extracted_filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationExtracted.seg.nrrd"<br />
voxels, header = nrrd.read(filename)<br />
segment_list = [("Segment_1", 10), ("Segment_3", 12), ("Segment_4", 6)]<br />
extracted_voxels, extracted_header = extract_segments(voxels, header, segmentation_info, segment_list)<br />
nrrd.write(extracted_filename, extracted_voxels, extracted_header)<br />
</pre><br />
<br />
==Quantifying segments==<br />
<br />
===Get centroid of each segment===<br />
<br />
Place a markups fiducial point at the centroid of each segment.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute centroids<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Place a markup point in each centroid<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
for segmentId in stats['SegmentIDs']:<br />
centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]<br />
segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()<br />
markupsNode.AddFiducialFromArray(centroid_ras, segmentName)<br />
</pre><br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
Complete list of available parameters can be obtained by running <code>segStatLogic.getParameterNode().GetParameterNames()</code>.<br />
<br />
==Markups==<br />
<br />
===Load markups fiducial list from file===<br />
<br />
Markups fiducials can be loaded from file:<br />
<br />
<pre><br />
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')<br />
</pre><br />
<br />
===Adding Fiducials Programatically===<br />
<br />
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial()<br />
</pre><br />
<br />
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)<br />
</pre><br />
<br />
===How to draw a curve using control points stored in a numpy array===<br />
<br />
<pre><br />
# Create random numpy array to use as input<br />
import numpy as np<br />
pointPositions = np.random.uniform(-50,50,size=[15,3])<br />
<br />
# Create curve from numpy array<br />
curveNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsCurveNode")<br />
slicer.util.updateMarkupsControlPointsFromArray(curveNode, pointPositions)<br />
</pre><br />
<br />
===Add a button to module GUI to activate fiducial placement===<br />
<br />
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).<br />
<br />
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.<br />
<br />
<pre><br />
w=slicer.qSlicerMarkupsPlaceWidget()<br />
w.setMRMLScene(slicer.mrmlScene)<br />
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()<br />
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))<br />
# Hide all buttons and only show place button<br />
w.buttonsVisible=False<br />
w.placeButton().show()<br />
w.show()<br />
</pre><br />
<br />
===Adding Fiducials via Mouse Clicks===<br />
<br />
You can also set the mouse mode into Markups fiducial placement by calling:<br />
<br />
<pre><br />
placeModePersistence = 1<br />
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)<br />
</pre><br />
<br />
A lower level way to do this is via the selection and interaction nodes:<br />
<br />
<pre><br />
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
placeModePersistence = 1<br />
interactionNode.SetPlaceModePersistence(placeModePersistence)<br />
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place<br />
interactionNode.SetCurrentInteractionMode(1)<br />
</pre><br />
<br />
To switch back to view transform once you're done placing fiducials:<br />
<br />
<pre><br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
interactionNode.SwitchToViewTransformMode()<br />
# also turn off place mode persistence if required<br />
interactionNode.SetPlaceModePersistence(0)<br />
</pre><br />
<br />
===Access to Fiducial Properties===<br />
<br />
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:<br />
<br />
<pre><br />
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")<br />
n = fidNode.AddFiducial(4.0, 5.5, -6.0)<br />
fidNode.SetNthFiducialLabel(n, "new label")<br />
# each markup is given a unique id which can be accessed from the superclass level<br />
id1 = fidNode.GetNthMarkupID(n)<br />
# manually set the position<br />
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)<br />
# set the label<br />
fidNode.SetNthFiducialLabel(n, "New label")<br />
# set the selected flag, only selected = 1 fiducials will be passed to CLIs<br />
fidNode.SetNthFiducialSelected(n, 1)<br />
# set the visibility flag<br />
fidNode.SetNthFiducialVisibility(n, 0) <br />
</pre><br />
<br />
You can loop over the fiducials in a list and get the coordinates:<br />
<br />
<pre><br />
fidList = slicer.util.getNode('F')<br />
numFids = fidList.GetNumberOfFiducials()<br />
for i in range(numFids):<br />
ras = [0,0,0]<br />
fidList.GetNthFiducialPosition(i,ras)<br />
# the world position is the RAS position with any transform matrices applied<br />
world = [0,0,0,0]<br />
fidList.GetNthFiducialWorldCoordinates(0,world)<br />
print(i,": RAS =",ras,", world =",world)<br />
</pre><br />
<br />
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.<br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Access VTK actor properties==<br />
<br />
This example shows how to access and modify VTK actor properties to experiment with physically-based rendering.<br />
<br />
<pre><br />
modelNode = slicer.util.getNode('MyModel')<br />
<br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLModelDisplayableManager')<br />
actor=modelDisplayableManager.GetActorByID(modelNode.GetDisplayNode().GetID())<br />
property=actor.GetProperty()<br />
property.SetInterpolationToPBR()<br />
property.SetMetallic(0.5)<br />
property.SetRoughness(0.5)<br />
property.SetColor(0.5,0.5,0.9)<br />
slicer.util.forceRenderAllViews()<br />
</pre><br />
<br />
See more information on physically based rendering in VTK here: https://blog.kitware.com/vtk-pbr/<br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(myButton)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Change view axis labels==<br />
<br />
<pre><br />
labels = ['x', 'X', 'y', 'Y', 'z', 'Z']<br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
# for slice view:<br />
# viewNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
for index, label in enumerate(labels):<br />
viewNode.SetAxisLabel(index, label)<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children) # Add a third argument with value True for recursive query<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Subject hierarchy plugin offering view context menu action===<br />
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 [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107 here]. This example makes it easier to create such a plugin.<br />
<syntaxhighlight lang="python"><br />
import vtk, qt, ctk, slicer<br />
from slicer.ScriptedLoadableModule import *<br />
from slicer.util import VTKObservationMixin<br />
<br />
from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin<br />
<br />
class ViewContextMenu(ScriptedLoadableModule):<br />
"""Uses ScriptedLoadableModule base class, available at:<br />
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py<br />
"""<br />
<br />
def __init__(self, parent):<br />
ScriptedLoadableModule.__init__(self, parent)<br />
self.parent.title = "Markup Editor"<br />
self.parent.categories = ["SlicerMorph", "Labs"]<br />
self.parent.dependencies = []<br />
self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]<br />
self.parent.helpText = """<br />
A tool to manipulate Markups using the Segment Editor as a geometry backend<br />
"""<br />
self.parent.helpText += self.getDefaultModuleDocumentationLink()<br />
self.parent.acknowledgementText = """<br />
This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,<br />
through a NSF ABI Development grant, "An Integrated Platform for Retrieval,<br />
Visualization and Analysis of 3D Morphology From Digital Biological Collections"<br />
(Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).<br />
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,<br />
Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.<br />
and was partially funded by NIH grant 3P41RR013218-12S1.<br />
"""<br />
<br />
#<br />
# register subject hierarchy plugin once app is initialized<br />
#<br />
def onStartupCompleted():<br />
import SubjectHierarchyPlugins<br />
from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin<br />
scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)<br />
scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginHandler.registerPlugin(scriptedPlugin)<br />
print('ViewContextMenuSubjectHierarchyPlugin loaded')<br />
slicer.app.connect("startupCompleted()", onStartupCompleted)<br />
<br />
<br />
class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):<br />
<br />
# Necessary static member to be able to set python source to scripted subject hierarchy plugin<br />
filePath = __file__<br />
<br />
def __init__(self, scriptedPlugin):<br />
self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)<br />
self.viewAction.objectName = 'CustomViewAction'<br />
self.viewAction.connect("triggered()", self.onViewAction)<br />
<br />
def onViewAction(self):<br />
print(f"VIEW ACTION")<br />
<br />
def viewContextMenuActions(self):<br />
return [self.viewAction]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData=None):<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = list(pluginLogic.availableViewMenuActionNames())<br />
menuActions.append('CustomViewAction')<br />
pluginLogic.setDisplayedViewMenuActionNames(menuActions)<br />
self.viewAction.visible = True<br />
</syntaxhighlight><br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
import JupyterNotebooksLib as slicernb<br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
<br />
import matplotlib.pyplot as plt<br />
import numpy as np<br />
<br />
def f(t):<br />
s1 = np.cos(2*np.pi*t)<br />
e1 = np.exp(-t)<br />
return s1 * e1<br />
<br />
t1 = np.arange(0.0, 5.0, 0.1)<br />
t2 = np.arange(0.0, 5.0, 0.02)<br />
t3 = np.arange(0.0, 2.0, 0.01)<br />
<br />
<br />
fig, axs = plt.subplots(2, 1, constrained_layout=True)<br />
axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-')<br />
axs[0].set_title('subplot 1')<br />
axs[0].set_xlabel('distance (m)')<br />
axs[0].set_ylabel('Damped oscillation')<br />
fig.suptitle('This is a somewhat long figure title', fontsize=16)<br />
<br />
axs[1].plot(t3, np.cos(2*np.pi*t3), '--')<br />
axs[1].set_xlabel('time (s)')<br />
axs[1].set_title('subplot 2')<br />
axs[1].set_ylabel('Undamped')<br />
<br />
slicernb.MatplotlibDisplay(matplotlib.pyplot)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre><br />
<br />
===Install a module directly from a git repository===<br />
This can be useful for sharing code in development without requiring a restart of Slicer.<br />
<br />
https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=63500Documentation/Nightly/ScriptRepository2020-12-19T00:03:03Z<p>Pieper: /* Extract randomly oriented slabs of given shape from a volume */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Get node object from the scene from node name or ID==<br />
<br />
Examples in the script repository commonly use <code>slicer.util.getNode()</code> function for getting a node object from the scene. This method is only recommended for testing and interactive debugging. <br />
<br />
*<code>slicer.util.getNode()</code> is recommended **only for interactive debugging** in the Python console/Jupyter notebook<br />
**its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as <code>*</code>), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module<br />
**throws an exception so that the developer knows immediately that there was a typo or other unexpected error<br />
*<code>slicer.mrmlScene.GetNodeByID()</code> is optimized for usage in modules:<br />
**its behavior is more predictable: it only accepts node ID as input. <code>slicer.mrmlScene.GetFirstNodeByName()</code> 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.<br />
**if node is not found it returns <code>None</code> (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<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})<br />
</pre><br />
<br />
Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
<br />
==Load volume from URL==<br />
<br />
Download a volume from a URL and load it into the scene using the code snippet below. Downloaded data is temporarily preserved in the application's cache folder and if the checksum of the already downloaded data matches the specified checksum (<algo>:<digest>) then the file is retrieved from the cache instead of being downloaded again. To compute digest with algo ''SHA256'', you can run <code>slicer.util.computeChecksum("SHA256", "path/to/file")</code>.<br />
<br />
<pre><br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')[0]<br />
</pre><br />
<br />
With interruptible progress reporting using a progress bar:<br />
<br />
<pre><br />
import SampleData<br />
<br />
def reportProgress(msg, level=None):<br />
# Print progress in the console<br />
print("Loading... {0}%".format(sampleDataLogic.downloadPercent))<br />
# Abort download if cancel is clicked in progress bar<br />
if slicer.progressWindow.wasCanceled:<br />
raise Exception('download aborted')<br />
# Update progress window<br />
slicer.progressWindow.show()<br />
slicer.progressWindow.activateWindow()<br />
slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))<br />
slicer.progressWindow.setLabelText("Downloading...")<br />
# Process events to allow screen to refresh<br />
slicer.app.processEvents() <br />
<br />
try:<br />
volumeNode = None<br />
slicer.progressWindow = slicer.util.createProgressDialog()<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
sampleDataLogic.logMessage = reportProgress<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')<br />
volumeNode = loadedNodes[0]<br />
finally:<br />
slicer.progressWindow.close()<br />
</pre><br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
<pre><br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
</pre><br />
<br />
===How to import DICOM files into the application's DICOM database===<br />
<br />
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.<br />
<br />
<pre><br />
# instantiate a new DICOM browser<br />
slicer.util.selectModule("DICOM")<br />
dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser<br />
# use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)<br />
dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)<br />
# wait for import to finish before proceeding (optional, if removed then import runs in the background)<br />
dicomBrowser.waitForImportFinished()<br />
</pre><br />
<br />
===How to import DICOM files using DICOMweb===<br />
<br />
Download and import DICOM data set using DICOMweb from [https://kheops.online/ Kheops], Google Health API, etc.<br />
<br />
How to obtain accessToken:<br />
<br />
*Google Cloud: Execute <code>gcloud auth print-access-token</code> once you have logged in<br />
*Kheops: create an album, create a sharing link (somethin like <code>https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf</code>), the token is the string after the last slash<br />
<br />
<pre><br />
slicer.util.selectModule("DICOM") # ensure DICOM database is initialized and <br />
slicer.app.processEvents()<br />
from DICOMLib import DICOMUtils<br />
DICOMUtils.importFromDICOMWeb(<br />
dicomWebEndpoint="http://demo.kheops.online/api",<br />
studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",<br />
accessToken="TfYXwbKAW7JYbAgZ7MyISf")<br />
</pre><br />
<br />
===How to access top level tags of DICOM images imported into Slicer?===<br />
<br />
For example, to print the first patient's first study's first series' "0020,0032" field:<br />
<br />
<pre><br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
# Get tag group,number from dicom dictionary<br />
import pydicom as dicom<br />
tagName = "StudyDate"<br />
tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(' ','')<br />
print(db.fileValue(fileList[0],tagStr))<br />
</pre><br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Export a segmentation to DICOM segmentation object===<br />
<br />
<pre><br />
segmentationNode = ...<br />
referenceVolumeNode = ...<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Associate segmentation node with a reference volume node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)<br />
studyShItem = shNode.GetItemParent(referenceVolumeShItem)<br />
segmentationShItem = shNode.GetItemByDataNode(segmentationNode)<br />
shNode.SetItemParent(segmentationShItem, studyShItem)<br />
<br />
# Export to DICOM<br />
import DICOMSegmentationPlugin<br />
exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()<br />
exportables = exporter.examineForExport(segmentationShItem)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser<br />
dicomDatabase = dicomBrowser.database()<br />
<br />
# Print list of available columns<br />
print(dicomDatabase.patientFieldNames)<br />
print(dicomDatabase.studyFieldNames)<br />
print(dicomDatabase.seriesFieldNames)<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsComments', True)<br />
dicomDatabase.setWeightForField('Patients', 'PatientsComments', 8)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
<br />
# Force database views update<br />
dicomDatabase.closeDatabase()<br />
dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Switch to a different module==<br />
<br />
This utility function can be used to open a different module:<br />
<br />
<pre><br />
slicer.util.selectModule('DICOM')<br />
</pre><br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
===How to define/edit a circular region of interest in a slice viewer?===<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
===Specify a sphere by multiple of markups points===<br />
<br />
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.<br />
<br />
<pre><br />
# Get markup node from scene<br />
markups = slicer.util.getNode('F')<br />
<br />
from scipy.optimize import least_squares<br />
import numpy<br />
<br />
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))):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Uses scipy's least squares optimisor to fit a sphere to a set<br />
of 3D Points<br />
:return: x: an array containing the four fitted parameters<br />
:return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the<br />
solution was found.<br />
:param: (x,y,z) three arrays of equal length containing the x, y, and z<br />
coordinates.<br />
:param: an array containing four initial values (centre, and radius)<br />
"""<br />
return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method='trf', jac='3-point', args=(x_values, y_values, z_values))<br />
<br />
def _calculate_residual_sphere(parameters, x_values, y_values, z_values):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Calculates the residual error for an x,y,z coordinates, fitted<br />
to a sphere with centre and radius defined by the parameters tuple<br />
:return: The residual error<br />
:param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]<br />
:param: arrays containing the x,y, and z coordinates.<br />
"""<br />
#extract the parameters<br />
x_centre, y_centre, z_centre, radius = parameters<br />
#use numpy's sqrt function here, which works by element on arrays<br />
distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)<br />
return distance_from_centre - radius<br />
<br />
# Fit a sphere to the markups fidicual points<br />
markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)<br />
import numpy as np<br />
# initial guess<br />
center0 = np.mean(markupsPositions, 0)<br />
radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0<br />
fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])<br />
[centerX, centerY, centerZ, radius] = fittingResult['x']<br />
<br />
# Create a sphere using the fitted parameters<br />
sphere = vtk.vtkSphereSource()<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.SetCenter(centerX, centerY, centerZ)<br />
sphere.SetRadius(radius)<br />
sphere.Update()<br />
<br />
# Add the sphere to the scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup planes==<br />
<br />
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
planeNodeNames = ['P', 'P_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
planeNormalVectors = []<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNormalVector = [0.0, 0.0, 0.0]<br />
planeNode.GetNormalWorld(planeNormalVector)<br />
planeNormalVectors.append(planeNormalVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))<br />
<br />
# Observe plane node changes<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup lines==<br />
<br />
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
lineNodeNames = ['L', 'L_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
import numpy as np<br />
lineDirectionVectors = []<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineStartPos = np.zeros(3)<br />
lineEndPos = np.zeros(3)<br />
lineNode.GetNthControlPointPositionWorld(0, lineStartPos)<br />
lineNode.GetNthControlPointPositionWorld(1, lineEndPos)<br />
lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)<br />
lineDirectionVectors.append(lineDirectionVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))<br />
<br />
# Observe line node changes<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Extract randomly oriented slabs of given shape from a volume==<br />
<br />
Returns a numpy array of sliceCount random tiles.<br />
<br />
<pre><br />
def randomSlices(volume, sliceCount, sliceShape):<br />
layoutManager = slicer.app.layoutManager()<br />
redWidget = layoutManager.sliceWidget('Red')<br />
sliceNode = redWidget.mrmlSliceNode()<br />
sliceNode.SetDimensions(*sliceShape, 1)<br />
sliceNode.SetFieldOfView(*sliceShape, 1)<br />
bounds = [0]*6<br />
volume.GetRASBounds(bounds)<br />
imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()<br />
<br />
sliceSize = sliceShape[0] * sliceShape[1]<br />
X = numpy.zeros([sliceCount, sliceSize])<br />
<br />
for sliceIndex in range(sliceCount):<br />
position = numpy.random.rand(3) * 2 - 1<br />
position = [bounds[0] + bounds[1]-bounds[0] * position[0],<br />
bounds[2] + bounds[3]-bounds[2] * position[1],<br />
bounds[4] + bounds[5]-bounds[4] * position[2]]<br />
normal = numpy.random.rand(3) * 2 - 1<br />
normal = normal / numpy.linalg.norm(normal)<br />
transverse = numpy.cross(normal, [0,0,1])<br />
orientation = 0<br />
sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2], <br />
transverse[0], transverse[1], transverse[2], <br />
position[0], position[1], position[2],<br />
orientation) <br />
if sliceIndex % 100 == 0:<br />
slicer.app.processEvents()<br />
imageReslice.Update()<br />
imageData = imageReslice.GetOutputDataObject(0)<br />
array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())<br />
X[sliceIndex] = array<br />
return X<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligned coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.<br />
<br />
<pre><br />
<br />
def viewContextMenuActions(self):<br />
return [self.doSomething]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData):<br />
if not itemID:<br />
logging.error('Invalid item for view context menu ' + str(itemID))<br />
return<br />
<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
shNode = pluginHandlerSingleton.subjectHierarchyNode()<br />
if shNode is None:<br />
logging.error('Failed to access subject hierarchy node')<br />
return<br />
<br />
associatedNode = shNode.GetItemDataNode(itemID)<br />
if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):<br />
return<br />
<br />
self.viewMenuEventData = eventData<br />
self.viewMenuEventData['NodeID'] = associatedNode.GetID()<br />
<br />
def onDoSomething(self):<br />
nodeID = self.viewMenuEventData['NodeID']<br />
markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)<br />
if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):<br />
logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))<br />
return<br />
<br />
componentIndex = self.viewMenuEventData['ComponentIndex']<br />
markupID = markupsNode.GetNthMarkupID(componentIndex)<br />
<br />
# Do something with the clicked fiducial<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# Create model node<br />
planeSource = vtk.vtkPlaneSource()<br />
planeSource.SetOrigin(-50.0, -50.0, 0.0)<br />
planeSource.SetPoint1(50.0, -50.0, 0.0)<br />
planeSource.SetPoint2(-50.0, 50.0, 0.0)<br />
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())<br />
<br />
# Tune display properties<br />
modelDisplay = model.GetDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
<br />
# Add texture (just use image of an ellipsoid)<br />
e = vtk.vtkImageEllipsoidSource()<br />
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Load volume from .vti file==<br />
<br />
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:<br />
<br />
<pre><br />
reader=vtk.vtkXMLImageDataReader()<br />
reader.SetFileName("/path/to/file.vti")<br />
reader.Update()<br />
imageData = reader.GetOutput()<br />
spacing = imageData.GetSpacing()<br />
origin = imageData.GetOrigin()<br />
imageData.SetOrigin(0,0,0)<br />
imageData.SetSpacing(1,1,1)<br />
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.SetSpacing(spacing)<br />
volumeNode.SetOrigin(origin)<br />
slicer.util.setSliceViewerLayers(volumeNode, fit=True)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array('Volume')<br />
label = array('Volume-label')<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt('values.txt', values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Show comparison view of all model files a folder==<br />
<br />
<pre><br />
# Inputs<br />
modelDir = "c:/some/folder/containing/models"<br />
modelFileExt = "stl"<br />
numberOfColumns = 4<br />
<br />
import math<br />
import os<br />
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))<br />
<br />
# Create a custom layout<br />
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))<br />
customLayoutId=567 # we pick a random id that is not used by others<br />
slicer.app.setRenderPaused(True)<br />
customLayout = '<layout type="vertical">'<br />
viewIndex = 0<br />
for rowIndex in range(numberOfRows):<br />
customLayout += '<item><layout type="horizontal">'<br />
for colIndex in range(numberOfColumns):<br />
name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)<br />
customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name<br />
customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'<br />
viewIndex += 1<br />
customLayout += '</layout></item>'<br />
<br />
customLayout += '</layout>'<br />
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):<br />
slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)<br />
<br />
slicer.app.layoutManager().setLayout(customLayoutId)<br />
<br />
# Load and show each model in a view<br />
for modelIndex, modelFile in enumerate(modelFiles):<br />
# Show only one model in each view<br />
name = os.path.basename(modelFile)<br />
viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")<br />
viewNode.LinkedControlOn()<br />
modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)<br />
modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())<br />
<br />
slicer.app.setRenderPaused(False)<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Override default scene save dialog==<br />
<br />
Place this class in the scripted module file to override<br />
<br />
<pre><br />
class MyModuleFileDialog ():<br />
"""This specially named class is detected by the scripted loadable<br />
module and is the target for optional drag and drop operations.<br />
See: Base/QTGUI/qSlicerScriptedFileDialog.h.<br />
<br />
This class is used for overriding default scene save dialog<br />
with simple saving the scene without asking anything.<br />
"""<br />
<br />
def __init__(self,qSlicerFileDialog ):<br />
self.qSlicerFileDialog = qSlicerFileDialog<br />
qSlicerFileDialog.fileType = 'NoFile'<br />
qSlicerFileDialog.description = 'Save scene'<br />
qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write<br />
<br />
def execDialog(self):<br />
# Implement custom scene save operation here.<br />
# Return True if saving completed successfully,<br />
# return False if saving was cancelled.<br />
...<br />
return saved<br />
</pre><br />
<br />
==Override application close behavior==<br />
<br />
When application close is requested then by default confirmation popup is displayed.<br />
To customize this behavior (for example, allow application closing without displaying default confirmation popup)<br />
an event filter can be installed for the close event on the main window:<br />
<br />
<pre><br />
class CloseApplicationEventFilter(qt.QWidget):<br />
def eventFilter(self, object, event):<br />
if event.type() == qt.QEvent.Close:<br />
event.accept()<br />
return True<br />
return False<br />
<br />
filter = CloseApplicationEventFilter()<br />
slicer.util.mainWindow().installEventFilter(filter)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
view=slicer.app.layoutManager().sliceWidget("Red").sliceView()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Customize keyboard/mouse gestures in viewers==<br />
<br />
Example for making the 3D view rotate using right-click-and-drag:<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLCameraDisplayableManager')<br />
cameraWidget = cameraDisplayableManager.GetCameraWidget()<br />
<br />
# Remove old mapping from right-click-and-drag<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)<br />
<br />
# Make right-click-and-drag rotate the view<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode.UnRegister(None) # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show orientation marker in all views==<br />
<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLAbstractViewNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
layoutName = "TestSlice1"<br />
layoutLabel = "TS1"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
sliceLogics = slicer.app.applicationLogic().GetSliceLogics()<br />
viewWidget.setSliceLogics(sliceLogics)<br />
sliceLogics.AddItem(viewWidget.sliceLogic())<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(*layoutColor)<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLModelDisplayableManager')<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get axial slice as numpy array==<br />
<br />
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:<br />
<br />
<pre><br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
sliceIndex = 12<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array<br />
slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print(voxels.shape)<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Add noise to image==<br />
<br />
This example shows how to add simulated noise to a volume.<br />
<br />
<pre><br />
import SampleData<br />
import numpy as np<br />
<br />
# Get a sample input volume node<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Get volume as numpy array and add noise<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)<br />
slicer.util.arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
<br />
==Mask volume using segmentation==<br />
<br />
This example shows how to blank out voxels of a volume outside all segments.<br />
<br />
<pre><br />
# Input nodes<br />
volumeNode = getNode('MRHead')<br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Write segmentation to labelmap volume node with a geometry that matches the volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)<br />
<br />
# Masking<br />
import numpy as np<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
mask = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
maskedVoxels = np.copy(voxels) # we don't want to modify the original volume<br />
maskedVoxels[mask==0] = 0<br />
<br />
# Write masked volume to volume node and show it<br />
maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")<br />
slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)<br />
slicer.util.setSliceViewerLayers(maskedVolumeNode)<br />
</pre><br />
<br />
==Apply random deformations to image==<br />
<br />
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.<br />
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.<br />
Thin-plate spline transform is computed from the original and transformed point list.<br />
<br />
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940<br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Access voxels of a 4D volume as numpy array===<br />
<br />
<pre><br />
# Get sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
# Alternatively, get the first sequence node in the scene:<br />
# sequenceNode = slicer.util.getNodesByClass('vtkMRMLSequenceNode')[0]<br />
<br />
# Get voxels of itemIndex'th volume as numpy array<br />
itemIndex = 5<br />
voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))<br />
</pre><br />
<br />
===Get index value===<br />
<br />
<pre><br />
print("Index value of {0}th item: {1} = {2} {3}".format(<br />
itemIndex,<br />
sequenceNode.GetIndexName(),<br />
sequenceNode.GetNthIndexValue(itemIndex),<br />
sequenceNode.GetIndexUnit()))<br />
</pre><br />
<br />
===Browse a sequence and access currently displayed nodes===<br />
<br />
<pre><br />
# Get a sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
<br />
# Find corresponding sequence browser node<br />
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)<br />
<br />
# Print sequence information<br />
print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))<br />
print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))<br />
<br />
# Jump to a selected sequence item<br />
browserNode.SetSelectedItemNumber(5)<br />
<br />
# Get currently displayed volume node voxels as numpy array<br />
volumeNode = browserNode.GetProxyNode(sequenceNode)<br />
voxelArray = slicer.util.arrayFromVolume(volumeNode)<br />
```<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export labelmap matching reference geometry of the segmentation:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)<br />
</pre><br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export a selection of segments (identified by their names):<br />
<br />
<pre><br />
segmentNames = ["Prostate", "Urethra"]<br />
segmentIds = vtk.vtkStringArray()<br />
for segmentName in segmentNames:<br />
segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)<br />
segmentIds.InsertNextValue(segmentId)<br />
slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export to file by pressing Ctrl+Shift+S key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
</pre><br />
<br />
===Create a hollow model from boundary of solid segment===<br />
<br />
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.<br />
<br />
Example of creating a shell model from a segment (id="Segment_1") and convert it to a shell:<br />
<br />
<pre><br />
# Get closed surface representation of the segment<br />
shellThickness = 3.0 # mm<br />
segmentationNode = getNode('Segmentation')<br />
segmentationNode.CreateClosedSurfaceRepresentation()<br />
polyData = segmentationNode.GetClosedSurfaceInternalRepresentation('Segment_1')<br />
<br />
# Create shell<br />
extrude = vtk.vtkLinearExtrusionFilter()<br />
extrude.SetInputData(polyData)<br />
extrude.SetExtrusionTypeToNormalExtrusion()<br />
extrude.SetScaleFactor(shellThickness)<br />
<br />
# Compute consistent surface normals<br />
triangle_filter = vtk.vtkTriangleFilter()<br />
triangle_filter.SetInputConnection(extrude.GetOutputPort())<br />
normals = vtk.vtkPolyDataNormals()<br />
normals.SetInputConnection(triangle_filter.GetOutputPort())<br />
normals.FlipNormalsOn()<br />
<br />
# Save result into new model node<br />
slicer.modules.models.logic().AddModel(normals.GetOutputPort())<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = slicer.vtkOrientedImageData()<br />
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Create keyboard shortcut for toggling sphere brush for paint and erase effects===<br />
<br />
<pre><br />
def toggleSphereBrush():<br />
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor<br />
paintEffect = segmentEditorWidget.effectByName("Paint")<br />
isSphere = paintEffect.integerParameter('BrushSphere')<br />
# BrushSphere is "common" parameter (shared between paint and erase)<br />
paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence("s"))<br />
shortcut.connect('activated()', toggleSphereBrush)<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===Get histogram of a segmented region===<br />
<br />
<pre><br />
# Generate input data<br />
################################################<br />
<br />
# Load master volume<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()<br />
<br />
# Create segmentation<br />
segmentationNode = slicer.vtkMRMLSegmentationNode()<br />
slicer.mrmlScene.AddNode(segmentationNode)<br />
segmentationNode.CreateDefaultDisplayNodes() # only needed for display<br />
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)<br />
<br />
# Create segment<br />
tumorSeed = vtk.vtkSphereSource()<br />
tumorSeed.SetCenter(-6, 30, 28)<br />
tumorSeed.SetRadius(25)<br />
tumorSeed.Update()<br />
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])<br />
<br />
# Compute histogram<br />
################################################<br />
<br />
labelValue = 1 # label value of first segment<br />
<br />
# Get segmentation as labelmap volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)<br />
<br />
# Extract all voxels of the segment as numpy array<br />
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)<br />
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
segmentVoxels = volumeArray[labelArray==labelValue]<br />
<br />
# Compute histogram<br />
import numpy as np<br />
histogram = np.histogram(segmentVoxels, bins=50)<br />
<br />
# Plot histogram<br />
################################################<br />
<br />
slicer.util.plot(histogram, xColumnIndex = 1)<br />
</pre><br />
<br />
===Get segments visible at a selected position===<br />
<br />
Show in the console names of segments visible at a markups fiducial position:<br />
<br />
<pre><br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
sliceViewLabel = "Red" # any slice view where segmentation node is visible works<br />
<br />
def printSegmentNames(unused1=None, unused2=None):<br />
<br />
sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)<br />
segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName('vtkMRMLSegmentationsDisplayableManager2D')<br />
ras = [0,0,0]<br />
markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)<br />
for idIndex in range(segmentIds.GetNumberOfValues()):<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))<br />
print('Segment found at position {0}: {1}'.format(ras, segment.GetName()))<br />
<br />
# Observe markup node changes<br />
markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)<br />
printSegmentNames()<br />
</pre><br />
<br />
===Set default segmentation options===<br />
<br />
Allow segments to overlap each other by default:<br />
<br />
<pre><br />
defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()<br />
defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)<br />
slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)<br />
</pre><br />
<br />
To always make this the default, add the lines above to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
===How to run segment editor effects from a script===<br />
<br />
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.<br />
<br />
====Use Segment editor effects from script (qMRMLSegmentEditorWidget)====<br />
<br />
Examples:<br />
<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]<br />
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]<br />
<br />
Description of effect parameters are available [https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmenteditor.html#effect-parameters here].<br />
<br />
====Use logic of effect from a script====<br />
<br />
This example shows how to perform operations on segmentations using VTK filters ''extracted'' from an effect:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
===Process segment using a VTK filter===<br />
<br />
This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = "Segment_1"<br />
kernelSize = [3,1,5]<br />
<br />
# Export segment as vtkImageData (via temporary labelmap volume node)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentIds.InsertNextValue(segmentId)<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)<br />
<br />
# Process segmentation<br />
segmentImageData = labelmapVolumeNode.GetImageData()<br />
erodeDilate = vtk.vtkImageDilateErode3D()<br />
erodeDilate.SetInputData(segmentImageData)<br />
erodeDilate.SetDilateValue(1)<br />
erodeDilate.SetErodeValue(0)<br />
erodeDilate.SetKernelSize(*kernelSize)<br />
erodeDilate.Update()<br />
segmentImageData.DeepCopy(erodeDilate.GetOutput())<br />
<br />
# Import segment from vtkImageData<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)<br />
<br />
# Cleanup temporary nodes<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
===Get information from segmentation nrrd file header===<br />
<br />
You can use this code snippet to get information from segmentation (.seg.nrrd), for example when creating numpy arrays for generating training data for deep learning networks. This script can be used in any Python environment, not just inside Slicer.<br />
<br />
<pre><br />
# pip_install('pynrrd')<br />
<br />
def read_segmentation_info(filename):<br />
import nrrd<br />
header = nrrd.read_header(filename)<br />
segmentation_info = {}<br />
segments = []<br />
segment_index = 0<br />
while True:<br />
prefix = "Segment{0}_".format(segment_index)<br />
if not prefix + "ID" in header.keys():<br />
break<br />
segment = {}<br />
segment["index"] = segment_index<br />
segment["color"] = [float(i) for i in header[prefix + "Color"].split(" ")] # Segment0_Color:=0.501961 0.682353 0.501961<br />
segment["colorAutoGenerated"] = int(header[prefix + "ColorAutoGenerated"]) != 0 # Segment0_ColorAutoGenerated:=1<br />
segment["extent"] = [int(i) for i in header[prefix + "Extent"].split(" ")] # Segment0_Extent:=68 203 53 211 24 118<br />
segment["id"] = header[prefix + "ID"] # Segment0_ID:=Segment_1<br />
segment["labelValue"] = int(header[prefix + "LabelValue"]) # Segment0_LabelValue:=1<br />
segment["layer"] = int(header[prefix + "Layer"]) # Segment0_Layer:=0<br />
segment["name"] = header[prefix + "Name"] # Segment0_Name:=Segment_1<br />
segment["nameAutoGenerated"] = int(header[prefix + "NameAutoGenerated"]) != 0 # Segment0_NameAutoGenerated:=1<br />
# Segment0_Tags:=Segmentation.Status:inprogress|TerminologyEntry:Segmentation category and type - 3D Slicer General Anatomy list<br />
# ~SCT^85756007^Tissue~SCT^85756007^Tissue~^^~Anatomic codes - DICOM master list~^^~^^|<br />
tags = {}<br />
tags_str = header[prefix + "Tags"].split("|")<br />
for tag_str in tags_str:<br />
tag_str = tag_str.strip()<br />
if not tag_str:<br />
continue<br />
key, value = tag_str.split(":", maxsplit=1)<br />
tags[key] = value<br />
segment["tags"] = tags<br />
segments.append(segment)<br />
segment_index += 1<br />
segmentation_info["segments"] = segments<br />
return segmentation_info<br />
<br />
def segment_from_name(segmentation_info, segment_name):<br />
for segment in segmentation_info["segments"]:<br />
if segment_name == segment["name"]:<br />
return segment<br />
raise KeyError('segment not found by name ' + segment_name)<br />
<br />
def segment_names(segmentation_info):<br />
names = []<br />
for segment in segmentation_info["segments"]:<br />
names.append(segment["name"])<br />
return names<br />
<br />
def extract_segments(voxels, header, segmentation_info, segment_names_to_label_values):<br />
import numpy as np<br />
# Create empty array from last 3 dimensions (output will be flattened to a 3D array)<br />
output_voxels = np.zeros(voxels.shape[-3:])<br />
# Copy non-segmentation fields to the extracted header<br />
output_header = {}<br />
for key in header.keys():<br />
if not re.match("^Segment[0-9]+_.+", key):<br />
output_header[key] = header[key]<br />
# Copy extracted segments<br />
dims = len(voxels.shape)<br />
for output_segment_index, segment_name_to_label_value in enumerate(segment_names_to_label_values):<br />
# Copy relabeled voxel data<br />
segment = segment_from_name(segmentation_info, segment_name_to_label_value[0])<br />
input_label_value = segment["labelValue"]<br />
output_label_value = segment_name_to_label_value[1]<br />
if dims == 3:<br />
output_voxels[voxels == input_label_value] = output_label_value<br />
elif dims == 4:<br />
inputLayer = segment["layer"]<br />
output_voxels[voxels[inputLayer,:,:,:] == input_label_value] = output_label_value<br />
else:<br />
raise ValueError("Voxel array dimension is invalid")<br />
# Copy all segment fields corresponding to this segment<br />
for key in header.keys():<br />
prefix = "Segment{0}_".format(segment["index"])<br />
matched = re.match("^"+prefix+"(.+)", key)<br />
if matched:<br />
field_name = matched.groups()[0]<br />
if field_name == "LabelValue":<br />
value = output_label_value<br />
elif field_name == "Layer":<br />
# output is a single layer (3D volume)<br />
value = 0<br />
else:<br />
value = header[key]<br />
output_header["Segment{0}_".format(output_segment_index) + field_name] = value<br />
# Remove unnecessary 4th dimension (volume is collapsed into 3D)<br />
if dims == 4:<br />
# Remove "none" from "none (0,1,0) (0,0,-1) (-1.2999954223632812,0,0)"<br />
output_header["space directions"] = output_header["space directions"][-3:,:]<br />
# Remove "list" from "list domain domain domain"<br />
output_header["kinds"] = output_header["kinds"][-3:]<br />
return output_voxels, output_header<br />
<br />
# Read segmentation and show some information about segments<br />
filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationOverlapping.seg.nrrd"<br />
segmentation_info = read_segmentation_info(filename)<br />
number_of_segments = len(segmentation_info["segments"])<br />
names = segment_names(segmentation_info)<br />
label0 = segment_from_name(segmentation_info, names[0])["labelValue"]<br />
print("Number of segments: " + str())<br />
print("Segment names: " + str(names))<br />
print("Label value of {0}: {1}".format(names[0], label0))<br />
<br />
# Extract selected segments with chosen label values<br />
extracted_filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationExtracted.seg.nrrd"<br />
voxels, header = nrrd.read(filename)<br />
segment_list = [("Segment_1", 10), ("Segment_3", 12), ("Segment_4", 6)]<br />
extracted_voxels, extracted_header = extract_segments(voxels, header, segmentation_info, segment_list)<br />
nrrd.write(extracted_filename, extracted_voxels, extracted_header)<br />
</pre><br />
<br />
==Quantifying segments==<br />
<br />
===Get centroid of each segment===<br />
<br />
Place a markups fiducial point at the centroid of each segment.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute centroids<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Place a markup point in each centroid<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
for segmentId in stats['SegmentIDs']:<br />
centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]<br />
segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()<br />
markupsNode.AddFiducialFromArray(centroid_ras, segmentName)<br />
</pre><br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
Complete list of available parameters can be obtained by running <code>segStatLogic.getParameterNode().GetParameterNames()</code>.<br />
<br />
==Markups==<br />
<br />
===Load markups fiducial list from file===<br />
<br />
Markups fiducials can be loaded from file:<br />
<br />
<pre><br />
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')<br />
</pre><br />
<br />
===Adding Fiducials Programatically===<br />
<br />
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial()<br />
</pre><br />
<br />
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)<br />
</pre><br />
<br />
===Add a button to module GUI to activate fiducial placement===<br />
<br />
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).<br />
<br />
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.<br />
<br />
<pre><br />
w=slicer.qSlicerMarkupsPlaceWidget()<br />
w.setMRMLScene(slicer.mrmlScene)<br />
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()<br />
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))<br />
# Hide all buttons and only show place button<br />
w.buttonsVisible=False<br />
w.placeButton().show()<br />
w.show()<br />
</pre><br />
<br />
===Adding Fiducials via Mouse Clicks===<br />
<br />
You can also set the mouse mode into Markups fiducial placement by calling:<br />
<br />
<pre><br />
placeModePersistence = 1<br />
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)<br />
</pre><br />
<br />
A lower level way to do this is via the selection and interaction nodes:<br />
<br />
<pre><br />
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
placeModePersistence = 1<br />
interactionNode.SetPlaceModePersistence(placeModePersistence)<br />
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place<br />
interactionNode.SetCurrentInteractionMode(1)<br />
</pre><br />
<br />
To switch back to view transform once you're done placing fiducials:<br />
<br />
<pre><br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
interactionNode.SwitchToViewTransformMode()<br />
# also turn off place mode persistence if required<br />
interactionNode.SetPlaceModePersistence(0)<br />
</pre><br />
<br />
===Access to Fiducial Properties===<br />
<br />
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:<br />
<br />
<pre><br />
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")<br />
n = fidNode.AddFiducial(4.0, 5.5, -6.0)<br />
fidNode.SetNthFiducialLabel(n, "new label")<br />
# each markup is given a unique id which can be accessed from the superclass level<br />
id1 = fidNode.GetNthMarkupID(n)<br />
# manually set the position<br />
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)<br />
# set the label<br />
fidNode.SetNthFiducialLabel(n, "New label")<br />
# set the selected flag, only selected = 1 fiducials will be passed to CLIs<br />
fidNode.SetNthFiducialSelected(n, 1)<br />
# set the visibility flag<br />
fidNode.SetNthFiducialVisibility(n, 0) <br />
</pre><br />
<br />
You can loop over the fiducials in a list and get the coordinates:<br />
<br />
<pre><br />
fidList = slicer.util.getNode('F')<br />
numFids = fidList.GetNumberOfFiducials()<br />
for i in range(numFids):<br />
ras = [0,0,0]<br />
fidList.GetNthFiducialPosition(i,ras)<br />
# the world position is the RAS position with any transform matrices applied<br />
world = [0,0,0,0]<br />
fidList.GetNthFiducialWorldCoordinates(0,world)<br />
print(i,": RAS =",ras,", world =",world)<br />
</pre><br />
<br />
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.<br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(myButton)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Subject hierarchy plugin offering view context menu action===<br />
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 [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107 here]. This example makes it easier to create such a plugin.<br />
<syntaxhighlight lang="python"><br />
import vtk, qt, ctk, slicer<br />
from slicer.ScriptedLoadableModule import *<br />
from slicer.util import VTKObservationMixin<br />
<br />
from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin<br />
<br />
class ViewContextMenu(ScriptedLoadableModule):<br />
"""Uses ScriptedLoadableModule base class, available at:<br />
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py<br />
"""<br />
<br />
def __init__(self, parent):<br />
ScriptedLoadableModule.__init__(self, parent)<br />
self.parent.title = "Markup Editor"<br />
self.parent.categories = ["SlicerMorph", "Labs"]<br />
self.parent.dependencies = []<br />
self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]<br />
self.parent.helpText = """<br />
A tool to manipulate Markups using the Segment Editor as a geometry backend<br />
"""<br />
self.parent.helpText += self.getDefaultModuleDocumentationLink()<br />
self.parent.acknowledgementText = """<br />
This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,<br />
through a NSF ABI Development grant, "An Integrated Platform for Retrieval,<br />
Visualization and Analysis of 3D Morphology From Digital Biological Collections"<br />
(Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).<br />
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,<br />
Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.<br />
and was partially funded by NIH grant 3P41RR013218-12S1.<br />
"""<br />
<br />
#<br />
# register subject hierarchy plugin once app is initialized<br />
#<br />
def onStartupCompleted():<br />
import SubjectHierarchyPlugins<br />
from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin<br />
scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)<br />
scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginHandler.registerPlugin(scriptedPlugin)<br />
print('ViewContextMenuSubjectHierarchyPlugin loaded')<br />
slicer.app.connect("startupCompleted()", onStartupCompleted)<br />
<br />
<br />
class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):<br />
<br />
# Necessary static member to be able to set python source to scripted subject hierarchy plugin<br />
filePath = __file__<br />
<br />
def __init__(self, scriptedPlugin):<br />
self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)<br />
self.viewAction.objectName = 'CustomViewAction'<br />
self.viewAction.connect("triggered()", self.onViewAction)<br />
<br />
def onViewAction(self):<br />
print(f"VIEW ACTION")<br />
<br />
def viewContextMenuActions(self):<br />
return [self.viewAction]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData=None):<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = list(pluginLogic.availableViewMenuActionNames())<br />
menuActions.append('CustomViewAction')<br />
pluginLogic.setDisplayedViewMenuActionNames(menuActions)<br />
self.viewAction.visible = True<br />
</syntaxhighlight><br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
import JupyterNotebooksLib as slicernb<br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
<br />
import matplotlib.pyplot as plt<br />
import numpy as np<br />
<br />
def f(t):<br />
s1 = np.cos(2*np.pi*t)<br />
e1 = np.exp(-t)<br />
return s1 * e1<br />
<br />
t1 = np.arange(0.0, 5.0, 0.1)<br />
t2 = np.arange(0.0, 5.0, 0.02)<br />
t3 = np.arange(0.0, 2.0, 0.01)<br />
<br />
<br />
fig, axs = plt.subplots(2, 1, constrained_layout=True)<br />
axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-')<br />
axs[0].set_title('subplot 1')<br />
axs[0].set_xlabel('distance (m)')<br />
axs[0].set_ylabel('Damped oscillation')<br />
fig.suptitle('This is a somewhat long figure title', fontsize=16)<br />
<br />
axs[1].plot(t3, np.cos(2*np.pi*t3), '--')<br />
axs[1].set_xlabel('time (s)')<br />
axs[1].set_title('subplot 2')<br />
axs[1].set_ylabel('Undamped')<br />
<br />
slicernb.MatplotlibDisplay(matplotlib.pyplot)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre><br />
<br />
===Install a module directly from a git repository===<br />
This can be useful for sharing code in development without requiring a restart of Slicer.<br />
<br />
https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=63499Documentation/Nightly/ScriptRepository2020-12-18T23:59:41Z<p>Pieper: /* Set slice position and orientation from a normal vector and position */ Add random orientation example</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Get node object from the scene from node name or ID==<br />
<br />
Examples in the script repository commonly use <code>slicer.util.getNode()</code> function for getting a node object from the scene. This method is only recommended for testing and interactive debugging. <br />
<br />
* <code>slicer.util.getNode()</code> is recommended **only for interactive debugging** in the Python console/Jupyter notebook<br />
** its input is intentionally defined vaguely (it can be either node ID or name and you can use wildcards such as <code>*</code>), which is good because it make it simpler to use, but the uncertain behavior is not good for general-purpose use in a module<br />
** throws an exception so that the developer knows immediately that there was a typo or other unexpected error<br />
* <code>slicer.mrmlScene.GetNodeByID()</code> is optimized for usage in modules:<br />
** its behavior is more predictable: it only accepts node ID as input. <code>slicer.mrmlScene.GetFirstNodeByName()</code> 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.<br />
** if node is not found it returns <code>None</code> (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<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})<br />
</pre><br />
<br />
Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
<br />
==Load volume from URL==<br />
<br />
Download a volume from a URL and load it into the scene using the code snippet below. Downloaded data is temporarily preserved in the application's cache folder and if the checksum of the already downloaded data matches the specified checksum (<algo>:<digest>) then the file is retrieved from the cache instead of being downloaded again. To compute digest with algo ''SHA256'', you can run <code>slicer.util.computeChecksum("SHA256", "path/to/file")</code>.<br />
<br />
<pre><br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')[0]<br />
</pre><br />
<br />
With interruptible progress reporting using a progress bar:<br />
<br />
<pre><br />
import SampleData<br />
<br />
def reportProgress(msg, level=None):<br />
# Print progress in the console<br />
print("Loading... {0}%".format(sampleDataLogic.downloadPercent))<br />
# Abort download if cancel is clicked in progress bar<br />
if slicer.progressWindow.wasCanceled:<br />
raise Exception('download aborted')<br />
# Update progress window<br />
slicer.progressWindow.show()<br />
slicer.progressWindow.activateWindow()<br />
slicer.progressWindow.setValue(int(sampleDataLogic.downloadPercent))<br />
slicer.progressWindow.setLabelText("Downloading...")<br />
# Process events to allow screen to refresh<br />
slicer.app.processEvents() <br />
<br />
try:<br />
volumeNode = None<br />
slicer.progressWindow = slicer.util.createProgressDialog()<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
sampleDataLogic.logMessage = reportProgress<br />
loadedNodes = sampleDataLogic.downloadFromURL(<br />
nodeNames='MRHead',<br />
fileNames='MR-head25.nrrd',<br />
uris='https://github.com/Slicer/SlicerTestingData/releases/download/SHA256/cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93',<br />
checksums='SHA256:cc211f0dfd9a05ca3841ce1141b292898b2dd2d3f08286affadf823a7e58df93')<br />
volumeNode = loadedNodes[0]<br />
finally:<br />
slicer.progressWindow.close()<br />
</pre><br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
<pre><br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
</pre><br />
<br />
===How to import DICOM files into the application's DICOM database===<br />
<br />
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.<br />
<br />
<pre><br />
# instantiate a new DICOM browser<br />
slicer.util.selectModule("DICOM")<br />
dicomBrowser = slicer.modules.DICOMWidget.browserWidget.dicomBrowser<br />
# use dicomBrowser.ImportDirectoryCopy to make a copy of the files (useful for importing data from removable storage)<br />
dicomBrowser.importDirectory(dicomFilesDirectory, dicomBrowser.ImportDirectoryAddLink)<br />
# wait for import to finish before proceeding (optional, if removed then import runs in the background)<br />
dicomBrowser.waitForImportFinished()<br />
</pre><br />
<br />
===How to import DICOM files using DICOMweb===<br />
<br />
Download and import DICOM data set using DICOMweb from [https://kheops.online/ Kheops], Google Health API, etc.<br />
<br />
How to obtain accessToken:<br />
* Google Cloud: Execute <code>gcloud auth print-access-token</code> once you have logged in<br />
* Kheops: create an album, create a sharing link (somethin like <code>https://demo.kheops.online/view/TfYXwbKAW7JYbAgZ7MyISf</code>), the token is the string after the last slash<br />
<br />
<pre><br />
slicer.util.selectModule("DICOM") # ensure DICOM database is initialized and <br />
slicer.app.processEvents()<br />
from DICOMLib import DICOMUtils<br />
DICOMUtils.importFromDICOMWeb(<br />
dicomWebEndpoint="http://demo.kheops.online/api",<br />
studyInstanceUID="1.3.6.1.4.1.14519.5.2.1.8421.4009.985792766370191766692237040819",<br />
accessToken="TfYXwbKAW7JYbAgZ7MyISf")<br />
</pre><br />
<br />
===How to access top level tags of DICOM images imported into Slicer?===<br />
<br />
For example, to print the first patient's first study's first series' "0020,0032" field:<br />
<br />
<pre><br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
# Get tag group,number from dicom dictionary<br />
import pydicom as dicom<br />
tagName = "StudyDate"<br />
tagStr = str(dicom.tag.Tag(tagName))[1:-1].replace(' ','')<br />
print(db.fileValue(fileList[0],tagStr))<br />
</pre><br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Export a segmentation to DICOM segmentation object===<br />
<br />
<pre><br />
segmentationNode = ...<br />
referenceVolumeNode = ...<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Associate segmentation node with a reference volume node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
referenceVolumeShItem = shNode.GetItemByDataNode(referenceVolumeNode)<br />
studyShItem = shNode.GetItemParent(referenceVolumeShItem)<br />
segmentationShItem = shNode.GetItemByDataNode(segmentationNode)<br />
shNode.SetItemParent(segmentationShItem, studyShItem)<br />
<br />
# Export to DICOM<br />
import DICOMSegmentationPlugin<br />
exporter = DICOMSegmentationPlugin.DICOMSegmentationPluginClass()<br />
exportables = exporter.examineForExport(segmentationShItem)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().browserWidget.dicomBrowser<br />
dicomDatabase = dicomBrowser.database()<br />
<br />
# Print list of available columns<br />
print(dicomDatabase.patientFieldNames)<br />
print(dicomDatabase.studyFieldNames)<br />
print(dicomDatabase.seriesFieldNames)<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsComments', True)<br />
dicomDatabase.setWeightForField('Patients', 'PatientsComments', 8)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
<br />
# Force database views update<br />
dicomDatabase.closeDatabase()<br />
dicomDatabase.openDatabase(dicomBrowser.database().databaseFilename)<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Switch to a different module==<br />
<br />
This utility function can be used to open a different module:<br />
<br />
<pre><br />
slicer.util.selectModule('DICOM')<br />
</pre><br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
===How to define/edit a circular region of interest in a slice viewer?===<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
===Specify a sphere by multiple of markups points===<br />
<br />
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.<br />
<br />
<pre><br />
# Get markup node from scene<br />
markups = slicer.util.getNode('F')<br />
<br />
from scipy.optimize import least_squares<br />
import numpy<br />
<br />
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))):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Uses scipy's least squares optimisor to fit a sphere to a set<br />
of 3D Points<br />
:return: x: an array containing the four fitted parameters<br />
:return: ier: int An integer flag. If it is equal to 1, 2, 3 or 4, the<br />
solution was found.<br />
:param: (x,y,z) three arrays of equal length containing the x, y, and z<br />
coordinates.<br />
:param: an array containing four initial values (centre, and radius)<br />
"""<br />
return least_squares(_calculate_residual_sphere, initial_parameters, bounds=bounds, method='trf', jac='3-point', args=(x_values, y_values, z_values))<br />
<br />
def _calculate_residual_sphere(parameters, x_values, y_values, z_values):<br />
"""<br />
Source: https://github.com/thompson318/scikit-surgery-sphere-fitting/blob/master/sksurgeryspherefitting/algorithms/sphere_fitting.py<br />
Calculates the residual error for an x,y,z coordinates, fitted<br />
to a sphere with centre and radius defined by the parameters tuple<br />
:return: The residual error<br />
:param: A tuple of the parameters to be optimised, should contain [x_centre, y_centre, z_centre, radius]<br />
:param: arrays containing the x,y, and z coordinates.<br />
"""<br />
#extract the parameters<br />
x_centre, y_centre, z_centre, radius = parameters<br />
#use numpy's sqrt function here, which works by element on arrays<br />
distance_from_centre = numpy.sqrt((x_values - x_centre)**2 + (y_values - y_centre)**2 + (z_values - z_centre)**2)<br />
return distance_from_centre - radius<br />
<br />
# Fit a sphere to the markups fidicual points<br />
markupsPositions = slicer.util.arrayFromMarkupsControlPoints(markups)<br />
import numpy as np<br />
# initial guess<br />
center0 = np.mean(markupsPositions, 0)<br />
radius0 = np.linalg.norm(np.amin(markupsPositions,0)-np.amax(markupsPositions,0))/2.0<br />
fittingResult = fit_sphere_least_squares(markupsPositions[:,0], markupsPositions[:,1], markupsPositions[:,2], [center0[0], center0[1], center0[2], radius0])<br />
[centerX, centerY, centerZ, radius] = fittingResult['x']<br />
<br />
# Create a sphere using the fitted parameters<br />
sphere = vtk.vtkSphereSource()<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.SetCenter(centerX, centerY, centerZ)<br />
sphere.SetRadius(radius)<br />
sphere.Update()<br />
<br />
# Add the sphere to the scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup planes==<br />
<br />
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
planeNodeNames = ['P', 'P_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
planeNormalVectors = []<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNormalVector = [0.0, 0.0, 0.0]<br />
planeNode.GetNormalWorld(planeNormalVector)<br />
planeNormalVectors.append(planeNormalVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))<br />
<br />
# Observe plane node changes<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup lines==<br />
<br />
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
lineNodeNames = ['L', 'L_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
import numpy as np<br />
lineDirectionVectors = []<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineStartPos = np.zeros(3)<br />
lineEndPos = np.zeros(3)<br />
lineNode.GetNthControlPointPositionWorld(0, lineStartPos)<br />
lineNode.GetNthControlPointPositionWorld(1, lineEndPos)<br />
lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)<br />
lineDirectionVectors.append(lineDirectionVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))<br />
<br />
# Observe line node changes<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Extract randomly oriented slabs of given shape from a volume==<br />
<br />
Returns a numpy array of the slabs.<br />
<br />
<pre><br />
def randomSlices(volume, sliceCount, sliceShape):<br />
layoutManager = slicer.app.layoutManager()<br />
redWidget = layoutManager.sliceWidget('Red')<br />
sliceNode = redWidget.mrmlSliceNode()<br />
sliceNode.SetDimensions(*sliceShape, 1)<br />
sliceNode.SetFieldOfView(*sliceShape, 1)<br />
bounds = [0]*6<br />
volume.GetRASBounds(bounds)<br />
imageReslice = redWidget.sliceLogic().GetBackgroundLayer().GetReslice()<br />
<br />
sliceSize = sliceShape[0] * sliceShape[1]<br />
X = numpy.zeros([sliceCount, sliceSize])<br />
<br />
for sliceIndex in range(sliceCount):<br />
position = numpy.random.rand(3) * 2 - 1<br />
position = [bounds[0] + bounds[1]-bounds[0] * position[0],<br />
bounds[2] + bounds[3]-bounds[2] * position[1],<br />
bounds[4] + bounds[5]-bounds[4] * position[2]]<br />
normal = numpy.random.rand(3) * 2 - 1<br />
normal = normal / numpy.linalg.norm(normal)<br />
transverse = numpy.cross(normal, [0,0,1])<br />
orientation = 0<br />
sliceNode.SetSliceToRASByNTP( normal[0], normal[1], normal[2], <br />
transverse[0], transverse[1], transverse[2], <br />
position[0], position[1], position[2],<br />
orientation) <br />
if sliceIndex % 100 == 0:<br />
slicer.app.processEvents()<br />
imageReslice.Update()<br />
imageData = imageReslice.GetOutputDataObject(0)<br />
array = vtk.util.numpy_support.vtk_to_numpy(imageData.GetPointData().GetScalars())<br />
X[sliceIndex] = array<br />
return X<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligned coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.<br />
<br />
<pre><br />
<br />
def viewContextMenuActions(self):<br />
return [self.doSomething]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData):<br />
if not itemID:<br />
logging.error('Invalid item for view context menu ' + str(itemID))<br />
return<br />
<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
shNode = pluginHandlerSingleton.subjectHierarchyNode()<br />
if shNode is None:<br />
logging.error('Failed to access subject hierarchy node')<br />
return<br />
<br />
associatedNode = shNode.GetItemDataNode(itemID)<br />
if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):<br />
return<br />
<br />
self.viewMenuEventData = eventData<br />
self.viewMenuEventData['NodeID'] = associatedNode.GetID()<br />
<br />
def onDoSomething(self):<br />
nodeID = self.viewMenuEventData['NodeID']<br />
markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)<br />
if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):<br />
logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))<br />
return<br />
<br />
componentIndex = self.viewMenuEventData['ComponentIndex']<br />
markupID = markupsNode.GetNthMarkupID(componentIndex)<br />
<br />
# Do something with the clicked fiducial<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# Create model node<br />
planeSource = vtk.vtkPlaneSource()<br />
planeSource.SetOrigin(-50.0, -50.0, 0.0)<br />
planeSource.SetPoint1(50.0, -50.0, 0.0)<br />
planeSource.SetPoint2(-50.0, 50.0, 0.0)<br />
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())<br />
<br />
# Tune display properties<br />
modelDisplay = model.GetDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
<br />
# Add texture (just use image of an ellipsoid)<br />
e = vtk.vtkImageEllipsoidSource()<br />
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Load volume from .vti file==<br />
<br />
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:<br />
<br />
<pre><br />
reader=vtk.vtkXMLImageDataReader()<br />
reader.SetFileName("/path/to/file.vti")<br />
reader.Update()<br />
imageData = reader.GetOutput()<br />
spacing = imageData.GetSpacing()<br />
origin = imageData.GetOrigin()<br />
imageData.SetOrigin(0,0,0)<br />
imageData.SetSpacing(1,1,1)<br />
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.SetSpacing(spacing)<br />
volumeNode.SetOrigin(origin)<br />
slicer.util.setSliceViewerLayers(volumeNode, fit=True)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array('Volume')<br />
label = array('Volume-label')<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt('values.txt', values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Show comparison view of all model files a folder==<br />
<br />
<pre><br />
# Inputs<br />
modelDir = "c:/some/folder/containing/models"<br />
modelFileExt = "stl"<br />
numberOfColumns = 4<br />
<br />
import math<br />
import os<br />
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))<br />
<br />
# Create a custom layout<br />
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))<br />
customLayoutId=567 # we pick a random id that is not used by others<br />
slicer.app.setRenderPaused(True)<br />
customLayout = '<layout type="vertical">'<br />
viewIndex = 0<br />
for rowIndex in range(numberOfRows):<br />
customLayout += '<item><layout type="horizontal">'<br />
for colIndex in range(numberOfColumns):<br />
name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)<br />
customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name<br />
customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'<br />
viewIndex += 1<br />
customLayout += '</layout></item>'<br />
<br />
customLayout += '</layout>'<br />
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):<br />
slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)<br />
<br />
slicer.app.layoutManager().setLayout(customLayoutId)<br />
<br />
# Load and show each model in a view<br />
for modelIndex, modelFile in enumerate(modelFiles):<br />
# Show only one model in each view<br />
name = os.path.basename(modelFile)<br />
viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")<br />
viewNode.LinkedControlOn()<br />
modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)<br />
modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())<br />
<br />
slicer.app.setRenderPaused(False)<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Override default scene save dialog==<br />
<br />
Place this class in the scripted module file to override<br />
<br />
<pre><br />
class MyModuleFileDialog ():<br />
"""This specially named class is detected by the scripted loadable<br />
module and is the target for optional drag and drop operations.<br />
See: Base/QTGUI/qSlicerScriptedFileDialog.h.<br />
<br />
This class is used for overriding default scene save dialog<br />
with simple saving the scene without asking anything.<br />
"""<br />
<br />
def __init__(self,qSlicerFileDialog ):<br />
self.qSlicerFileDialog = qSlicerFileDialog<br />
qSlicerFileDialog.fileType = 'NoFile'<br />
qSlicerFileDialog.description = 'Save scene'<br />
qSlicerFileDialog.action = slicer.qSlicerFileDialog.Write<br />
<br />
def execDialog(self):<br />
# Implement custom scene save operation here.<br />
# Return True if saving completed successfully,<br />
# return False if saving was cancelled.<br />
...<br />
return saved<br />
</pre><br />
<br />
==Override application close behavior==<br />
<br />
When application close is requested then by default confirmation popup is displayed.<br />
To customize this behavior (for example, allow application closing without displaying default confirmation popup)<br />
an event filter can be installed for the close event on the main window:<br />
<br />
<pre><br />
class CloseApplicationEventFilter(qt.QWidget):<br />
def eventFilter(self, object, event):<br />
if event.type() == qt.QEvent.Close:<br />
event.accept()<br />
return True<br />
return False<br />
<br />
filter = CloseApplicationEventFilter()<br />
slicer.util.mainWindow().installEventFilter(filter)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
view=slicer.app.layoutManager().sliceWidget("Red").sliceView()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Customize keyboard/mouse gestures in viewers==<br />
<br />
Example for making the 3D view rotate using right-click-and-drag:<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
cameraDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLCameraDisplayableManager')<br />
cameraWidget = cameraDisplayableManager.GetCameraWidget()<br />
<br />
# Remove old mapping from right-click-and-drag<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, vtk.vtkWidgetEvent.NoEvent, vtk.vtkWidgetEvent.NoEvent)<br />
<br />
# Make right-click-and-drag rotate the view<br />
cameraWidget.SetEventTranslationClickAndDrag(cameraWidget.WidgetStateIdle, vtk.vtkCommand.RightButtonPressEvent, vtk.vtkEvent.NoModifier,<br />
cameraWidget.WidgetStateRotate, cameraWidget.WidgetEventRotateStart, cameraWidget.WidgetEventRotateEnd)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode.UnRegister(None) # CreateNodeByClass is factory method, need to unregister the result to prevent memory leaks<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManager = sliceView.displayableManagerByClassName("vtkMRMLRulerDisplayableManager")<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show orientation marker in all views==<br />
<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLAbstractViewNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetOrientationMarkerType(slicer.vtkMRMLAbstractViewNode.OrientationMarkerTypeAxes)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
layoutName = "TestSlice1"<br />
layoutLabel = "TS1"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
sliceLogics = slicer.app.applicationLogic().GetSliceLogics()<br />
viewWidget.setSliceLogics(sliceLogics)<br />
sliceLogics.AddItem(viewWidget.sliceLogic())<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(*layoutColor)<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
modelDisplayableManager = threeDViewWidget.threeDView().displayableManagerByClassName('vtkMRMLModelDisplayableManager')<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get axial slice as numpy array==<br />
<br />
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:<br />
<br />
<pre><br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
sliceIndex = 12<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array<br />
slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print(voxels.shape)<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Add noise to image==<br />
<br />
This example shows how to add simulated noise to a volume.<br />
<br />
<pre><br />
import SampleData<br />
import numpy as np<br />
<br />
# Get a sample input volume node<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Get volume as numpy array and add noise<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)<br />
slicer.util.arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
<br />
==Mask volume using segmentation==<br />
<br />
This example shows how to blank out voxels of a volume outside all segments.<br />
<br />
<pre><br />
# Input nodes<br />
volumeNode = getNode('MRHead')<br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Write segmentation to labelmap volume node with a geometry that matches the volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, volumeNode)<br />
<br />
# Masking<br />
import numpy as np<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
mask = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
maskedVoxels = np.copy(voxels) # we don't want to modify the original volume<br />
maskedVoxels[mask==0] = 0<br />
<br />
# Write masked volume to volume node and show it<br />
maskedVolumeNode = slicer.modules.volumes.logic().CloneVolume(volumeNode, "Masked")<br />
slicer.util.updateVolumeFromArray(maskedVolumeNode, maskedVoxels)<br />
slicer.util.setSliceViewerLayers(maskedVolumeNode)<br />
</pre><br />
<br />
==Apply random deformations to image==<br />
<br />
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.<br />
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.<br />
Thin-plate spline transform is computed from the original and transformed point list.<br />
<br />
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940<br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Access voxels of a 4D volume as numpy array===<br />
<br />
<pre><br />
# Get sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
# Alternatively, get the first sequence node in the scene:<br />
# sequenceNode = slicer.util.getNodesByClass('vtkMRMLSequenceNode')[0]<br />
<br />
# Get voxels of itemIndex'th volume as numpy array<br />
itemIndex = 5<br />
voxelArray = slicer.util.arrayFromVolume(sequenceNode.GetNthDataNode(itemIndex))<br />
</pre><br />
<br />
===Get index value===<br />
<br />
<pre><br />
print("Index value of {0}th item: {1} = {2} {3}".format(<br />
itemIndex,<br />
sequenceNode.GetIndexName(),<br />
sequenceNode.GetNthIndexValue(itemIndex),<br />
sequenceNode.GetIndexUnit()))<br />
</pre><br />
<br />
===Browse a sequence and access currently displayed nodes===<br />
<br />
<pre><br />
# Get a sequence node<br />
import SampleData<br />
sequenceNode = SampleData.SampleDataLogic().downloadSample('CTPCardioSeq')<br />
<br />
# Find corresponding sequence browser node<br />
browserNode = slicer.modules.sequences.logic().GetFirstBrowserNodeForSequenceNode(sequenceNode)<br />
<br />
# Print sequence information<br />
print("Number of items in the sequence: {0}".format(browserNode.GetNumberOfItems()))<br />
print("Index name: {0}".format(browserNode.GetMasterSequenceNode().GetIndexName()))<br />
<br />
# Jump to a selected sequence item<br />
browserNode.SetSelectedItemNumber(5)<br />
<br />
# Get currently displayed volume node voxels as numpy array<br />
volumeNode = browserNode.GetProxyNode(sequenceNode)<br />
voxelArray = slicer.util.arrayFromVolume(volumeNode)<br />
```<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export labelmap matching reference geometry of the segmentation:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)<br />
</pre><br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export a selection of segments (identified by their names):<br />
<br />
<pre><br />
segmentNames = ["Prostate", "Urethra"]<br />
segmentIds = vtk.vtkStringArray()<br />
for segmentName in segmentNames:<br />
segmentId = segmentationNode.GetSegmentation().GetSegmentIdBySegmentName(segmentName)<br />
segmentIds.InsertNextValue(segmentId)<br />
slicer.vtkSlicerSegmentationsModuleLogic.ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export to file by pressing Ctrl+Shift+S key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
</pre><br />
<br />
===Create a hollow model from boundary of solid segment===<br />
<br />
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.<br />
<br />
Example of creating a shell model from a segment (id="Segment_1") and convert it to a shell:<br />
<br />
<pre><br />
# Get closed surface representation of the segment<br />
shellThickness = 3.0 # mm<br />
segmentationNode = getNode('Segmentation')<br />
segmentationNode.CreateClosedSurfaceRepresentation()<br />
polyData = segmentationNode.GetClosedSurfaceInternalRepresentation('Segment_1')<br />
<br />
# Create shell<br />
extrude = vtk.vtkLinearExtrusionFilter()<br />
extrude.SetInputData(polyData)<br />
extrude.SetExtrusionTypeToNormalExtrusion()<br />
extrude.SetScaleFactor(shellThickness)<br />
<br />
# Compute consistent surface normals<br />
triangle_filter = vtk.vtkTriangleFilter()<br />
triangle_filter.SetInputConnection(extrude.GetOutputPort())<br />
normals = vtk.vtkPolyDataNormals()<br />
normals.SetInputConnection(triangle_filter.GetOutputPort())<br />
normals.FlipNormalsOn()<br />
<br />
# Save result into new model node<br />
slicer.modules.models.logic().AddModel(normals.GetOutputPort())<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = slicer.vtkOrientedImageData()<br />
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
referenceGeometry = slicer.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(slicer.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Create keyboard shortcut for toggling sphere brush for paint and erase effects===<br />
<br />
<pre><br />
def toggleSphereBrush():<br />
segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor<br />
paintEffect = segmentEditorWidget.effectByName("Paint")<br />
isSphere = paintEffect.integerParameter('BrushSphere')<br />
# BrushSphere is "common" parameter (shared between paint and erase)<br />
paintEffect.setCommonParameter("BrushSphere", 0 if isSphere else 1)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence("s"))<br />
shortcut.connect('activated()', toggleSphereBrush)<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===Get histogram of a segmented region===<br />
<br />
<pre><br />
# Generate input data<br />
################################################<br />
<br />
# Load master volume<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()<br />
<br />
# Create segmentation<br />
segmentationNode = slicer.vtkMRMLSegmentationNode()<br />
slicer.mrmlScene.AddNode(segmentationNode)<br />
segmentationNode.CreateDefaultDisplayNodes() # only needed for display<br />
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)<br />
<br />
# Create segment<br />
tumorSeed = vtk.vtkSphereSource()<br />
tumorSeed.SetCenter(-6, 30, 28)<br />
tumorSeed.SetRadius(25)<br />
tumorSeed.Update()<br />
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])<br />
<br />
# Compute histogram<br />
################################################<br />
<br />
labelValue = 1 # label value of first segment<br />
<br />
# Get segmentation as labelmap volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)<br />
<br />
# Extract all voxels of the segment as numpy array<br />
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)<br />
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
segmentVoxels = volumeArray[labelArray==labelValue]<br />
<br />
# Compute histogram<br />
import numpy as np<br />
histogram = np.histogram(segmentVoxels, bins=50)<br />
<br />
# Plot histogram<br />
################################################<br />
<br />
slicer.util.plot(histogram, xColumnIndex = 1)<br />
</pre><br />
<br />
===Get segments visible at a selected position===<br />
<br />
Show in the console names of segments visible at a markups fiducial position:<br />
<br />
<pre><br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
markupsFiducialNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
sliceViewLabel = "Red" # any slice view where segmentation node is visible works<br />
<br />
def printSegmentNames(unused1=None, unused2=None):<br />
<br />
sliceViewWidget = slicer.app.layoutManager().sliceWidget(sliceViewLabel)<br />
segmentationsDisplayableManager = sliceViewWidget.sliceView().displayableManagerByClassName('vtkMRMLSegmentationsDisplayableManager2D')<br />
ras = [0,0,0]<br />
markupsFiducialNode.GetNthControlPointPositionWorld(0, ras)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentationsDisplayableManager.GetVisibleSegmentsForPosition(ras, segmentationNode.GetDisplayNode(), segmentIds)<br />
for idIndex in range(segmentIds.GetNumberOfValues()):<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentIds.GetValue(idIndex))<br />
print('Segment found at position {0}: {1}'.format(ras, segment.GetName()))<br />
<br />
# Observe markup node changes<br />
markupsFiducialNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, printSegmentNames)<br />
printSegmentNames()<br />
</pre><br />
<br />
===Set default segmentation options===<br />
<br />
Allow segments to overlap each other by default:<br />
<br />
<pre><br />
defaultSegmentEditorNode = slicer.vtkMRMLSegmentEditorNode()<br />
defaultSegmentEditorNode.SetOverwriteMode(slicer.vtkMRMLSegmentEditorNode.OverwriteNone)<br />
slicer.mrmlScene.AddDefaultNode(defaultSegmentEditorNode)<br />
</pre><br />
<br />
To always make this the default, add the lines above to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
===How to run segment editor effects from a script===<br />
<br />
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.<br />
<br />
==== Use Segment editor effects from script (qMRMLSegmentEditorWidget) ====<br />
<br />
Examples:<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]<br />
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]<br />
<br />
Description of effect parameters are available [https://slicer.readthedocs.io/en/latest/developer_guide/modules/segmenteditor.html#effect-parameters here].<br />
<br />
==== Use logic of effect from a script ====<br />
<br />
This example shows how to perform operations on segmentations using VTK filters ''extracted'' from an effect:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
=== Process segment using a VTK filter ===<br />
<br />
This example shows how to apply a VTK filter to a segment that dilates the image by a specified margin.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = "Segment_1"<br />
kernelSize = [3,1,5]<br />
<br />
# Export segment as vtkImageData (via temporary labelmap volume node)<br />
segmentIds = vtk.vtkStringArray()<br />
segmentIds.InsertNextValue(segmentId)<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportSegmentsToLabelmapNode(segmentationNode, segmentIds, labelmapVolumeNode)<br />
<br />
# Process segmentation<br />
segmentImageData = labelmapVolumeNode.GetImageData()<br />
erodeDilate = vtk.vtkImageDilateErode3D()<br />
erodeDilate.SetInputData(segmentImageData)<br />
erodeDilate.SetDilateValue(1)<br />
erodeDilate.SetErodeValue(0)<br />
erodeDilate.SetKernelSize(*kernelSize)<br />
erodeDilate.Update()<br />
segmentImageData.DeepCopy(erodeDilate.GetOutput())<br />
<br />
# Import segment from vtkImageData<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, segmentationNode, segmentIds)<br />
<br />
# Cleanup temporary nodes<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
===Get information from segmentation nrrd file header===<br />
<br />
You can use this code snippet to get information from segmentation (.seg.nrrd), for example when creating numpy arrays for generating training data for deep learning networks. This script can be used in any Python environment, not just inside Slicer.<br />
<br />
<pre><br />
# pip_install('pynrrd')<br />
<br />
def read_segmentation_info(filename):<br />
import nrrd<br />
header = nrrd.read_header(filename)<br />
segmentation_info = {}<br />
segments = []<br />
segment_index = 0<br />
while True:<br />
prefix = "Segment{0}_".format(segment_index)<br />
if not prefix + "ID" in header.keys():<br />
break<br />
segment = {}<br />
segment["index"] = segment_index<br />
segment["color"] = [float(i) for i in header[prefix + "Color"].split(" ")] # Segment0_Color:=0.501961 0.682353 0.501961<br />
segment["colorAutoGenerated"] = int(header[prefix + "ColorAutoGenerated"]) != 0 # Segment0_ColorAutoGenerated:=1<br />
segment["extent"] = [int(i) for i in header[prefix + "Extent"].split(" ")] # Segment0_Extent:=68 203 53 211 24 118<br />
segment["id"] = header[prefix + "ID"] # Segment0_ID:=Segment_1<br />
segment["labelValue"] = int(header[prefix + "LabelValue"]) # Segment0_LabelValue:=1<br />
segment["layer"] = int(header[prefix + "Layer"]) # Segment0_Layer:=0<br />
segment["name"] = header[prefix + "Name"] # Segment0_Name:=Segment_1<br />
segment["nameAutoGenerated"] = int(header[prefix + "NameAutoGenerated"]) != 0 # Segment0_NameAutoGenerated:=1<br />
# Segment0_Tags:=Segmentation.Status:inprogress|TerminologyEntry:Segmentation category and type - 3D Slicer General Anatomy list<br />
# ~SCT^85756007^Tissue~SCT^85756007^Tissue~^^~Anatomic codes - DICOM master list~^^~^^|<br />
tags = {}<br />
tags_str = header[prefix + "Tags"].split("|")<br />
for tag_str in tags_str:<br />
tag_str = tag_str.strip()<br />
if not tag_str:<br />
continue<br />
key, value = tag_str.split(":", maxsplit=1)<br />
tags[key] = value<br />
segment["tags"] = tags<br />
segments.append(segment)<br />
segment_index += 1<br />
segmentation_info["segments"] = segments<br />
return segmentation_info<br />
<br />
def segment_from_name(segmentation_info, segment_name):<br />
for segment in segmentation_info["segments"]:<br />
if segment_name == segment["name"]:<br />
return segment<br />
raise KeyError('segment not found by name ' + segment_name)<br />
<br />
def segment_names(segmentation_info):<br />
names = []<br />
for segment in segmentation_info["segments"]:<br />
names.append(segment["name"])<br />
return names<br />
<br />
def extract_segments(voxels, header, segmentation_info, segment_names_to_label_values):<br />
import numpy as np<br />
# Create empty array from last 3 dimensions (output will be flattened to a 3D array)<br />
output_voxels = np.zeros(voxels.shape[-3:])<br />
# Copy non-segmentation fields to the extracted header<br />
output_header = {}<br />
for key in header.keys():<br />
if not re.match("^Segment[0-9]+_.+", key):<br />
output_header[key] = header[key]<br />
# Copy extracted segments<br />
dims = len(voxels.shape)<br />
for output_segment_index, segment_name_to_label_value in enumerate(segment_names_to_label_values):<br />
# Copy relabeled voxel data<br />
segment = segment_from_name(segmentation_info, segment_name_to_label_value[0])<br />
input_label_value = segment["labelValue"]<br />
output_label_value = segment_name_to_label_value[1]<br />
if dims == 3:<br />
output_voxels[voxels == input_label_value] = output_label_value<br />
elif dims == 4:<br />
inputLayer = segment["layer"]<br />
output_voxels[voxels[inputLayer,:,:,:] == input_label_value] = output_label_value<br />
else:<br />
raise ValueError("Voxel array dimension is invalid")<br />
# Copy all segment fields corresponding to this segment<br />
for key in header.keys():<br />
prefix = "Segment{0}_".format(segment["index"])<br />
matched = re.match("^"+prefix+"(.+)", key)<br />
if matched:<br />
field_name = matched.groups()[0]<br />
if field_name == "LabelValue":<br />
value = output_label_value<br />
elif field_name == "Layer":<br />
# output is a single layer (3D volume)<br />
value = 0<br />
else:<br />
value = header[key]<br />
output_header["Segment{0}_".format(output_segment_index) + field_name] = value<br />
# Remove unnecessary 4th dimension (volume is collapsed into 3D)<br />
if dims == 4:<br />
# Remove "none" from "none (0,1,0) (0,0,-1) (-1.2999954223632812,0,0)"<br />
output_header["space directions"] = output_header["space directions"][-3:,:]<br />
# Remove "list" from "list domain domain domain"<br />
output_header["kinds"] = output_header["kinds"][-3:]<br />
return output_voxels, output_header<br />
<br />
# Read segmentation and show some information about segments<br />
filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationOverlapping.seg.nrrd"<br />
segmentation_info = read_segmentation_info(filename)<br />
number_of_segments = len(segmentation_info["segments"])<br />
names = segment_names(segmentation_info)<br />
label0 = segment_from_name(segmentation_info, names[0])["labelValue"]<br />
print("Number of segments: " + str())<br />
print("Segment names: " + str(names))<br />
print("Label value of {0}: {1}".format(names[0], label0))<br />
<br />
# Extract selected segments with chosen label values<br />
extracted_filename = "c:/Users/andra/OneDrive/Projects/SegmentationPynrrd/SegmentationExtracted.seg.nrrd"<br />
voxels, header = nrrd.read(filename)<br />
segment_list = [("Segment_1", 10), ("Segment_3", 12), ("Segment_4", 6)]<br />
extracted_voxels, extracted_header = extract_segments(voxels, header, segmentation_info, segment_list)<br />
nrrd.write(extracted_filename, extracted_voxels, extracted_header)<br />
</pre><br />
<br />
==Quantifying segments==<br />
<br />
===Get centroid of each segment===<br />
<br />
Place a markups fiducial point at the centroid of each segment.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute centroids<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.centroid_ras.enabled", str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Place a markup point in each centroid<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
for segmentId in stats['SegmentIDs']:<br />
centroid_ras = stats[segmentId,"LabelmapSegmentStatisticsPlugin.centroid_ras"]<br />
segmentName = segmentationNode.GetSegmentation().GetSegment(segmentId).GetName()<br />
markupsNode.AddFiducialFromArray(centroid_ras, segmentName)<br />
</pre><br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
Complete list of available parameters can be obtained by running <code>segStatLogic.getParameterNode().GetParameterNames()</code>.<br />
<br />
==Markups==<br />
<br />
===Load markups fiducial list from file===<br />
<br />
Markups fiducials can be loaded from file:<br />
<br />
<pre><br />
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')<br />
</pre><br />
<br />
===Adding Fiducials Programatically===<br />
<br />
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial()<br />
</pre><br />
<br />
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)<br />
</pre><br />
<br />
===Add a button to module GUI to activate fiducial placement===<br />
<br />
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).<br />
<br />
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.<br />
<br />
<pre><br />
w=slicer.qSlicerMarkupsPlaceWidget()<br />
w.setMRMLScene(slicer.mrmlScene)<br />
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()<br />
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))<br />
# Hide all buttons and only show place button<br />
w.buttonsVisible=False<br />
w.placeButton().show()<br />
w.show()<br />
</pre><br />
<br />
===Adding Fiducials via Mouse Clicks===<br />
<br />
You can also set the mouse mode into Markups fiducial placement by calling:<br />
<br />
<pre><br />
placeModePersistence = 1<br />
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)<br />
</pre><br />
<br />
A lower level way to do this is via the selection and interaction nodes:<br />
<br />
<pre><br />
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
placeModePersistence = 1<br />
interactionNode.SetPlaceModePersistence(placeModePersistence)<br />
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place<br />
interactionNode.SetCurrentInteractionMode(1)<br />
</pre><br />
<br />
To switch back to view transform once you're done placing fiducials:<br />
<br />
<pre><br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
interactionNode.SwitchToViewTransformMode()<br />
# also turn off place mode persistence if required<br />
interactionNode.SetPlaceModePersistence(0)<br />
</pre><br />
<br />
===Access to Fiducial Properties===<br />
<br />
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:<br />
<br />
<pre><br />
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")<br />
n = fidNode.AddFiducial(4.0, 5.5, -6.0)<br />
fidNode.SetNthFiducialLabel(n, "new label")<br />
# each markup is given a unique id which can be accessed from the superclass level<br />
id1 = fidNode.GetNthMarkupID(n)<br />
# manually set the position<br />
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)<br />
# set the label<br />
fidNode.SetNthFiducialLabel(n, "New label")<br />
# set the selected flag, only selected = 1 fiducials will be passed to CLIs<br />
fidNode.SetNthFiducialSelected(n, 1)<br />
# set the visibility flag<br />
fidNode.SetNthFiducialVisibility(n, 0) <br />
</pre><br />
<br />
You can loop over the fiducials in a list and get the coordinates:<br />
<br />
<pre><br />
fidList = slicer.util.getNode('F')<br />
numFids = fidList.GetNumberOfFiducials()<br />
for i in range(numFids):<br />
ras = [0,0,0]<br />
fidList.GetNthFiducialPosition(i,ras)<br />
# the world position is the RAS position with any transform matrices applied<br />
world = [0,0,0,0]<br />
fidList.GetNthFiducialWorldCoordinates(0,world)<br />
print(i,": RAS =",ras,", world =",world)<br />
</pre><br />
<br />
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.<br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(myButton)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Subject hierarchy plugin offering view context menu action===<br />
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 [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107 here]. This example makes it easier to create such a plugin.<br />
<syntaxhighlight lang="python"><br />
import vtk, qt, ctk, slicer<br />
from slicer.ScriptedLoadableModule import *<br />
from slicer.util import VTKObservationMixin<br />
<br />
from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin<br />
<br />
class ViewContextMenu(ScriptedLoadableModule):<br />
"""Uses ScriptedLoadableModule base class, available at:<br />
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py<br />
"""<br />
<br />
def __init__(self, parent):<br />
ScriptedLoadableModule.__init__(self, parent)<br />
self.parent.title = "Markup Editor"<br />
self.parent.categories = ["SlicerMorph", "Labs"]<br />
self.parent.dependencies = []<br />
self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]<br />
self.parent.helpText = """<br />
A tool to manipulate Markups using the Segment Editor as a geometry backend<br />
"""<br />
self.parent.helpText += self.getDefaultModuleDocumentationLink()<br />
self.parent.acknowledgementText = """<br />
This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,<br />
through a NSF ABI Development grant, "An Integrated Platform for Retrieval,<br />
Visualization and Analysis of 3D Morphology From Digital Biological Collections"<br />
(Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).<br />
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,<br />
Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.<br />
and was partially funded by NIH grant 3P41RR013218-12S1.<br />
"""<br />
<br />
#<br />
# register subject hierarchy plugin once app is initialized<br />
#<br />
def onStartupCompleted():<br />
import SubjectHierarchyPlugins<br />
from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin<br />
scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)<br />
scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginHandler.registerPlugin(scriptedPlugin)<br />
print('ViewContextMenuSubjectHierarchyPlugin loaded')<br />
slicer.app.connect("startupCompleted()", onStartupCompleted)<br />
<br />
<br />
class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):<br />
<br />
# Necessary static member to be able to set python source to scripted subject hierarchy plugin<br />
filePath = __file__<br />
<br />
def __init__(self, scriptedPlugin):<br />
self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)<br />
self.viewAction.objectName = 'CustomViewAction'<br />
self.viewAction.connect("triggered()", self.onViewAction)<br />
<br />
def onViewAction(self):<br />
print(f"VIEW ACTION")<br />
<br />
def viewContextMenuActions(self):<br />
return [self.viewAction]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData=None):<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = list(pluginLogic.availableViewMenuActionNames())<br />
menuActions.append('CustomViewAction')<br />
pluginLogic.setDisplayedViewMenuActionNames(menuActions)<br />
self.viewAction.visible = True<br />
</syntaxhighlight><br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
import JupyterNotebooksLib as slicernb<br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
<br />
import matplotlib.pyplot as plt<br />
import numpy as np<br />
<br />
def f(t):<br />
s1 = np.cos(2*np.pi*t)<br />
e1 = np.exp(-t)<br />
return s1 * e1<br />
<br />
t1 = np.arange(0.0, 5.0, 0.1)<br />
t2 = np.arange(0.0, 5.0, 0.02)<br />
t3 = np.arange(0.0, 2.0, 0.01)<br />
<br />
<br />
fig, axs = plt.subplots(2, 1, constrained_layout=True)<br />
axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-')<br />
axs[0].set_title('subplot 1')<br />
axs[0].set_xlabel('distance (m)')<br />
axs[0].set_ylabel('Damped oscillation')<br />
fig.suptitle('This is a somewhat long figure title', fontsize=16)<br />
<br />
axs[1].plot(t3, np.cos(2*np.pi*t3), '--')<br />
axs[1].set_xlabel('time (s)')<br />
axs[1].set_title('subplot 2')<br />
axs[1].set_ylabel('Undamped')<br />
<br />
slicernb.MatplotlibDisplay(matplotlib.pyplot)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre><br />
<br />
=== Install a module directly from a git repository ===<br />
This can be useful for sharing code in development without requiring a restart of Slicer.<br />
<br />
https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=63323Documentation/Nightly/ScriptRepository2020-07-11T19:42:53Z<p>Pieper: /* Subject hierarchy plugin offering view context menu action */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})<br />
</pre><br />
<br />
*Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
<br />
===How to 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:===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser<br />
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Switch to a different module==<br />
<br />
This utility function can be used to open a different module:<br />
<br />
<pre><br />
slicer.util.selectModule('DICOM')<br />
</pre><br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
*How to define/edit a circular region of interest in a slice viewer?<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup planes==<br />
<br />
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
planeNodeNames = ['P', 'P_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
planeNormalVectors = []<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNormalVector = [0.0, 0.0, 0.0]<br />
planeNode.GetNormalWorld(planeNormalVector)<br />
planeNormalVectors.append(planeNormalVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))<br />
<br />
# Observe plane node changes<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup lines==<br />
<br />
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
lineNodeNames = ['L', 'L_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
import numpy as np<br />
lineDirectionVectors = []<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineStartPos = np.zeros(3)<br />
lineEndPos = np.zeros(3)<br />
lineNode.GetNthControlPointPositionWorld(0, lineStartPos)<br />
lineNode.GetNthControlPointPositionWorld(1, lineEndPos)<br />
lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)<br />
lineDirectionVectors.append(lineDirectionVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))<br />
<br />
# Observe line node changes<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligne coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.<br />
<br />
<pre><br />
<br />
def viewContextMenuActions(self):<br />
return [self.doSomething]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData):<br />
if not itemID:<br />
logging.error('Invalid item for view context menu ' + str(itemID))<br />
return<br />
<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
shNode = pluginHandlerSingleton.subjectHierarchyNode()<br />
if shNode is None:<br />
logging.error('Failed to access subject hierarchy node')<br />
return<br />
<br />
associatedNode = shNode.GetItemDataNode(itemID)<br />
if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):<br />
return<br />
<br />
self.viewMenuEventData = eventData<br />
self.viewMenuEventData['NodeID'] = associatedNode.GetID()<br />
<br />
def onDoSomething(self):<br />
nodeID = self.viewMenuEventData['NodeID']<br />
markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)<br />
if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):<br />
logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))<br />
return<br />
<br />
componentIndex = self.viewMenuEventData['ComponentIndex']<br />
markupID = markupsNode.GetNthMarkupID(componentIndex)<br />
<br />
# Do something with the clicked fiducial<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# Create model node<br />
planeSource = vtk.vtkPlaneSource()<br />
planeSource.SetOrigin(-50.0, -50.0, 0.0)<br />
planeSource.SetPoint1(50.0, -50.0, 0.0)<br />
planeSource.SetPoint2(-50.0, 50.0, 0.0)<br />
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())<br />
<br />
# Tune display properties<br />
modelDisplay = model.GetDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
<br />
# Add texture (just use image of an ellipsoid)<br />
e = vtk.vtkImageEllipsoidSource()<br />
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Load volume from .vti file==<br />
<br />
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:<br />
<br />
<pre><br />
reader=vtk.vtkXMLImageDataReader()<br />
reader.SetFileName("/path/to/file.vti")<br />
reader.Update()<br />
imageData = reader.GetOutput()<br />
spacing = imageData.GetSpacing()<br />
origin = imageData.GetOrigin()<br />
imageData.SetOrigin(0,0,0)<br />
imageData.SetSpacing(1,1,1)<br />
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.SetSpacing(spacing)<br />
volumeNode.SetOrigin(origin)<br />
slicer.util.setSliceViewerLayers(volumeNode, fit=True)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array(‘Volume’)<br />
label = array(‘Volume-label’)<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt(‘values.txt’, values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Show comparison view of all model files a folder==<br />
<br />
<pre><br />
# Inputs<br />
modelDir = "c:/some/folder/containing/models"<br />
modelFileExt = "stl"<br />
numberOfColumns = 4<br />
<br />
import math<br />
import os<br />
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))<br />
<br />
# Create a custom layout<br />
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))<br />
customLayoutId=567 # we pick a random id that is not used by others<br />
slicer.app.setRenderPaused(True)<br />
customLayout = '<layout type="vertical">'<br />
viewIndex = 0<br />
for rowIndex in range(numberOfRows):<br />
customLayout += '<item><layout type="horizontal">'<br />
for colIndex in range(numberOfColumns):<br />
name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)<br />
customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name<br />
customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'<br />
viewIndex += 1<br />
customLayout += '</layout></item>'<br />
<br />
customLayout += '</layout>'<br />
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):<br />
slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)<br />
<br />
slicer.app.layoutManager().setLayout(customLayoutId)<br />
<br />
# Load and show each model in a view<br />
for modelIndex, modelFile in enumerate(modelFiles):<br />
# Show only one model in each view<br />
name = os.path.basename(modelFile)<br />
viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")<br />
viewNode.LinkedControlOn()<br />
modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)<br />
modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())<br />
<br />
slicer.app.setRenderPaused(False)<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManagerCollection = vtk.vtkCollection()<br />
sliceView.getDisplayableManagers(displayableManagerCollection)<br />
for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):<br />
displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)<br />
if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):<br />
continue<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
layoutName = "TestSlice"<br />
layoutLabel = "TS"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(*layoutColor)<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
modelDisplayableManager = None<br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
managers = vtk.vtkCollection()<br />
threeDViewWidget.getDisplayableManagers(managers)<br />
for i in range(managers.GetNumberOfItems()):<br />
obj = managers.GetItemAsObject(i)<br />
if obj.IsA('vtkMRMLModelDisplayableManager'):<br />
modelDisplayableManager = obj<br />
break<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get axial slice as numpy array==<br />
<br />
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:<br />
<br />
<pre><br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
sliceIndex = 12<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array<br />
slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print(voxels.shape)<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Add noise to image==<br />
<br />
This example shows how to add simulated noise to a volume.<br />
<br />
<pre><br />
import SampleData<br />
import numpy as np<br />
<br />
# Get a sample input volume node<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Get volume as numpy array and add noise<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)<br />
slicer.util.arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
==Apply random deformations to image==<br />
<br />
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.<br />
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.<br />
Thin-plate spline transform is computed from the original and transformed point list.<br />
<br />
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940<br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export labelmap matching reference geometry of the segmentation:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode, slicer.vtkSegmentation.EXTENT_REFERENCE_GEOMETRY)<br />
</pre><br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export by pressing Ctrl+Shift+s key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = slicer.vtkOrientedImageData()<br />
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===Get histogram of a segmented region===<br />
<br />
<pre><br />
# Generate input data<br />
################################################<br />
<br />
# Load master volume<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()<br />
<br />
# Create segmentation<br />
segmentationNode = slicer.vtkMRMLSegmentationNode()<br />
slicer.mrmlScene.AddNode(segmentationNode)<br />
segmentationNode.CreateDefaultDisplayNodes() # only needed for display<br />
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)<br />
<br />
# Create segment<br />
tumorSeed = vtk.vtkSphereSource()<br />
tumorSeed.SetCenter(-6, 30, 28)<br />
tumorSeed.SetRadius(25)<br />
tumorSeed.Update()<br />
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])<br />
<br />
# Compute histogram<br />
################################################<br />
<br />
labelValue = 1 # label value of first segment<br />
<br />
# Get segmentation as labelmap volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)<br />
<br />
# Extract all voxels of the segment as numpy array<br />
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)<br />
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
segmentVoxels = volumeArray[labelArray==labelValue]<br />
<br />
# Compute histogram<br />
import numpy as np<br />
histogram = np.histogram(segmentVoxels, bins=50)<br />
<br />
# Plot histogram<br />
################################################<br />
<br />
slicer.util.plot(histogram, xColumnIndex = 1)<br />
</pre><br />
<br />
===How to run segment editor effects from a script===<br />
<br />
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.<br />
<br />
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):<br />
<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]<br />
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]<br />
<br />
This example shows how to perform operations on segmentations using VTK filters:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
==Quantifying segments==<br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
==Markups==<br />
<br />
===Load markups fiducial list from file===<br />
<br />
Markups fiducials can be loaded from file:<br />
<br />
<pre><br />
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')<br />
</pre><br />
<br />
===Adding Fiducials Programatically===<br />
<br />
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial()<br />
</pre><br />
<br />
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)<br />
</pre><br />
<br />
===Add a button to module GUI to activate fiducial placement===<br />
<br />
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).<br />
<br />
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.<br />
<br />
<pre><br />
w=slicer.qSlicerMarkupsPlaceWidget()<br />
w.setMRMLScene(slicer.mrmlScene)<br />
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()<br />
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))<br />
# Hide all buttons and only show place button<br />
w.buttonsVisible=False<br />
w.placeButton().show()<br />
w.show()<br />
</pre><br />
<br />
===Adding Fiducials via Mouse Clicks===<br />
<br />
You can also set the mouse mode into Markups fiducial placement by calling:<br />
<br />
<pre><br />
placeModePersistence = 1<br />
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)<br />
</pre><br />
<br />
A lower level way to do this is via the selection and interaction nodes:<br />
<br />
<pre><br />
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
placeModePersistence = 1<br />
interactionNode.SetPlaceModePersistence(placeModePersistence)<br />
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place<br />
interactionNode.SetCurrentInteractionMode(1)<br />
</pre><br />
<br />
To switch back to view transform once you're done placing fiducials:<br />
<br />
<pre><br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
interactionNode.SwitchToViewTransformMode()<br />
# also turn off place mode persistence if required<br />
interactionNode.SetPlaceModePersistence(0)<br />
</pre><br />
<br />
===Access to Fiducial Properties===<br />
<br />
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:<br />
<br />
<pre><br />
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")<br />
n = fidNode.AddFiducial(4.0, 5.5, -6.0)<br />
fidNode.SetNthFiducialLabel(n, "new label")<br />
# each markup is given a unique id which can be accessed from the superclass level<br />
id1 = fidNode.GetNthMarkupID(n)<br />
# manually set the position<br />
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)<br />
# set the label<br />
fidNode.SetNthFiducialLabel(n, "New label")<br />
# set the selected flag, only selected = 1 fiducials will be passed to CLIs<br />
fidNode.SetNthFiducialSelected(n, 1)<br />
# set the visibility flag<br />
fidNode.SetNthFiducialVisibility(n, 0) <br />
</pre><br />
<br />
You can loop over the fiducials in a list and get the coordinates:<br />
<br />
<pre><br />
fidList = slicer.util.getNode('F')<br />
numFids = fidList.GetNumberOfFiducials()<br />
for i in range(numFids):<br />
ras = [0,0,0]<br />
fidList.GetNthFiducialPosition(i,ras)<br />
# the world position is the RAS position with any transform matrices applied<br />
world = [0,0,0,0]<br />
fidList.GetNthFiducialWorldCoordinates(0,world)<br />
print(i,": RAS =",ras,", world =",world)<br />
</pre><br />
<br />
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.<br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(b)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Subject hierarchy plugin offering view context menu action===<br />
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 [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107 here]. This example makes it easier to create such a plugin.<br />
<syntaxhighlight lang="python"><br />
import vtk, qt, ctk, slicer<br />
from slicer.ScriptedLoadableModule import *<br />
from slicer.util import VTKObservationMixin<br />
<br />
from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin<br />
<br />
class ViewContextMenu(ScriptedLoadableModule):<br />
"""Uses ScriptedLoadableModule base class, available at:<br />
https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py<br />
"""<br />
<br />
def __init__(self, parent):<br />
ScriptedLoadableModule.__init__(self, parent)<br />
self.parent.title = "Markup Editor"<br />
self.parent.categories = ["SlicerMorph", "Labs"]<br />
self.parent.dependencies = []<br />
self.parent.contributors = ["Steve Pieper (Isomics, Inc.)"]<br />
self.parent.helpText = """<br />
A tool to manipulate Markups using the Segment Editor as a geometry backend<br />
"""<br />
self.parent.helpText += self.getDefaultModuleDocumentationLink()<br />
self.parent.acknowledgementText = """<br />
This module was developed by Steve Pieper, Sara Rolfe and Murat Maga,<br />
through a NSF ABI Development grant, "An Integrated Platform for Retrieval,<br />
Visualization and Analysis of 3D Morphology From Digital Biological Collections"<br />
(Award Numbers: 1759883 (Murat Maga), 1759637 (Adam Summers), 1759839 (Douglas Boyer)).<br />
This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc.,<br />
Andras Lasso, PerkLab, and Steve Pieper, Isomics, Inc.<br />
and was partially funded by NIH grant 3P41RR013218-12S1.<br />
"""<br />
<br />
#<br />
# register subject hierarchy plugin once app is initialized<br />
#<br />
def onStartupCompleted():<br />
import SubjectHierarchyPlugins<br />
from ViewContextMenu import ViewContextMenuSubjectHierarchyPlugin<br />
scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)<br />
scriptedPlugin.setPythonSource(ViewContextMenuSubjectHierarchyPlugin.filePath)<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginHandler.registerPlugin(scriptedPlugin)<br />
print('ViewContextMenuSubjectHierarchyPlugin loaded')<br />
slicer.app.connect("startupCompleted()", onStartupCompleted)<br />
<br />
<br />
class ViewContextMenuSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):<br />
<br />
# Necessary static member to be able to set python source to scripted subject hierarchy plugin<br />
filePath = __file__<br />
<br />
def __init__(self, scriptedPlugin):<br />
self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)<br />
self.viewAction.objectName = 'CustomViewAction'<br />
self.viewAction.connect("triggered()", self.onViewAction)<br />
<br />
def onViewAction(self):<br />
print(f"VIEW ACTION")<br />
<br />
def viewContextMenuActions(self):<br />
return [self.viewAction]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData=None):<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = list(pluginLogic.availableViewMenuActionNames())<br />
menuActions.append('CustomViewAction')<br />
pluginLogic.setDisplayedViewMenuActionNames(menuActions)<br />
self.viewAction.visible = True<br />
</syntaxhighlight><br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
import JupyterNotebooksLib as slicernb<br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
<br />
import matplotlib.pyplot as plt<br />
import numpy as np<br />
<br />
def f(t):<br />
s1 = np.cos(2*np.pi*t)<br />
e1 = np.exp(-t)<br />
return s1 * e1<br />
<br />
t1 = np.arange(0.0, 5.0, 0.1)<br />
t2 = np.arange(0.0, 5.0, 0.02)<br />
t3 = np.arange(0.0, 2.0, 0.01)<br />
<br />
<br />
fig, axs = plt.subplots(2, 1, constrained_layout=True)<br />
axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-')<br />
axs[0].set_title('subplot 1')<br />
axs[0].set_xlabel('distance (m)')<br />
axs[0].set_ylabel('Damped oscillation')<br />
fig.suptitle('This is a somewhat long figure title', fontsize=16)<br />
<br />
axs[1].plot(t3, np.cos(2*np.pi*t3), '--')<br />
axs[1].set_xlabel('time (s)')<br />
axs[1].set_title('subplot 2')<br />
axs[1].set_ylabel('Undamped')<br />
<br />
slicernb.MatplotlibDisplay(matplotlib.pyplot)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre><br />
<br />
=== Install a module directly from a git repository ===<br />
This can be useful for sharing code in development without requiring a restart of Slicer.<br />
<br />
https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Extensions/Radiomics&diff=63316Documentation/Nightly/Extensions/Radiomics2020-06-28T14:50:27Z<p>Pieper: </p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
Extension: [[Documentation/{{documentation/version}}/Extensions/Radiomics|Radiomics]]<br><br />
Acknowledgments:<br />
This work is funded in part by the National Institutes of Health National Cancer Institute through the Informatics Technology for Cancer Research program grant U24 CA194354 (PI Hugo Aerts).<br><br />
Authors: Andrey Fedorov (SPL), Joost van Griethuysen (NKI), Nicole Aucoin (SPL), Jean-Christophe Fillion-Robin (Kitware), Steve Pieper (Isomics), Hugo Aerts (DFCI)<br><br />
Contact: pyradiomics community https://discourse.slicer.org/c/community/radiomics<br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
<br />
[[Image:RadiomicsExtension.png|128px|left|]]<br />
<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
{|<br />
|<br />
Radiomics provides a 3D Slicer interface to the [http://pyradiomics.readthedocs.io/en/latest/ pyradiomics library]. pyradiomics is an open-source python package for the extraction of Radiomics features from medical imaging. <br />
<br />
Usage instructions for SlicerRadiomics are available at https://github.com/Radiomics/SlicerRadiomics/blob/master/USAGE.md<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
<br />
*Joost JM van Griethuysen, Andriy Fedorov, Chintan Parmar, Ahmed Hosny, Nicole Aucoin, Vivek Narayan, Regina GH Beets-Tan, Jean-Christophe Fillion-Robin, Steve Pieper, Hugo JWL Aerts, “Computational Radiomics System to Decode the Radiographic Phenotype”; Submitted 2017<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}<br />
{{documentation/{{documentation/version}}/module-developerinfo}}<br />
<br />
Source code: https://github.com/Radiomics/SlicerRadiomics<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
[[Category:Documentation/{{documentation/version}}/Modules/Quantification]]<br />
<!-- ---------------------------- -->|}</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=63315Documentation/Nightly/ScriptRepository2020-06-27T16:40:35Z<p>Pieper: Add git install of module example</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
Additional options may be specified in <code>properties</code> argument. For example, load an image stack by disabling <code>singleFile</code> option:<br />
<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/SomeImage/file001.png', {'singleFile': False})<br />
</pre><br />
<br />
*Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
<br />
===How to 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:===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser<br />
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Switch to a different module==<br />
<br />
This utility function can be used to open a different module:<br />
<br />
<pre><br />
slicer.util.selectModule('DICOM')<br />
</pre><br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
*How to define/edit a circular region of interest in a slice viewer?<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup planes==<br />
<br />
Measure angle between two markup plane nodes. Whenever any of the plane nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
planeNodeNames = ['P', 'P_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
planeNormalVectors = []<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNormalVector = [0.0, 0.0, 0.0]<br />
planeNode.GetNormalWorld(planeNormalVector)<br />
planeNormalVectors.append(planeNormalVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(planeNormalVectors[0], planeNormalVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between planes {0} and {1} = {2:0.3f}'.format(planeNodeNames[0], planeNodeNames[1], angleDeg))<br />
<br />
# Observe plane node changes<br />
for planeNodeName in planeNodeNames:<br />
planeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsPlaneNode', planeNodeName)<br />
planeNode.AddObserver(slicer.vtkMRMLMarkupsPlaneNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Measure angle between two markup lines==<br />
<br />
Measure angle between two markup line nodes. Whenever either line is moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
lineNodeNames = ['L', 'L_1']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
import numpy as np<br />
lineDirectionVectors = []<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineStartPos = np.zeros(3)<br />
lineEndPos = np.zeros(3)<br />
lineNode.GetNthControlPointPositionWorld(0, lineStartPos)<br />
lineNode.GetNthControlPointPositionWorld(1, lineEndPos)<br />
lineDirectionVector = (lineEndPos-lineStartPos)/np.linalg.norm(lineEndPos-lineStartPos)<br />
lineDirectionVectors.append(lineDirectionVector)<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(lineDirectionVectors[0], lineDirectionVectors[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between lines {0} and {1} = {2:0.3f}'.format(lineNodeNames[0], lineNodeNames[1], angleDeg))<br />
<br />
# Observe line node changes<br />
for lineNodeName in lineNodeNames:<br />
lineNode = slicer.util.getFirstNodeByClassByName('vtkMRMLMarkupsLineNode', lineNodeName)<br />
lineNode.AddObserver(slicer.vtkMRMLMarkupsLineNode.PointModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligne coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
Subject hierarchy plugins can offer actions in the view context menu when right-clicking objects that support such picking (such as Markups fiducials). A comprehensive [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py subject hierarchy plugin example] is for the Annotations module.<br />
<br />
<pre><br />
<br />
def viewContextMenuActions(self):<br />
return [self.doSomething]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData):<br />
if not itemID:<br />
logging.error('Invalid item for view context menu ' + str(itemID))<br />
return<br />
<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
shNode = pluginHandlerSingleton.subjectHierarchyNode()<br />
if shNode is None:<br />
logging.error('Failed to access subject hierarchy node')<br />
return<br />
<br />
associatedNode = shNode.GetItemDataNode(itemID)<br />
if not associatedNode or not associatedNode.IsA("vtkMRMLMarkupsNode"):<br />
return<br />
<br />
self.viewMenuEventData = eventData<br />
self.viewMenuEventData['NodeID'] = associatedNode.GetID()<br />
<br />
def onDoSomething(self):<br />
nodeID = self.viewMenuEventData['NodeID']<br />
markupsNode = slicer.mrmlScene.GetNodeByID(nodeID)<br />
if markupsNode is None or not markupsNode.IsA("vtkMRMLMarkupsNode"):<br />
logging.error('Failed to get fiducial markups node by ID ' + str(nodeID))<br />
return<br />
<br />
componentIndex = self.viewMenuEventData['ComponentIndex']<br />
markupID = markupsNode.GetNthMarkupID(componentIndex)<br />
<br />
# Do something with the clicked fiducial<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# Create model node<br />
planeSource = vtk.vtkPlaneSource()<br />
planeSource.SetOrigin(-50.0, -50.0, 0.0)<br />
planeSource.SetPoint1(50.0, -50.0, 0.0)<br />
planeSource.SetPoint2(-50.0, 50.0, 0.0)<br />
model = slicer.modules.models.logic().AddModel(planeSource.GetOutputPort())<br />
<br />
# Tune display properties<br />
modelDisplay = model.GetDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
<br />
# Add texture (just use image of an ellipsoid)<br />
e = vtk.vtkImageEllipsoidSource()<br />
modelDisplay.SetTextureImageDataConnection(e.GetOutputPort())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Load volume from .vti file==<br />
<br />
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:<br />
<br />
<pre><br />
reader=vtk.vtkXMLImageDataReader()<br />
reader.SetFileName("/path/to/file.vti")<br />
reader.Update()<br />
imageData = reader.GetOutput()<br />
spacing = imageData.GetSpacing()<br />
origin = imageData.GetOrigin()<br />
imageData.SetOrigin(0,0,0)<br />
imageData.SetSpacing(1,1,1)<br />
volumeNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.SetSpacing(spacing)<br />
volumeNode.SetOrigin(origin)<br />
slicer.util.setSliceViewerLayers(volumeNode, fit=True)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array(‘Volume’)<br />
label = array(‘Volume-label’)<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt(‘values.txt’, values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Show comparison view of all model files a folder==<br />
<br />
<pre><br />
# Inputs<br />
modelDir = "c:/some/folder/containing/models"<br />
modelFileExt = "stl"<br />
numberOfColumns = 4<br />
<br />
import math<br />
import os<br />
modelFiles = list(f for f in os.listdir(modelDir) if f.endswith('.' + modelFileExt))<br />
<br />
# Create a custom layout<br />
numberOfRows = int(math.ceil(len(modelFiles)/numberOfColumns))<br />
customLayoutId=567 # we pick a random id that is not used by others<br />
slicer.app.setRenderPaused(True)<br />
customLayout = '<layout type="vertical">'<br />
viewIndex = 0<br />
for rowIndex in range(numberOfRows):<br />
customLayout += '<item><layout type="horizontal">'<br />
for colIndex in range(numberOfColumns):<br />
name = os.path.basename(modelFiles[viewIndex]) if viewIndex < len(modelFiles) else "compare "+str(viewIndex)<br />
customLayout += '<item><view class="vtkMRMLViewNode" singletontag="'+name<br />
customLayout += '"><property name="viewlabel" action="default">'+name+'</property></view></item>'<br />
viewIndex += 1<br />
customLayout += '</layout></item>'<br />
<br />
customLayout += '</layout>'<br />
if not slicer.app.layoutManager().layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId, customLayout):<br />
slicer.app.layoutManager().layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout)<br />
<br />
slicer.app.layoutManager().setLayout(customLayoutId)<br />
<br />
# Load and show each model in a view<br />
for modelIndex, modelFile in enumerate(modelFiles):<br />
# Show only one model in each view<br />
name = os.path.basename(modelFile)<br />
viewNode = slicer.mrmlScene.GetSingletonNode(name, "vtkMRMLViewNode")<br />
viewNode.LinkedControlOn()<br />
modelNode = slicer.util.loadModel(modelDir+"/"+modelFile)<br />
modelNode.GetDisplayNode().AddViewNodeID(viewNode.GetID())<br />
<br />
slicer.app.setRenderPaused(False)<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManagerCollection = vtk.vtkCollection()<br />
sliceView.getDisplayableManagers(displayableManagerCollection)<br />
for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):<br />
displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)<br />
if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):<br />
continue<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
layoutName = "TestSlice"<br />
layoutLabel = "TS"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
layoutColor = [1.0, 1.0, 0.0]<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(layoutColor)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(*layoutColor)<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
modelDisplayableManager = None<br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
managers = vtk.vtkCollection()<br />
threeDViewWidget.getDisplayableManagers(managers)<br />
for i in range(managers.GetNumberOfItems()):<br />
obj = managers.GetItemAsObject(i)<br />
if obj.IsA('vtkMRMLModelDisplayableManager'):<br />
modelDisplayableManager = obj<br />
break<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get axial slice as numpy array==<br />
<br />
An axis-aligned (axial/sagittal/coronal/) slices of a volume can be extracted using simple numpy array indexing. For example:<br />
<br />
<pre><br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
sliceIndex = 12<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # Get volume as numpy array<br />
slice = voxels[sliceIndex:,:] # Get one slice of the volume as numpy array<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print(voxels.shape)<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Add noise to image==<br />
<br />
This example shows how to add simulated noise to a volume.<br />
<br />
<pre><br />
import SampleData<br />
import numpy as np<br />
<br />
# Get a sample input volume node<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Get volume as numpy array and add noise<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
voxels[:] = voxels + np.random.normal(0.0, 20.0, size=voxels.shape)<br />
slicer.util.arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
==Apply random deformations to image==<br />
<br />
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.<br />
Control points are placed on a regularly spaced grid and then each control point is displaced by a random amount.<br />
Thin-plate spline transform is computed from the original and transformed point list.<br />
<br />
https://gist.github.com/lassoan/428af5285da75dc033d32ebff65ba940<br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export by pressing Ctrl+Shift+s key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
segmentationNode = getNode("Segmentation")<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
exportFolderItemId = shNode.CreateFolderItem(shNode.GetSceneItemID(), "Segments")<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModels(segmentationNode, exportFolderItemId)<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = slicer.vtkOrientedImageData()<br />
segmentationNode.GetBinaryLabelmapRepresentation(segmentID, image)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
segmentationNode.GetClosedSurfaceRepresentation(segmentID, outputPolyData)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===Get histogram of a segmented region===<br />
<br />
<pre><br />
# Generate input data<br />
################################################<br />
<br />
# Load master volume<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
masterVolumeNode = sampleDataLogic.downloadMRBrainTumor1()<br />
<br />
# Create segmentation<br />
segmentationNode = slicer.vtkMRMLSegmentationNode()<br />
slicer.mrmlScene.AddNode(segmentationNode)<br />
segmentationNode.CreateDefaultDisplayNodes() # only needed for display<br />
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(masterVolumeNode)<br />
<br />
# Create segment<br />
tumorSeed = vtk.vtkSphereSource()<br />
tumorSeed.SetCenter(-6, 30, 28)<br />
tumorSeed.SetRadius(25)<br />
tumorSeed.Update()<br />
segmentationNode.AddSegmentFromClosedSurfaceRepresentation(tumorSeed.GetOutput(), "Segment A", [1.0,0.0,0.0])<br />
<br />
# Compute histogram<br />
################################################<br />
<br />
labelValue = 1 # label value of first segment<br />
<br />
# Get segmentation as labelmap volume node<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, masterVolumeNode)<br />
<br />
# Extract all voxels of the segment as numpy array<br />
volumeArray = slicer.util.arrayFromVolume(masterVolumeNode)<br />
labelArray = slicer.util.arrayFromVolume(labelmapVolumeNode)<br />
segmentVoxels = volumeArray[labelArray==labelValue]<br />
<br />
# Compute histogram<br />
import numpy as np<br />
histogram = np.histogram(segmentVoxels, bins=50)<br />
<br />
# Plot histogram<br />
################################################<br />
<br />
slicer.util.plot(histogram, xColumnIndex = 1)<br />
</pre><br />
<br />
===How to run segment editor effects from a script===<br />
<br />
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.<br />
<br />
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):<br />
<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
*[https://gist.github.com/lassoan/4d0b94bda52d5b099432e424e03aa2b1 segment cranial cavity automatically in dry bone skull CT]<br />
*[https://gist.github.com/lassoan/84d1f9a093dbb6a46c0fcc89279d8088 remove patient table from CT image]<br />
<br />
This example shows how to perform operations on segmentations using VTK filters:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
==Quantifying segments==<br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
==Markups==<br />
<br />
===Load markups fiducial list from file===<br />
<br />
Markups fiducials can be loaded from file:<br />
<br />
<pre><br />
slicer.util.loadMarkupsFiducialList('/path/to/list/F.fcsv')<br />
</pre><br />
<br />
===Adding Fiducials Programatically===<br />
<br />
Markups fiducials can be added to the currently active list from the python console by using the following module logic command:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial()<br />
</pre><br />
<br />
The command with no arguments will place a new fiducial at the origin. You can also pass it an initial location:<br />
<br />
<pre><br />
slicer.modules.markups.logic().AddFiducial(1.0, -2.0, 3.3)<br />
</pre><br />
<br />
===Add a button to module GUI to activate fiducial placement===<br />
<br />
This code snippet creates a toggle button, which activates fiducial placement when pressed (and deactivates when released).<br />
<br />
The [http://apidocs.slicer.org/master/classqSlicerMarkupsPlaceWidget.html qSlicerMarkupsPlaceWidget widget] can automatically activate placement of multiple points and can show buttons for deleting points, changing colors, lock, and hide points.<br />
<br />
<pre><br />
w=slicer.qSlicerMarkupsPlaceWidget()<br />
w.setMRMLScene(slicer.mrmlScene)<br />
markupsNodeID = slicer.modules.markups.logic().AddNewFiducialNode()<br />
w.setCurrentNode(slicer.mrmlScene.GetNodeByID(markupsNodeID))<br />
# Hide all buttons and only show place button<br />
w.buttonsVisible=False<br />
w.placeButton().show()<br />
w.show()<br />
</pre><br />
<br />
===Adding Fiducials via Mouse Clicks===<br />
<br />
You can also set the mouse mode into Markups fiducial placement by calling:<br />
<br />
<pre><br />
placeModePersistence = 1<br />
slicer.modules.markups.logic().StartPlaceMode(placeModePersistence)<br />
</pre><br />
<br />
A lower level way to do this is via the selection and interaction nodes:<br />
<br />
<pre><br />
selectionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSelectionNodeSingleton")<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
placeModePersistence = 1<br />
interactionNode.SetPlaceModePersistence(placeModePersistence)<br />
# mode 1 is Place, can also be accessed via slicer.vtkMRMLInteractionNode().Place<br />
interactionNode.SetCurrentInteractionMode(1)<br />
</pre><br />
<br />
To switch back to view transform once you're done placing fiducials:<br />
<br />
<pre><br />
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")<br />
interactionNode.SwitchToViewTransformMode()<br />
# also turn off place mode persistence if required<br />
interactionNode.SetPlaceModePersistence(0)<br />
</pre><br />
<br />
===Access to Fiducial Properties===<br />
<br />
Each vtkMRMLMarkupsFiducialNode has a vector of points in it which can be accessed from python:<br />
<br />
<pre><br />
fidNode = getNode("vtkMRMLMarkupsFiducialNode1")<br />
n = fidNode.AddFiducial(4.0, 5.5, -6.0)<br />
fidNode.SetNthFiducialLabel(n, "new label")<br />
# each markup is given a unique id which can be accessed from the superclass level<br />
id1 = fidNode.GetNthMarkupID(n)<br />
# manually set the position<br />
fidNode.SetNthFiducialPosition(n, 6.0, 7.0, 8.0)<br />
# set the label<br />
fidNode.SetNthFiducialLabel(n, "New label")<br />
# set the selected flag, only selected = 1 fiducials will be passed to CLIs<br />
fidNode.SetNthFiducialSelected(n, 1)<br />
# set the visibility flag<br />
fidNode.SetNthFiducialVisibility(n, 0) <br />
</pre><br />
<br />
You can loop over the fiducials in a list and get the coordinates:<br />
<br />
<pre><br />
fidList = slicer.util.getNode('F')<br />
numFids = fidList.GetNumberOfFiducials()<br />
for i in range(numFids):<br />
ras = [0,0,0]<br />
fidList.GetNthFiducialPosition(i,ras)<br />
# the world position is the RAS position with any transform matrices applied<br />
world = [0,0,0,0]<br />
fidList.GetNthFiducialWorldCoordinates(0,world)<br />
print(i,": RAS =",ras,", world =",world)<br />
</pre><br />
<br />
You can also look at the sample code in the [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/Endoscopy/Endoscopy.py#L287 Endoscopy module] to see how python is used to access fiducials from a scripted module.<br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(b)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Subject hierarchy plugin offering view context menu action===<br />
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 [https://github.com/Slicer/Slicer/blob/master/Modules/Loadable/Annotations/SubjectHierarchyPlugins/AnnotationsSubjectHierarchyPlugin.py#L96-L107 here]. This example makes it easier to create such a plugin.<br />
<br />
import os<br />
import vtk, qt, ctk, slicer<br />
from slicer.ScriptedLoadableModule import *<br />
<br />
from SubjectHierarchyPlugins import AbstractScriptedSubjectHierarchyPlugin<br />
<br />
class ViewContextMenuTest(ScriptedLoadableModule):<br />
def __init__(self, parent):<br />
ScriptedLoadableModule.__init__(self, parent)<br />
parent.title = "Test Subject Hierarchy Plugin Loader"<br />
<br />
# register subject hierarchy plugin once app is initialized<br />
def onStartupCompleted():<br />
import SubjectHierarchyPlugins<br />
from ViewContextMenuTest import ViewContextMenuTestSubjectHierarchyPlugin<br />
scriptedPlugin = slicer.qSlicerSubjectHierarchyScriptedPlugin(None)<br />
scriptedPlugin.setPythonSource(ViewContextMenuTestSubjectHierarchyPlugin.filePath)<br />
pluginHandlerSingleton = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginHandlerSingleton.registerPlugin(scriptedPlugin)<br />
slicer.app.connect("startupCompleted()", onStartupCompleted)<br />
<br />
class ViewContextMenuTestWidget(ScriptedLoadableModuleWidget):<br />
""" dummy module widget to allow reloading in developer mode """<br />
def setup(self):<br />
ScriptedLoadableModuleWidget.setup(self)<br />
<br />
class ViewContextMenuTestSubjectHierarchyPlugin(AbstractScriptedSubjectHierarchyPlugin):<br />
<br />
# Necessary static member to be able to set python source to scripted subject hierarchy plugin<br />
filePath = __file__<br />
<br />
def __init__(self, scriptedPlugin):<br />
# AbstractScriptedSubjectHierarchyPlugin.__init__(self, scriptedPlugin) # This call needs to be commented out!<br />
<br />
self.viewAction = qt.QAction(f"CUSTOM VIEW ...", scriptedPlugin)<br />
self.viewAction.objectName = 'CustomViewAction' # Necessary for the white list (see pluginLogic.setDisplayedViewMenuActionNames)<br />
self.viewAction.connect("triggered()", self.onViewAction)<br />
<br />
def onViewAction(self):<br />
print(f"VIEW ACTION")<br />
<br />
def viewContextMenuActions(self):<br />
return [self.viewAction]<br />
<br />
def showViewContextMenuActionsForItem(self, itemID, eventData=None):<br />
self.viewAction.visible = True<br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
import JupyterNotebooksLib as slicernb<br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
<br />
import matplotlib.pyplot as plt<br />
import numpy as np<br />
<br />
def f(t):<br />
s1 = np.cos(2*np.pi*t)<br />
e1 = np.exp(-t)<br />
return s1 * e1<br />
<br />
t1 = np.arange(0.0, 5.0, 0.1)<br />
t2 = np.arange(0.0, 5.0, 0.02)<br />
t3 = np.arange(0.0, 2.0, 0.01)<br />
<br />
<br />
fig, axs = plt.subplots(2, 1, constrained_layout=True)<br />
axs[0].plot(t1, f(t1), 'o', t2, f(t2), '-')<br />
axs[0].set_title('subplot 1')<br />
axs[0].set_xlabel('distance (m)')<br />
axs[0].set_ylabel('Damped oscillation')<br />
fig.suptitle('This is a somewhat long figure title', fontsize=16)<br />
<br />
axs[1].plot(t3, np.cos(2*np.pi*t3), '--')<br />
axs[1].set_xlabel('time (s)')<br />
axs[1].set_title('subplot 2')<br />
axs[1].set_ylabel('Undamped')<br />
<br />
slicernb.MatplotlibDisplay(matplotlib.pyplot)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre><br />
<br />
=== Install a module directly from a git repository ===<br />
This can be useful for sharing code in development without requiring a restart of Slicer.<br />
<br />
https://gist.github.com/pieper/a9c0ba57de3833c9f5aea68247bda597</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/DICOM&diff=63106Documentation/Nightly/Modules/DICOM2020-03-07T15:30:28Z<p>Pieper: /* Advanced options */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
{{Clear|right}}{{TOC right}}<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
:'''Author(s)/Contributor(s):''' Steve Pieper (Isomics Inc.), Michael Onken (Offis), Marco Nolden (DFKZ), Julien Finet (Kitware), Stephen Aylward (Kitware), Nicholas Herlambang (AZE), Alireza Mehrtash (BWH), Csaba Pinter (PerkLab, Queen's)<br><br />
: '''Acknowledgements:''' This work is part of the [http://www.na-mic.org/ 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, and by Quantitative Image Informatics for Cancer Research (QIICR) (U24 CA180918) <br><br />
: '''Contact:''' Steve Pieper, <email>pieper@bwh.harvard.edu</email><br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-logo-gallery<br />
|{{collaborator|logo|isomics}}|{{collaborator|longname|isomics}}<br />
|{{collaborator|logo|namic}}|{{collaborator|longname|namic}}<br />
|{{collaborator|logo|nac}}|{{collaborator|longname|nac}}<br />
|{{collaborator|logo|ctk}}|{{collaborator|longname|ctk}}<br />
|{{collaborator|logo|qiicr}}|{{collaborator|longname|qiicr}}<br />
|Image:DICOM-OFFIS-logo.png|DICOM-OFFIS<br />
}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
{{documentation/{{documentation/version}}/module-description}}<br />
<br />
DICOM is a widely used and sophisticated set of standards for digital radiology (see the [[#References|References]] section for more information). Slicer provides support for a subset of DICOM functionality, with the particular features driven by the needs of clinical research: '''loading''' and '''saving''' data sets from/to disk in DICOM format and '''sending''' and '''receiving''' data sets via DICOM networking.<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
<br />
===Data import and loading===<br />
<br />
Since DICOM files are often located in several folders, they can cross-reference each other, and can be often interpreted in different ways, importing and loading of DICOM files are performed as two separate steps:<br />
- Import: User selects a folder that contains DICOM files. Slicer reads header of all files in a folder (and all its subfolders) and stores essential information in the Slicer DICOM database. Optionally Slicer can also make a full copy of the files into the database, which is useful if files are stored on a removable media (CD, DVD, USB drive). All indexed data gets listed in the DICOM browser.<br />
- Loading: Items that user selected in the DICOM browser are loaded into the scene and displayed in viewers. In advanced mode, loading is performed in two steps: the application examines selected items and offers a list of loadable items that the user can choose from.<br />
<br />
====Data import====<br />
<br />
*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 RT structure set as segmentation''.<br />
*Select folders that contain DICOM files<br />
**Option A: Drag-and-drop the folder that contains DICOM files to the Slicer application window. Slicer displays a popup, asking what to do - click OK ("Load directory in DICOM database"). After import is completed, go to DICOM module.<br />
**Option B: Go to DICOM module. Click "Import" button in the top-left corner of the DICOM browser. Select folder that contains DICOM files.<br />
<br />
Optionally select the Copy option so that the files are copied into the database directory. Otherwise they will only be referenced in their original location. It is recommended to copy data if importing files from removable media (CD/DVD/USB drives) to be able to load the data set even after media is ejected.<br />
<br />
====Data loading====<br />
<br />
*Go to DICOM module. Click "Show DICOM browser" if the browser window is not visible (window title: "DICOM Browser"; it shows a list of patients, studies, and series).<br />
*Select items to load. If an item in the patient or study list is selected then by default all series that belong to that item will be loaded.<br />
*Click "Load" button to load selected items.<br />
*Go to Data module to see what has been loaded and show/hide them.<br />
<br />
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 loadables in the list in the box at the bottom, and click "Load".<br />
<br />
Some extensions make Slicer recognize more DICOM information object types. For example, SlicerRT extension makes it possible to load/export radiation therapy information objects (RT structure set, RT dose, RT image, RT plan). Make sure all relevant extensions are installed before attempting to load your data set.<br />
<br />
====Deleting imported data====<br />
<br />
By right clicking on a Patient, Study, or Series, you can delete the entry from the DICOM database. Note that to avoid accidental data loss, Slicer does not delete the corresponding image data files.<br />
<br />
===DICOM export===<br />
<br />
Data in the scene can be exported to the DICOM database:<br />
<br />
*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. For example, ''SlicerRT extension is needed for exporting segmentation as RT structure set''.<br />
*Prepare DICOM study to export in Data module (subject hierarchy tab). Make sure you have the nodes in the study that you want to export.<br />
**If you need to create a new patient, then right-click the empty space and choose Create new subject. Studies can be created under patients the same way.<br />
*Right-click on a data set to export it to DICOM format (export can also be initiated from DICOM browser).<br />
*Select the export modality in the bottom left of the export dialog.<br />
*Edit tags for exportables. The metadata from the select study will be automatically filled in to the Export dialog and you can select a Slicer volume to export.<br />
*Exported files are added to the DICOM database<br />
<br />
''Note that you should exercise extreme caution when working with these files in clinical situations, since non-standard or incorrect DICOM files can interfere with clinical operations.''<br />
<br />
You can also choose to encapsulate the current MRML scene (via an MRB file) inside a DICOM dataset, which will be treated as a DICOM secondary capture document. This secondary capture information stores all details of the scene but only 3D Slicer can interpret the data. This export feature has not been widely tested and should be considered experimental.<br />
<br />
===Data networking===<br />
<br />
DICOM is also a network communication standard. Slicer supports a DICOM Listener, DICOM Query/Retrieve interface, and a DICOM Send option. Note that in order to use these features, you must coordinate with the operators of the other DICOM nodes with which you wish to communicate. For example, you must work out agreement on such topics as network ports and application entity titles (AE Titles). Be aware that not all equipment supports all networking options, so configuration may be challenging and is often difficult to troubleshoot.<br />
<br />
====Connection Ports====<br />
<br />
Port 104 is the standard DICOM port. All ports below 1024 require root access on unix-like systems (Linux and Mac). So you can run Slicer with the sudo command to be able to open the port for the DICOM Listener. Or you can use a different port, like 11112. You need to configure that on both sides of the connection. You can only have one process at a time listening on a port so if you have a listener running the second one won't start up. Also if something adverse happens (a crash) the port may be kept open an you need to either kill the storescp helper process (or just reboot the computer) to free the port. Consult the [[documentation/{{documentation/version}}/SlicerApplication/ErrorLog|Error Log]] for diagnostic information.<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Screenshots}}<br />
<br />
{|<br />
|[[image:DICOM Screenshot-1 1204-11-17-10-36.png|thumb|380px|DICOM module in use]]<br />
|[[Image:Form_224.png|thumb|380px|DICOM Query dialog]]<br />
|[[Image:DICOM Horizontal View 2014-11-17-09-35.png|thumb|380px|Horizontal table view]]<br />
|}<br />
{|<br />
|[[Image:DICOM-3 2014-11-17-10-42.png|thumb|380px|DICOM Browser in Advanced Mode (Control plugins and access to load options)]]<br />
|[[Image:DICOM4 2014-11-17-09-38.png|thumb|380px|DICOM Meta Data Browser (DICOM header viewer)]]<br />
|[[Image:DICOM-5 2014-11-17-10-46.png|thumb|380px|More options (change local database directory and table display density)]]<br />
|}<br />
{|<br />
|[[Image:20141103_DICOM_Export_Dialog.png|thumb|380px|DICOM Export Dialog]]<br />
|[[Image:DICOM-Preferences.png|thumb|380px|DICOM Preferences]]<br />
|}<br />
<br />
<!-- Tutorials ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Tutorials}}<br />
<br />
*See [http://www.na-mic.org/Wiki/index.php/RSNA_2012#3D_Interactive_Visualization_of_DICOM_images the RSNA 2012 Training on Visualization] for description and sample data (Direct link to [http://www.na-mic.org/Wiki/images/6/66/3DVisualizationDICOM_RadiologyApplications_SoniaPujol_RSNA2012.pdf slides as pdf]).<br />
*[https://pieper.github.io/content/handson/#/DICOM This screen capture animation] shows how to import and load DICOM data (also includes inspection of the file contents, which is an optional step).<br />
<br />
<!-- Panels ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Panels}}<br />
<br />
==DICOM import==<br />
<br />
Referring to the "DICOM in use" image in the top left of this page, the most important controls for day-to-day use are highlighted with gray callouts. The are described here in the order you are likely to use them.<br />
<br />
*'''LocalDatabase''' (appears when ''>>'' button next to the menu bar is clicked): allows you to select a location on disk for Slicer's database of DICOM files. The application manages content of this folder (stores metadata and copy of imported DICOM files): do not manually copy any data into this folder.<br />
*'''Import''' all DICOM files in the selected folder (including subfolders) will be parsed and basic information from file headers will be stored in Slicer's DICOM database. If enabled, then Slicer will make a copy of the imported files into the database folder.<br />
*'''Patient list''' shows patients in the database. Studies available for the selected patient(s) are listed in study list. Multiple patients can be selected.<br />
*'''Study list''': shows studies for the currently selected patient(s). Multiple studies can be selected.<br />
*'''Series list''' shows list of series (images, structure sets, segmentations, registration objects, etc.) available for selected studies.<br />
*'''Search boxes''' Each patient/study/series can be filtered by typing in these fields.<br />
*'''Examine button''' Runs each of the DICOM Plugins on the currently selected series and offers the result in the Load Options table.<br />
*'''Plugin options''' (appears when ''Advanced'' checkbox is checked): you can choose which plugins will be allowed to examine the selected series for loading.<br />
*'''Loadables list''' displays all possible interpretations of the selected series by the selected plugins. The plugin that most likely interprets the series correctly, is selected by default. You can override the defaults if you want to load the data in a different way. There will not always be a one-to-one mapping of selected series to list of loadable items.<br />
*'''Load''' click this to load currently selected loadables into slicer.<br />
*'''Metadata''' click this button to see metadata stored in file headers of selected series.<br />
<br />
==DICOM export==<br />
<br />
===How to export data from the Slicer scene to DICOM files===<br />
<br />
*Open Data module, go to Subject Hierarchy tab (it is the tab shown by default)<br />
*Make sure the data nodes that you want to export are in a DICOM-compliant hierarchy: parent of the data node is a Study, parent of the study is a Subject. To create a hierarchy, right-click in an empty area of the tree, select "Create new subject", then right-click on the newly created subject and select "Create child study", then drag-and-drop your data nodes under this study.<br />
*Right-click on the data node to be exported and click "Export to DICOM..." to display DICOM export window (you can also show it by clicking "Export" button in the toolbar of DICOM browser module)<br />
*Choose Output folder: By default, files are written into the folder where Slicer DICOM database is located.<br />
*Click Export button: Export may take a few minutes. In case of error, the message is displayed in red on the left side of the dialog in the line of the "Save tags on export" checkbox.<br />
<br />
This workflow is also explained in a 2-minute [https://youtu.be/nzWf4xHy1BM video tutorial]<br />
<br />
===Advanced options===<br />
<br />
*DICOM export window can be also opened from the DICOM browser, by clicking "Export" button in the toolbar<br />
*Export mode:<br />
**Export series: export one or more selected series, to be viewed on a standard DICOM-compliant viewer<br />
**Export entire scene: save the entire Slicer scene into a DICOM secondary capture file; the content can be stored on a DICOM archival system but can only be edited in Slicer<br />
*Export type: Once the user selected a node, the DICOM plugins generate exportables for the series they can export. The list of the results appear in this section, grouped by plugin. The confidence number will be the average of the confidence numbers for the individual series for that plugin.<br />
*Editing DICOM tags:<br />
**DICOM tag editor consists of a list of tables. Tables for the common tags for the patient and study on the top, and the tags for the individual series below them (see image about tag editor below)<br />
**"Tags" in the displayed table are not written directly to DICOM tags, they are just used by the DICOM plugins to fill DICOM tags in the exported files. This allows much more flexibility and DICOM plugins can auto-populate some information and plugins can expose other export options in this list (e.g. compression, naming convention).<br />
**Save tags to scene: Checkbox to allow saving the tags back to the MRML nodes as attributes.<br />
*Import exported data: if checked, the exported files are added to Slicer's DICOM database.<br />
*The Edit->Preferences->DICOM page can be used to select options<br />
**Generic<br />
***Load referenced series will give you the option of easily loading, for example, the master volume of a segmentation when you open the segmentation. This can also be made to happen automatically.<br />
**Scalar Volume<br />
***You can choose what back-end library to use (currently GDCM, DCMTK, or GDCM with DCMTK fallback with the last option being the default. This is provided in case some data is unsupported by one library or the other.<br />
***Acquisition geometry regularization option supports the creation of a nonlinear transform that corrects for things like missing slices or gantry tilt in the acquisition. See [https://github.com/Slicer/Slicer/commit/b7650af3c27f34fc894bfdd587f2a4c02ba62a8b this commit message for more information]<br />
***Autoloading subseries by time is an option break up some 4D acquisitions into individual volume, but is optional since some volumes are also acquired in time unites and should not be split.<br />
<br />
=Slicer DICOM Database=<br />
<br />
To organize the data and avoid redundant calculations, Slicer keeps a DICOM Database of information about the DICOM data. You can have multiple databases on your computer at a time, and switch between them if, for example, they include data from different research projects. Each database is simply a directory on your local disk that has a few [http://sqlite.org/ SQLite] files and subdirectories to store image data. Don't manually modify the contents of these directories. DICOM data can enter the database either through manual import or via a DICOM network transfer. Modules may also populate the DICOM database with the results of computation.<br />
<br />
You can change location where the database is stored by opening the DICOM module, clicking the double arrow (>>) button on the right side of the menu bar, click the button next to "LocalDatabase" label, and select the new location (an empty folder).<br />
<br />
Note that the DICOM standard does not specify how files will be organized on disk, so if you have DICOM data from a CDROM or otherwise transferred from a scanner, you cannot in general tell anything about the contents from the file or directory names. However once the data is imported to the database, it will be organized according the the DICOM standard Patient/Study/Series hierarchy.<br />
<br />
=DICOM Loading/Saving and Plugins=<br />
<br />
A main function of the DICOM module is to map from ''acquisition'' data organization into ''volume'' representation. That is, DICOM files typically describe attributes of the image capture, like the sequence of locations of the table during CT acquisition, while Slicer operates on image volumes of regularly spaced pixels. If, for example, the speed of the table motion is not consistent during an acquisition (which can be the case for some contrast 'bolus chasing' scans, Slicer's DICOM module will warn the user that the acquisition geometry is not consistent and the user should use caution when interpreting analysis results such as measurements. <br />
<br />
From a developer perspective, the DICOM module exposes a plug-in architecture that allows acquisition-specific and modality-specific interpretation of DICOM data. From a user perspective this means that often Slicer will be able to suggest multiple ways of interpreting the data (such as reading DICOM files as a [[Documentation/{{documentation/version}}#Diffusion|Diffusion]] dataset or as a scalar volume. When it is computable by examining the files, the DICOM module will select the most likely interpretation option by default. As of this release, standard plugins include scalar volumes and diffusion volumes, while extensions are available for segmentation objects, RT data, and PET/CT data. More plugins are expected for future versions. It is a long-term objective to be able to represent most, if not all, of Slicer's data in the corresponding DICOM data objects as the standard evolves to support them.<br />
<br />
=Troubleshooting=<br />
<br />
See the [[Documentation/{{documentation/version}}/FAQ/DICOM|Slicer DICOM FAQ]] for troubleshooting tips.<br />
<br />
==How to obtain DICOM metadata==<br />
<br />
*Open DICOM browser<br />
*Select the data set that you want to load by clicking on a series (item in the listbox in the bottom)<br />
*Click Metadata button<br />
*Click Copy Metadata button<br />
*Paste the copied text to any text editor<br />
*'''Remove patient name, birthdate, ID, and all other patient identifiable information'''<br />
*Copy-paste remaining text to Slicer forum post or email<br />
<br />
<!-- Similar modules ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar modules}}<br />
<br />
*[[Documentation/{{documentation/version}}/SlicerApplication/LoadingData|Loading Data]] Can read scalar volume DICOM data, bypassing the database.<br />
*[[Documentation/{{documentation/version}}/Extensions/Reporting|Reporting Extension]] reads and writes DICOM Segmentation Objects (label maps).<br />
*[[Documentation/{{documentation/version}}/Extensions/Reporting|SlicerRT]] reads and write DICOM Radiation Therapy Objects and provides tools for processing them.<br />
*[[Documentation/{{documentation/version}}/Extensions/Reporting|LongitudinalPETCT]] reads all PET/CT studies for a selected patient and provides tools for tracking metabolic activity detected by PET tracers.<br />
<br />
<!-- References ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
See the [http://commontk.org CTK web site] for more information on the internals of the DICOM implementation. This tool uses the [http://dicom.offis.de DCMTK DICOM library].<br />
<br />
===Useful links===<br />
<br />
*The DICOM Homepage: http://dicom.nema.org/<br />
*DICOM on wikipedia: http://en.wikipedia.org/wiki/DICOM<br />
*Clean and simple DICOM tag browser: http://dicom.innolitics.com<br />
*A useful tag lookup site: http://dicomlookup.com/<br />
*A hyperlinked version of the standard: http://dabsoft.ch/dicom/<br />
*A handy book about DICOM: http://www.amazon.com/Digital-Imaging-Communications-Medicine-DICOM/dp/3642108490/ref=dp_ob_title_bk<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}<br />
<br />
[[Image:DICOM-architecture.png|thumb|580px|DICOM module architecture.]]<br />
<br />
The overall DICOM Implementation in 3D Slicer consists of two main bodies of code embedded within the application. [http://commontk.org CTK] code is responsible for the implementation of the DICOM database and networking layer. The CTK code is implemented C++ and follows the Qt style. The DICOM Module exposes this functionality to slicer users, and provides hooks through which other module can register DICOM Plugins to handle the conversion of specific DICOM data objects into the corresponding MRML representation. Once the data is in slicer, it can be operated on via the standard slicer mechanisms.<br />
<br />
====Customize table columns in DICOM browser====<br />
<br />
Columns in the browser can be renamed, reordered, shown/hidden, etc. An example snippet to customize the browser can be found in the [https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Customize_table_columns_in_DICOM_browser script repository]<br />
<br />
The changes made like this are written into the database (ColumnDisplayProperties table), so the column customizations can be done only once per database.<br />
<br />
====Customize DICOM browser table content====<br />
<br />
The way the raw DICOM tags are represented in the fields of the DICOM tables is determined by the displayed field generator rules. These rules are subclasses of the [https://github.com/commontk/CTK/blob/9c2af28e84da1abb986036317d75009d4c149923/master/Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorAbstractRule.h ctkDICOMDisplayedFieldGeneratorAbstractRule class], and need to be [https://github.com/commontk/CTK/blob/9c2af28e84da1abb986036317d75009d4c149923/master/Libs/DICOM/Core/ctkDICOMDisplayedFieldGenerator.h#L70 registered] to the displayed field generator in order to take part of the generation.<br />
<br />
The fields are [https://github.com/commontk/CTK/9c2af28e84da1abb986036317d75009d4c149923/blob/master/Libs/DICOM/Core/ctkDICOMDatabase.h#L207 updated] when 1) files are added to the database or 2) the database schema is updated (happens when opening an older database with a newer Slicer). In the [https://github.com/commontk/CTK/tree/9c2af28e84da1abb986036317d75009d4c149923 current] version only those files (i.e. instances) are processed for which the displayed fields have never been generated.<br />
<br />
When updating the displayed fields, every rule defines the fields it is responsible for using the cached DICOM tags in the database. Tags can be requested to be cached in the rules from the [https://github.com/commontk/CTK/blob/9c2af28e84da1abb986036317d75009d4c149923/Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorAbstractRule.h#L63 getRequiredDICOMTags] function. New field values are generated by the rules instance by instance. First, the getDisplayedFieldsForInstance function is called for each rule in which the custom values are generated from the raw tags, then the results are merged by calling mergeDisplayedFieldsForInstance for all the rules. Each field can be requested to be merged with "expect same value", which uses the only non-empty value and throws a warning if conflicting values are encountered, or with "concatenate", which simply concatenates the displayed field values together.<br />
<br />
The existing two rules can be used as examples: the [https://github.com/commontk/CTK/blob/9c2af28e84da1abb986036317d75009d4c149923/Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorDefaultRule.cpp default] and the [https://github.com/commontk/CTK/blob/9c2af28e84da1abb986036317d75009d4c149923/Libs/DICOM/Core/ctkDICOMDisplayedFieldGeneratorRadiotherapySeriesDescriptionRule.cpp RT] rules.<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=CitingSlicer&diff=63090CitingSlicer2020-02-29T18:13:44Z<p>Pieper: /* 3D Slicer as a Platform */</p>
<hr />
<div>===3D Slicer as a Platform===<br />
To acknowledge 3D Slicer as a platform, please cite the [http://www.slicer.org Slicer web site] and the following publications when publishing work that uses or incorporates 3D Slicer:<br />
<br />
* An overview of Slicer and its history<br />
** Kikinis R, Pieper SD, Vosburgh K (2014) [https://link.springer.com/chapter/10.1007/978-1-4614-7657-3_19 3D Slicer: a platform for subject-specific image analysis, visualization, and clinical support.] [https://link.springer.com/book/10.1007/978-1-4614-7657-3 Intraoperative Imaging Image-Guided Therapy], Ferenc A. Jolesz, Editor 3(19):277–289 ISBN: 978-1-4614-7656-6 (Print) 978-1-4614-7657-3 (Online)<br />
<br />
* About [https://projectweek.na-mic.org/ Project Week]<br />
** Kapur, Tina; Pieper, Steve; Fedorov, Andriy; Fillion-Robin, J-C; Halle, Michael; O'Donnell, Lauren; Lasso, Andras; Ungi, Tamas; Pinter, Csaba; Finet, Julien; Pujol, Sonia; Jagadeesan, Jayender; Tokuda, Junichi; Norton, Isaiah; Estepar, Raul San Jose; Gering, David; Aerts, Hugo J W L; Jakab, Marianna; Hata, Nobuhiko; Ibanez, Luiz; Blezek, Daniel; Miller, Jim; Aylward, Stephen; Grimson, W Eric L; Fichtinger, Gabor; Wells, William M; Lorensen, William E; Schroeder, Will; Kikinis, Ron; 2016. [https://www.sciencedirect.com/science/article/abs/pii/S1361841516301128 “Increasing the Impact of Medical Image Computing Using Community-Based Open-Access Hackathons: The NA-MIC and 3D Slicer Experience.”] Medical Image Analysis 33 (October): 176–80.<br />
<br />
* Slicer 4<br />
** Fedorov A., Beichel R., Kalpathy-Cramer J., Finet J., Fillion-Robin J-C., Pujol S., Bauer C., Jennings D., Fennessy F.M., Sonka M., Buatti J., Aylward S.R., Miller J.V., Pieper S., Kikinis R. [https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3466397/ 3D Slicer as an Image Computing Platform for the Quantitative Imaging Network.] Magn Reson Imaging. 2012 Nov;30(9):1323-41. PMID: 22770690. PMCID: PMC3466397.<br />
<br />
* Slicer 3<br />
**Pieper S, Lorensen B, Schroeder W, Kikinis R. [http://www.spl.harvard.edu/publications/item/view/68 The NA-MIC Kit: ITK, VTK, Pipelines, Grids and 3D Slicer as an Open Platform for the Medical Image Computing Community.] Proceedings of the 3rd IEEE International Symposium on Biomedical Imaging: From Nano to Macro 2006; 1:698-701.<br />
**Pieper S, Halle M, Kikinis R. [http://www.spl.harvard.edu/publications/item/view/91 3D SLICER.] Proceedings of the 1st IEEE International Symposium on Biomedical Imaging: From Nano to Macro 2004; 1:632-635.<br />
<br />
* Slicer 2<br />
** Gering D.T., Nabavi A., Kikinis R., Hata N., O'Donnell L., Grimson W.E.L., Jolesz F.A., Black P.M., Wells III W.M. [http://www.spl.harvard.edu/publications/item/view/156 An Integrated Visualization System for Surgical Planning and Guidance using Image Fusion and an Open MR.] J Magn Reson Imaging. 2001 Jun;13(6):967-75. PMID: 11382961. <br />
**Gering D.T., Nabavi A., Kikinis R., Grimson W.E.L., Hata N., Everett P., Jolesz F.A., Wells III W.M. [http://www.spl.harvard.edu/publications/item/view/816 An Integrated Visualization System for Surgical Planning and Guidance using Image Fusion and Interventional Imaging.] Int Conf Med Image Comput Comput Assist Interv. 1999 Sep;2:809-19.<br />
<br />
===Individual Modules===<br />
To acknowledge individual modules <br />
<br />
<br />
<gallery widths="280px" heights="280px" ><br />
File:Acknowledgement-tab.png|Each module has an acknowledgment tab in the top section. Information about contributors and funding source can be found there.<br />
File:Help-tab.png|Additional information (including information about the underlying publications) can be typically found on the manual pages accessible through the help tab in the top section<br />
</gallery></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=63003Documentation/Nightly/Developers/Build Instructions/Prerequisites2020-02-11T21:08:25Z<p>Pieper: /* Mac OSX 10.14 (Mojave) */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev libxkbcommon-x11-0<br />
<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554/21</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
<br />
* Build qt from homebrew<br />
<pre><br />
brew install qt<br />
ccmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 -DQt5_DIR=/usr/local//Cellar/qt/5.13.2/lib/cmake/Qt5 ~/slicer/latest/Slicer<br />
<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk make -j20<br />
</pre><br />
<br />
Note if you have build errors in dcmtk related to iconv symbols, you may need to uninstall the icu4c and dcmtk homebrew packages during the build process. See [https://github.com/QIICR/dcmqi/issues/395 here] and [https://github.com/Slicer/Slicer/commit/6523a62d776e64f970c554978a3c3a8f26022db5 here].<br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download download Qt 5.10.0 installer from [https://download.qt.io/archive/qt/5.10/5.10.0 here] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components (5.10.x version is not available in the [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe unified installer]). For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 or 2019 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=62859Documentation/Nightly/ScriptRepository2020-02-06T20:06:53Z<p>Pieper: /* Customize keyboard shortcuts */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules= <br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the[[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
<br />
*[https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
<br />
*[https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
*[https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
<br />
*[https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
*[https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Capture==<br />
<br />
*Capture the full Slicer screen and save it into a file<br />
<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
<br />
*Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
<br />
*Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
*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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
*Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
<br />
*How to open an .mrb file with Slicer at the command line?<br />
<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
<br />
*How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.<br />
<pre><br />
loadedVolumeNode = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd')<br />
</pre><br />
<br />
*Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
==Automatically load volumes that are copied into a folder==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
===How to load DICOM files into the scene from a folder===<br />
<br />
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.<br />
<br />
dicomDataDir = "c:/my/folder/with/dicom-files" # input folder with DICOM files<br />
loadedNodeIDs = [] # this list will contain the list of all loaded node IDs<br />
<br />
from DICOMLib import DICOMUtils<br />
with DICOMUtils.TemporaryDICOMDatabase() as db:<br />
DICOMUtils.importDicom(dicomDataDir, db)<br />
patientUIDs = db.patients()<br />
for patientUID in patientUIDs:<br />
loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))<br />
<br />
===How to 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:===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
<br />
===How to access DICOM tags nested in a sequence===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
===How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
===How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
===How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
===How can I convert DICOM to NRRD on the command line?===<br />
<br />
/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()"<br />
<br />
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.<br />
<br />
===Export a volume to DICOM file format===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
===Customize table columns in DICOM browser===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser<br />
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
</pre><br />
<br />
==Toolbar functions==<br />
<br />
*How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Manipulating objects in the slice viewer==<br />
<br />
*How to define/edit a circular region of interest in a slice viewer?<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSphere, 2)<br />
</pre><br />
<br />
==Measure angle between two slice planes==<br />
<br />
Measure angle between red and yellow slice nodes. Whenever any of the slice nodes are moved, the updated angle is printed on the console.<br />
<br />
<pre><br />
sliceNodeIds = ['vtkMRMLSliceNodeRed', 'vtkMRMLSliceNodeYellow']<br />
<br />
# Print angles between slice nodes<br />
def ShowAngle(unused1=None, unused2=None):<br />
sliceNormalVector = []<br />
for sliceNodeId in sliceNodeIds:<br />
sliceToRAS = slicer.mrmlScene.GetNodeByID(sliceNodeId).GetSliceToRAS()<br />
sliceNormalVector.append([sliceToRAS.GetElement(0,2), sliceToRAS.GetElement(1,2), sliceToRAS.GetElement(2,2)])<br />
angleRad = vtk.vtkMath.AngleBetweenVectors(sliceNormalVector[0], sliceNormalVector[1])<br />
angleDeg = vtk.vtkMath.DegreesFromRadians(angleRad)<br />
print('Angle between slice planes = {0:0.3f}'.format(angleDeg))<br />
<br />
# Observe slice node changes<br />
for sliceNodeId in sliceNodeIds:<br />
slicer.mrmlScene.GetNodeByID(sliceNodeId).AddObserver(vtk.vtkCommand.ModifiedEvent, ShowAngle)<br />
<br />
# Print current angle<br />
ShowAngle()<br />
</pre><br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from a normal vector and position==<br />
<br />
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.<br />
<br />
<pre><br />
def setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition, defaultViewUpDirection=None, backupViewRightDirection=None):<br />
"""<br />
Set slice pose from the provided plane normal and position. View up direction is determined automatically,<br />
to make view up point towards defaultViewUpDirection.<br />
:param defaultViewUpDirection Slice view will be spinned in-plane to match point approximately this up direction. Default: patient superior.<br />
:param backupViewRightDirection Slice view will be spinned in-plane to match point approximately this right direction<br />
if defaultViewUpDirection is too similar to sliceNormal. Default: patient left.<br />
"""<br />
# Fix up input directions<br />
if defaultViewUpDirection is None:<br />
defaultViewUpDirection = [0,0,1]<br />
if backupViewRightDirection is None:<br />
backupViewRightDirection = [-1,0,0]<br />
if sliceNormal[1]>=0:<br />
sliceNormalStandardized = sliceNormal<br />
else:<br />
sliceNormalStandardized = [-sliceNormal[0], -sliceNormal[1], -sliceNormal[2]]<br />
# Compute slice axes<br />
sliceNormalViewUpAngle = vtk.vtkMath.AngleBetweenVectors(sliceNormalStandardized, defaultViewUpDirection)<br />
angleTooSmallThresholdRad = 0.25 # about 15 degrees<br />
if sliceNormalViewUpAngle > angleTooSmallThresholdRad and sliceNormalViewUpAngle < vtk.vtkMath.Pi() - angleTooSmallThresholdRad:<br />
viewUpDirection = defaultViewUpDirection<br />
sliceAxisY = viewUpDirection<br />
sliceAxisX = [0, 0, 0]<br />
vtk.vtkMath.Cross(sliceAxisY, sliceNormalStandardized, sliceAxisX)<br />
else:<br />
sliceAxisX = backupViewRightDirection<br />
# Set slice axes<br />
sliceNode.SetSliceToRASByNTP(sliceNormalStandardized[0], sliceNormalStandardized[1], sliceNormalStandardized[2],<br />
sliceAxisX[0], sliceAxisX[1], sliceAxisX[2],<br />
slicePosition[0], slicePosition[1], slicePosition[2], 0)<br />
<br />
# Example usage:<br />
sliceNode = getNode('vtkMRMLSliceNodeRed')<br />
transformNode = getNode('Transform_3')<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToParent(transformMatrix)<br />
sliceNormal = [transformMatrix.GetElement(0,2), transformMatrix.GetElement(1,2), transformMatrix.GetElement(2,2)]<br />
slicePosition = [transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)]<br />
setSlicePoseFromSliceNormalAndPosition(sliceNode, sliceNormal, slicePosition)<br />
</pre><br />
<br />
==Switching to markup fiducial placement mode==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
==Change markup fiducial display properties==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
==Get a notification if a markup point position is modified==<br />
<br />
Event management of Slicer-4.11 version is still subject to change. The example below shows how point manipulation can be observed now.<br />
<br />
<pre><br />
def onMarkupChanged(caller,event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
if movingMarkupIndex >= 0:<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(movingMarkupIndex, pos)<br />
isPreview = markupsNode.GetNthControlPointPositionStatus(movingMarkupIndex) == slicer.vtkMRMLMarkupsNode.PositionPreview<br />
if isPreview:<br />
logging.info("Point {0} is previewed at {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Point {0} was moved {1} in slice view {2}".format(movingMarkupIndex, pos, sliceView))<br />
else:<br />
logging.info("Points modified: slice view = {0}".format(sliceView))<br />
<br />
def onMarkupStartInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint() <br />
logging.info("Start interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
def onMarkupEndInteraction(caller, event):<br />
markupsNode = caller<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
movingMarkupIndex = markupsNode.GetDisplayNode().GetActiveControlPoint()<br />
logging.info("End interaction: point ID = {0}, slice view = {1}".format(movingMarkupIndex, sliceView))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, onMarkupChanged)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointStartInteractionEvent, onMarkupStartInteraction)<br />
markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointEndInteractionEvent, onMarkupEndInteraction)<br />
</pre><br />
<br />
==Get a notification if a transform is modified==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
==Rotate a node around a specified point==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup fiducial node (centerOfRotationMarkupsNode) with a single point to specify center of rotation.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angles.<br />
*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.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move rotation sliders.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
centerOfRotationMarkupsNode = getNode('F')<br />
# This transform can be edited in Transforms module<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
rotationCenterPointCoord = [0.0, 0.0, 0.0]<br />
centerOfRotationMarkupsNode.GetNthControlPointPositionWorld(0, rotationCenterPointCoord)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Translate(rotationCenterPointCoord)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Translate(-rotationCenterPointCoord[0], -rotationCenterPointCoord[1], -rotationCenterPointCoord[2])<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
centerOfRotationMarkupsNodeObserver = centerOfRotationMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# centerOfRotationMarkupsNode.RemoveObserver(centerOfRotationMarkupsNodeObserver)<br />
<br />
</pre><br />
<br />
==Rotate a node around a specified line==<br />
<br />
Set up the scene:<br />
<br />
*Add a markup line node (rotationAxisMarkupsNode) with 2 points to specify rotation axis.<br />
*Add a rotation transform (rotationTransformNode) that will be edited in Transforms module to specify rotation angle.<br />
*Add a transform (finalTransformNode) and apply it (not harden) to those nodes (images, models, etc.) that you want to rotate around the line.<br />
<br />
Then run the script below, go to Transforms module, select rotationTransformNode, and move Edit / Rotation / IS slider.<br />
<br />
<pre><br />
# This markups fiducial node specifies the center of rotation<br />
rotationAxisMarkupsNode = getNode('L')<br />
# This transform can be edited in Transforms module (Edit / Rotation / IS slider)<br />
rotationTransformNode = getNode('LinearTransform_3')<br />
# This transform has to be applied to the image, model, etc.<br />
finalTransformNode = getNode('LinearTransform_4')<br />
<br />
def updateFinalTransform(unusedArg1=None, unusedArg2=None, unusedArg3=None):<br />
import numpy as np<br />
rotationAxisPoint1_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(0, rotationAxisPoint1_World)<br />
rotationAxisPoint2_World = np.zeros(3)<br />
rotationAxisMarkupsNode.GetNthControlPointPositionWorld(1, rotationAxisPoint2_World)<br />
axisDirectionZ_World = rotationAxisPoint2_World-rotationAxisPoint1_World<br />
axisDirectionZ_World = axisDirectionZ_World/np.linalg.norm(axisDirectionZ_World)<br />
# Get transformation between world coordinate system and rotation axis aligne coordinate system<br />
worldToRotationAxisTransform = vtk.vtkMatrix4x4()<br />
p=vtk.vtkPlaneSource()<br />
p.SetNormal(axisDirectionZ_World)<br />
axisOrigin = np.array(p.GetOrigin())<br />
axisDirectionX_World = np.array(p.GetPoint1())-axisOrigin<br />
axisDirectionY_World = np.array(p.GetPoint2())-axisOrigin<br />
rotationAxisToWorldTransform = np.row_stack((np.column_stack((axisDirectionX_World, axisDirectionY_World, axisDirectionZ_World, rotationAxisPoint1_World)), (0, 0, 0, 1)))<br />
rotationAxisToWorldTransformMatrix = slicer.util.vtkMatrixFromArray(rotationAxisToWorldTransform)<br />
worldToRotationAxisTransformMatrix = slicer.util.vtkMatrixFromArray(np.linalg.inv(rotationAxisToWorldTransform))<br />
# Compute transformation chain<br />
rotationMatrix = vtk.vtkMatrix4x4()<br />
rotationTransformNode.GetMatrixTransformToParent(rotationMatrix)<br />
finalTransform = vtk.vtkTransform()<br />
finalTransform.Concatenate(rotationAxisToWorldTransformMatrix)<br />
finalTransform.Concatenate(rotationMatrix)<br />
finalTransform.Concatenate(worldToRotationAxisTransformMatrix)<br />
finalTransformNode.SetAndObserveMatrixTransformToParent(finalTransform.GetMatrix())<br />
<br />
# Manual initial update<br />
updateFinalTransform()<br />
<br />
# Automatic update when point is moved or transform is modified<br />
rotationTransformNodeObserver = rotationTransformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, updateFinalTransform)<br />
rotationAxisMarkupsNodeObserver = rotationAxisMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, updateFinalTransform)<br />
<br />
# Execute these lines to stop automatic updates:<br />
# rotationTransformNode.RemoveObserver(rotationTransformNodeObserver)<br />
# rotationAxisMarkupsNode.RemoveObserver(rotationAxisMarkupsNodeObserver)<br />
</pre><br />
<br />
==Show a context menu when a markup point is clicked in a slice or 3D view==<br />
<br />
<pre><br />
<br />
# Example actions to perform<br />
<br />
def action1():<br />
print('Action1 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
def action2():<br />
print('Action2 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
def action3():<br />
print('Action3 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
# Clicked markup index is saved here to let the action<br />
# know which markup needs to be manipulated.<br />
slicer.clickedMarkupIndex = -1<br />
<br />
# Create a simple menu<br />
<br />
menu = qt.QMenu()<br />
a1 = qt.QAction("Test", slicer.util.mainWindow())<br />
a1.connect('triggered()', action1)<br />
menu.addAction(a1)<br />
a2 = qt.QAction("Action", slicer.util.mainWindow())<br />
a2.connect('triggered()', action1)<br />
menu.addAction(a2)<br />
a3 = qt.QAction("Here", slicer.util.mainWindow())<br />
a3.connect('triggered()', action1)<br />
menu.addAction(a3)<br />
<br />
# Add observer to a markup fiducial list<br />
<br />
@vtk.calldata_type(vtk.VTK_INT)<br />
def markupClickedCallback(caller, eventId, callData):<br />
slicer.clickedMarkupIndex = callData<br />
print('Open menu on markup '+str(slicer.clickedMarkupIndex))<br />
menu.move(qt.QCursor.pos())<br />
menu.show()<br />
<br />
markupsNode = getNode('F')<br />
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)<br />
<br />
</pre><br />
<br />
==Write markup positions to JSON file==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Write annotation ROI to JSON file==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
==Show a simple surface mesh as a model node==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
==Measure distance of points from surface==<br />
<br />
This example computes closest distance of points (markups fiducial 'F') from a surface (model node 'mymodel') and writes results into a table.<br />
<br />
<pre><br />
markupsNode = getNode('F')<br />
modelNode = getNode('mymodel')<br />
<br />
# Transform model polydata to world coordinate system<br />
if modelNode.GetParentTransformNode():<br />
transformModelToWorld = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(modelNode.GetParentTransformNode(), None, transformModelToWorld)<br />
polyTransformToWorld = vtk.vtkTransformPolyDataFilter()<br />
polyTransformToWorld.SetTransform(transformModelToWorld)<br />
polyTransformToWorld.SetInputData(modelNode.GetPolyData())<br />
polyTransformToWorld.Update()<br />
surface_World = polyTransformToWorld.GetOutput()<br />
else:<br />
surface_World = modelNode.GetPolyData()<br />
<br />
# Create arrays to store results<br />
indexCol = vtk.vtkIntArray()<br />
indexCol.SetName("Index")<br />
labelCol = vtk.vtkStringArray()<br />
labelCol.SetName("Name")<br />
distanceCol = vtk.vtkDoubleArray()<br />
distanceCol.SetName("Distance")<br />
<br />
distanceFilter = vtk.vtkImplicitPolyDataDistance()<br />
distanceFilter.SetInput(surface_World);<br />
nOfFiduciallPoints = markupsNode.GetNumberOfFiducials()<br />
for i in range(0, nOfFiduciallPoints):<br />
point_World = [0,0,0]<br />
markupsNode.GetNthControlPointPositionWorld(i, point_World)<br />
closestPointOnSurface_World = [0,0,0]<br />
closestPointDistance = distanceFilter.EvaluateFunctionAndGetClosestPoint(point_World, closestPointOnSurface_World)<br />
indexCol.InsertNextValue(i)<br />
labelCol.InsertNextValue(markupsNode.GetNthControlPointLabel(i))<br />
distanceCol.InsertNextValue(closestPointDistance)<br />
<br />
# Create a table from result arrays<br />
resultTableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode", "Points from surface distance")<br />
resultTableNode.AddColumn(indexCol)<br />
resultTableNode.AddColumn(labelCol)<br />
resultTableNode.AddColumn(distanceCol)<br />
<br />
# Show table in view layout<br />
slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpTableView)<br />
slicer.app.applicationLogic().GetSelectionNode().SetReferenceActiveTableID(resultTableNode.GetID())<br />
slicer.app.applicationLogic().PropagateTableSelection()<br />
</pre><br />
<br />
==Add a texture mapped plane to the scene as a model==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# use dummy image data here<br />
e = vtk.vtkImageEllipsoidSource()<br />
<br />
scene = slicer.mrmlScene<br />
<br />
# Create model node<br />
model = slicer.vtkMRMLModelNode()<br />
model.SetScene(scene)<br />
model.SetName(scene.GenerateUniqueName("2DImageModel"))<br />
<br />
planeSource = vtk.vtkPlaneSource()<br />
model.SetAndObservePolyData(planeSource.GetOutput())<br />
<br />
# Create display node<br />
modelDisplay = slicer.vtkMRMLModelDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
modelDisplay.SetScene(scene)<br />
scene.AddNode(modelDisplay)<br />
model.SetAndObserveDisplayNodeID(modelDisplay.GetID())<br />
<br />
# Add to scene<br />
modelDisplay.SetAndObserveTextureImageData(e.GetOutput())<br />
scene.AddNode(model) <br />
<br />
<br />
transform = slicer.vtkMRMLLinearTransformNode()<br />
scene.AddNode(transform) <br />
model.SetAndObserveTransformNodeID(transform.GetID())<br />
<br />
vTransform = vtk.vtkTransform()<br />
vTransform.Scale(50,50,50)<br />
vTransform.RotateX(30)<br />
transform.SetAndObserveMatrixTransformToParent(vTransform.GetMatrix())<br />
</pre><br />
<br />
==Get scalar values at surface of a model==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
==Select cells of a model using markups fiducial points==<br />
<br />
The following script selects cells of a model node that are closest to positions of markups fiducial points.<br />
<br />
<pre><br />
# Get input nodes<br />
modelNode = slicer.util.getNode('Segment_1') # select cells in this model<br />
markupsNode = slicer.util.getNode('F') # points will be selected at positions specified by this markups fiducial node<br />
<br />
# Create scalar array that will store selection state<br />
cellScalars = modelNode.GetMesh().GetCellData()<br />
selectionArray = cellScalars.GetArray('selection')<br />
if not selectionArray:<br />
selectionArray = vtk.vtkIntArray()<br />
selectionArray.SetName('selection')<br />
selectionArray.SetNumberOfValues(modelNode.GetMesh().GetNumberOfCells())<br />
selectionArray.Fill(0)<br />
cellScalars.AddArray(selectionArray)<br />
<br />
# Set up coloring by selection array<br />
modelNode.GetDisplayNode().SetActiveScalar("selection", vtk.vtkAssignAttribute.CELL_DATA)<br />
modelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeWarm1")<br />
modelNode.GetDisplayNode().SetScalarVisibility(True)<br />
<br />
# Initialize cell locator<br />
cell = vtk.vtkCellLocator()<br />
cell.SetDataSet(modelNode.GetMesh())<br />
cell.BuildLocator()<br />
<br />
def onPointsModified(observer=None, eventid=None):<br />
global markupsNode, selectionArray<br />
selectionArray.Fill(0) # set all cells to non-selected by default<br />
markupPoints = slicer.util.arrayFromMarkupsControlPoints(markupsNode)<br />
closestPoint = [0.0, 0.0, 0.0]<br />
cellObj = vtk.vtkGenericCell()<br />
cellId = vtk.mutable(0)<br />
subId = vtk.mutable(0)<br />
dist2 = vtk.mutable(0.0)<br />
for markupPoint in markupPoints:<br />
cell.FindClosestPoint(markupPoint, closestPoint, cellObj, cellId, subId, dist2)<br />
closestCell = cellId.get()<br />
if closestCell >=0:<br />
selectionArray.SetValue(closestCell, 100) # set selected cell's scalar value to non-zero<br />
selectionArray.Modified()<br />
<br />
# Initial update<br />
onPointsModified()<br />
# Automatic update each time when a markup point is modified<br />
markupsNodeObserverTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsFiducialNode.PointModifiedEvent, onPointsModified)<br />
<br />
# To stop updating selection, run this:<br />
# markupsNode.RemoveObserver(markupsNodeObserverTag)<br />
</pre><br />
<br />
==Export entire scene as VRML==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
==Export model to Blender, including color by scalar==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Export a tract (FiberBundle) to Blender, including color==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
==Iterate over tract (FiberBundle) streamline points==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
==Clone a node==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
==Clone a volume==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
==Create a new volume==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
imageData.GetPointData().GetScalars().Fill(fillVoxelValue)<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(imageData)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
==Get value of a volume at specific voxel coordinates==<br />
<br />
This example shows how to get voxel value of "volumeNode" at "ijk" volume voxel coordinates.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
ijk = [20,40,30] # volume voxel coordinates<br />
<br />
voxels = slicer.util.arrayFromVolume(volumeNode) # get voxels as a numpy array<br />
voxelValue = voxels[ijk[2], ijk[1], ijk[0]] # note that numpy array index order is kji (not ijk)<br />
</pre><br />
<br />
==Modify voxels in a volume==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
==Get volume voxel coordinates from markup fiducial RAS coordinates==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
==Get markup fiducial RAS coordinates from volume voxel coordinates==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
==Get the values of all voxels for a label value==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array(‘Volume’)<br />
label = array(‘Volume-label’)<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt(‘values.txt’, values)<br />
</pre><br />
<br />
==Access values in a DTI tensor volume==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
==Change window/level (brightness/contrast) or colormap of a volume==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
==Make mouse left-click and drag on the image adjust window/level==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
==Create custom color table==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
==Manipulate a Slice View==<br />
<br />
===Change slice offset===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
===Change slice orientation===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
===Show slice views in 3D window===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
===Reset field of view to show background volume maximized===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
===Rotate slice views to volume plane===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
===Iterate over current visible slice views, and set foreground and background images===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
compositeNode = layoutManager.sliceWidget(sliceViewName).sliceLogic().GetSliceCompositeNode()<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
==Show a volume in slice views==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
==Change opacity of foreground volume in slice views==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
==Fit slice plane to markup fiducials==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.zeros(3)<br />
p2 = np.zeros(3)<br />
p3 = np.zeros(3)<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0.0, 0.0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
==Save a series of images from a Slice View==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
==Rasterize a model and save it to a series of image files==<br />
<br />
This example shows how to generate a stack of image files from an STL file:<br />
<br />
inputModelFile = "/some/input/folder/SomeShape.stl"<br />
outputDir = "/some/output/folder"<br />
outputVolumeLabelValue = 100<br />
outputVolumeSpacingMm = [0.5, 0.5, 0.5]<br />
outputVolumeMarginMm = [10.0, 10.0, 10.0]<br />
<br />
# Read model<br />
inputModel = slicer.util.loadModel(inputModelFile)<br />
<br />
# Determine output volume geometry and create a corresponding reference volume<br />
import math<br />
import numpy as np<br />
bounds = np.zeros(6)<br />
inputModel.GetBounds(bounds)<br />
imageData = vtk.vtkImageData()<br />
imageSize = [ int((bounds[axis*2+1]-bounds[axis*2]+outputVolumeMarginMm[axis]*2.0)/outputVolumeSpacingMm[axis]) for axis in range(3) ]<br />
imageOrigin = [ bounds[axis*2]-outputVolumeMarginMm[axis] for axis in range(3) ]<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)<br />
imageData.GetPointData().GetScalars().Fill(0)<br />
referenceVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
referenceVolumeNode.SetOrigin(imageOrigin)<br />
referenceVolumeNode.SetSpacing(outputVolumeSpacingMm)<br />
referenceVolumeNode.SetAndObserveImageData(imageData)<br />
referenceVolumeNode.CreateDefaultDisplayNodes()<br />
<br />
# Convert model to labelmap<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
seg.SetReferenceImageGeometryParameterFromVolumeNode(referenceVolumeNode)<br />
slicer.modules.segmentations.logic().ImportModelToSegmentationNode(inputModel, seg)<br />
seg.CreateBinaryLabelmapRepresentation()<br />
outputLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(seg, outputLabelmapVolumeNode, referenceVolumeNode)<br />
outputLabelmapVolumeArray = (slicer.util.arrayFromVolume(outputLabelmapVolumeNode) * outputVolumeLabelValue).astype('int8')<br />
<br />
# Write labelmap volume to series of TIFF files<br />
pip_install("imageio")<br />
import imageio<br />
for i in range(len(outputLabelmapVolumeArray)):<br />
imageio.imwrite(f'{outputDir}/image_{i:03}.tiff', outputLabelmapVolumeArray[i])<br />
<br />
==Save the scene into a new directory==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save the scene into a single MRB file==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
==Save a node to file==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
==Center the 3D View on the Scene==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
==Display text in a 3D view or slice view==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
==Hide slice view annotations (DataProbe)==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
==Turning off interpolation==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
==Customize viewer layout==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
You can use this code snippet to add a button to the layout selector toolbar:<br />
<br />
<pre><br />
# Add button to layout selector toolbar for this custom layout<br />
viewToolBar = mainWindow().findChild('QToolBar', 'ViewToolBar')<br />
layoutMenu = viewToolBar.widgetForAction(viewToolBar.actions()[0]).menu()<br />
layoutSwitchActionParent = layoutMenu # use `layoutMenu` to add inside layout list, use `viewToolBar` to add next the standard layout list<br />
layoutSwitchAction = layoutSwitchActionParent.addAction("My view") # add inside layout list<br />
layoutSwitchAction.setData(layoutId)<br />
layoutSwitchAction.setIcon(qt.QIcon(':Icons/Go.png'))<br />
layoutSwitchAction.setToolTip('3D and slice view')<br />
layoutSwitchAction.connect('triggered()', lambda layoutId = customLayoutId: slicer.app.layoutManager().setLayout(layoutId))<br />
</pre><br />
<br />
==Customize keyboard shortcuts==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
Here's an example for cycling through Segment Editor effects (requested [https://discourse.slicer.org/t/is-there-a-keystroke-to-cycle-through-effects-in-segment-editor/10117/2 on the forum] for the [http://slicermorph.org SlicerMorph] project).<br />
<pre><br />
def cycleEffect(delta=1):<br />
try:<br />
orderedNames = list(slicer.modules.SegmentEditorWidget.editor.effectNameOrder())<br />
allNames = slicer.modules.SegmentEditorWidget.editor.availableEffectNames()<br />
for name in allNames:<br />
try:<br />
orderedNames.index(name)<br />
except ValueError:<br />
orderedNames.append(name)<br />
orderedNames.insert(0, None)<br />
activeEffect = slicer.modules.SegmentEditorWidget.editor.activeEffect()<br />
if activeEffect:<br />
activeName = slicer.modules.SegmentEditorWidget.editor.activeEffect().name<br />
else:<br />
activeName = None<br />
newIndex = (orderedNames.index(activeName) + delta) % len(orderedNames)<br />
slicer.modules.SegmentEditorWidget.editor.setActiveEffectByName(orderedNames[newIndex])<br />
except AttributeError:<br />
# module not active<br />
pass<br />
<br />
shortcuts = [<br />
('`', lambda: cycleEffect(-1)),<br />
('~', lambda: cycleEffect(1)),<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
==Disable certain user interactions in slice views==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
==Change default slice view orientation==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
==Set all slice views linked by default==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
==Set crosshair jump mode to centered by default==<br />
<br />
You can change default slice jump mode (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
crosshair=slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLCrosshairNode")<br />
crosshair.SetCrosshairBehavior(crosshair.CenteredJumpSlice)<br />
</pre><br />
<br />
==Set up custom units in slice view ruler==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManagerCollection = vtk.vtkCollection()<br />
sliceView.getDisplayableManagers(displayableManagerCollection)<br />
for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):<br />
displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)<br />
if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):<br />
continue<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
==Show a slice view outside the view layout==<br />
<br />
<pre><br />
layoutName = "TestSlice"<br />
layoutLabel = "TS"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Show a 3D view outside the view layout==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
==Get displayable manager of a certain type for a certain view==<br />
<br />
<pre><br />
modelDisplayableManager = None<br />
threeDViewWidget = slicer.app.layoutManager().threeDWidget(0)<br />
managers = vtk.vtkCollection()<br />
threeDViewWidget.getDisplayableManagers(managers)<br />
for i in range(managers.GetNumberOfItems()):<br />
obj = managers.GetItemAsObject(i)<br />
if obj.IsA('vtkMRMLModelDisplayableManager'):<br />
modelDisplayableManager = obj<br />
break<br />
if modelDisplayableManager is None:<br />
logging.error('Failed to find the model displayable manager')<br />
return<br />
</pre><br />
<br />
==Running an ITK filter in Python using SimpleITK==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
<br />
*See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
*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<br />
<br />
==Get current mouse coordinates in a slice view==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
==Get DataProbe text==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
==Get reformatted image from a slice viewer as numpy array==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print voxels.shape<br />
</pre><br />
<br />
==Combine multiple volumes into one==<br />
<br />
This example combines two volumes into a new one by subtracting one from the other.<br />
<br />
<pre><br />
import SampleData<br />
[input1Volume, input2Volume] = SampleData.SampleDataLogic().downloadDentalSurgery()<br />
<br />
import slicer.util<br />
a = slicer.util.arrayFromVolume(input1Volume)<br />
b = slicer.util.arrayFromVolume(input2Volume)<br />
<br />
# 'a' and 'b' are numpy arrays,<br />
# they can be combined using any numpy array operations<br />
# to produce the result array 'c'<br />
c = b-a<br />
<br />
volumeNode = slicer.modules.volumes.logic().CloneVolume(input1Volume, "Difference")<br />
slicer.util.updateVolumeFromArray(volumeNode, c)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==Thick slab reconstruction and maximum/minimum intensity volume projections==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
==Change default file type for nodes (that have never been saved yet)==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
==Change file type for saving for all volumes (with already existing storage nodes)==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
==Sequences==<br />
<br />
===Concatenate all sequences in the scene into a new sequence===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
==Segmentations==<br />
<br />
===Create a segmentation from a labelmap volume and display in 3D===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
===Export labelmap node from segmentation node===<br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export by pressing Ctrl+Shift+s key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
===Export model nodes from segmentation node===<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
exportedModelsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelHierarchyNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModelHierarchy(seg, exportedModelsNode)<br />
</pre><br />
<br />
===Show a segmentation in 3D===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
===Get a representation of a segment===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
===Convert all segments using default path and conversion parameters===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
===Convert all segments using custom path or conversion parameters===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
===Re-convert using a modified conversion parameter===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
===Get centroid of a segment in world (RAS) coordinates===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
===How to run segment editor effects from a script===<br />
<br />
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.<br />
<br />
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):<br />
<br />
*[https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
*[https://gist.github.com/lassoan/ef30bc27a22a648ead7f82243f5cc7d5 AI-assisted brain tumor segmentation]<br />
*[https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
*[https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
*[https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
<br />
This example shows how to perform operations on segmentations using VTK filters:<br />
<br />
*[https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
==Quantifying segments==<br />
<br />
===Get size, position, and orientation of each segment===<br />
<br />
This example computes oriented bounding box for each segment and displays them using annotation ROI.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
<br />
# Compute bounding boxes<br />
import SegmentStatistics<br />
segStatLogic = SegmentStatistics.SegmentStatisticsLogic()<br />
segStatLogic.getParameterNode().SetParameter("Segmentation", segmentationNode.GetID())<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_origin_ras.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_diameter_mm.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_x.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_y.enabled",str(True))<br />
segStatLogic.getParameterNode().SetParameter("LabelmapSegmentStatisticsPlugin.obb_direction_ras_z.enabled",str(True))<br />
segStatLogic.computeStatistics()<br />
stats = segStatLogic.getStatistics()<br />
<br />
# Draw ROI for each oriented bounding box<br />
import numpy as np<br />
for segmentId in stats['SegmentIDs']:<br />
# Get bounding box<br />
obb_origin_ras = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_origin_ras"])<br />
obb_diameter_mm = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_diameter_mm"])<br />
obb_direction_ras_x = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_x"])<br />
obb_direction_ras_y = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_y"])<br />
obb_direction_ras_z = np.array(stats[segmentId,"LabelmapSegmentStatisticsPlugin.obb_direction_ras_z"])<br />
# Create ROI<br />
segment = segmentationNode.GetSegmentation().GetSegment(segmentId)<br />
roi=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLAnnotationROINode")<br />
roi.SetName(segment.GetName()+' bounding box')<br />
roi.SetXYZ(0.0, 0.0, 0.0)<br />
roi.SetRadiusXYZ(*(0.5*obb_diameter_mm))<br />
# Position and orient ROI using a transform<br />
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)<br />
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)))<br />
boundingBoxToRasTransformMatrix = slicer.util.vtkMatrixFromArray(boundingBoxToRasTransform)<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLTransformNode')<br />
transformNode.SetAndObserveMatrixTransformToParent(boundingBoxToRasTransformMatrix)<br />
roi.SetAndObserveTransformNodeID(transformNode.GetID())<br />
</pre><br />
<br />
==Accessing views, renderers, and cameras==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
==Hide view controller bars==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
==Customize widgets in view controller bars==<br />
<br />
<pre><br />
sliceController = slicer.app.layoutManager().sliceWidget("Red").sliceController()<br />
<br />
# hide what is not needed<br />
sliceController.pinButton().hide()<br />
#sliceController.viewLabel().hide()<br />
sliceController.fitToWindowToolButton().hide()<br />
sliceController.sliceOffsetSlider().hide()<br />
<br />
# add custom widgets<br />
myButton = qt.QPushButton("My custom button")<br />
sliceController.barLayout().addWidget(b)<br />
</pre><br />
<br />
==Change 3D view background color==<br />
<br />
<pre><br />
viewNode = slicer.app.layoutManager().threeDWidget(0).mrmlViewNode()<br />
viewNode.SetBackgroundColor(1,0,0)<br />
viewNode.SetBackgroundColor2(1,0,0)<br />
<br />
</pre><br />
<br />
==Hide Slicer logo from main window (to increase space)==<br />
<br />
<pre><br />
slicer.util.findChild(slicer.util.mainWindow(), 'LogoLabel').visible = False<br />
</pre><br />
<br />
==Subject hierarchy== <br />
====Get the pseudo-singleton subject hierarchy node====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.mrmlScene.GetSubjectHierarchyNode()<br />
<br />
====Create subject hierarchy item====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
====Get subject hierarchy item====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
====Traverse children of a subject hierarchy item====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
====Manipulate subject hierarchy item====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data node associated to an item<br />
shNode.SetItemDisplayVisibility(itemID, 1)<br />
# Set visibility of whole branch<br />
# Note: Folder-type items (fodler, subject, study, etc.) create their own display nodes when show/hiding from UI.<br />
# The displayable managers use SH information to determine visibility of an item, so no need to show/hide individual leaf nodes any more.<br />
# Once the folder display node is created, it can be shown hidden simply using shNode.SetItemDisplayVisibility<br />
# From python, this is how to trigger creating a folder display node<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler().instance()<br />
folderPlugin = pluginHandler.pluginByName('Folder')<br />
folderPlugin.setDisplayVisibility(folderItemID, 1)<br />
<br />
====Filter items in TreeView or ComboBox====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
===Listen to subject hierarchy item events===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
===Use whitelist to customize view menu===<br />
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.<br />
pluginHandler = slicer.qSlicerSubjectHierarchyPluginHandler.instance()<br />
pluginLogic = pluginHandler.pluginLogic()<br />
menuActions = pluginLogic.availableViewMenuActionNames()<br />
# Returns ('RenamePointAction', 'DeletePointAction', 'ToggleSelectPointAction', 'EditPropertiesAction')<br />
newActions = ['RenamePointAction']<br />
pluginLogic.setDisplayedViewMenuActionNames(newActions)<br />
<br />
==Plotting==<br />
<br />
===Slicer plots displayed in view layout===<br />
<br />
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<br />
<br />
====Using <code>slicer.util.plot</code> utility function====<br />
<br />
<pre><br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
chartNode = slicer.util.plot(histogram, xColumnIndex = 1)<br />
chartNode.SetYAxisRangeAuto(False)<br />
chartNode.SetYAxisRange(0, 4e5)<br />
</pre><br />
<br />
[[Image:SlicerPlot.png]]<br />
<br />
====Using MRML classes only====<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
</pre><br />
<br />
===Using matplotlib===<br />
<br />
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 [http://matplotlib.sourceforge.net/ MatPlotLib] pages.<br />
<br />
====Non-interactive plot====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
<br />
# Static image view<br />
pm = qt.QPixmap("MatplotlibExample.png")<br />
imageWidget = qt.QLabel()<br />
imageWidget.setPixmap(pm)<br />
imageWidget.setScaledContents(True)<br />
imageWidget.show()<br />
</pre><br />
<br />
[[Image:MatplotlibExample.png]]<br />
<br />
====Plot in Slicer Jupyter notebook====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib')<br />
import matplotlib<br />
<br />
matplotlib.use('Agg')<br />
from pylab import *<br />
<br />
t1 = arange(0.0, 5.0, 0.1)<br />
t2 = arange(0.0, 5.0, 0.02)<br />
t3 = arange(0.0, 2.0, 0.01) <br />
<br />
subplot(211)<br />
plot(t1, cos(2*pi*t1)*exp(-t1), 'bo', t2, cos(2*pi*t2)*exp(-t2), 'k')<br />
grid(True)<br />
title('A tale of 2 subplots')<br />
ylabel('Damped')<br />
<br />
subplot(212)<br />
plot(t3, cos(2*pi*t3), 'r--')<br />
grid(True)<br />
xlabel('time (s)')<br />
ylabel('Undamped')<br />
savefig('MatplotlibExample.png')<br />
display(filename='MatplotlibExample.png', type="image/png", binary=True)<br />
</pre><br />
<br />
[[Image:JupyterNotebookMatplotlibExample.png]]<br />
<br />
====Interactive plot using wxWidgets GUI toolkit====<br />
<br />
<pre><br />
try:<br />
import matplotlib<br />
import wx<br />
except ModuleNotFoundError:<br />
pip_install('matplotlib wxPython')<br />
import matplotlib<br />
<br />
# Get a volume from SampleData and compute its histogram<br />
import SampleData<br />
import numpy as np<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Set matplotlib to use WXAgg backend<br />
import matplotlib<br />
matplotlib.use('WXAgg')<br />
<br />
# Show an interactive plot<br />
import matplotlib.pyplot as plt<br />
fig, ax = plt.subplots()<br />
ax.plot(histogram[1][1:], histogram[0].astype(float))<br />
ax.grid(True)<br />
ax.set_ylim((0, 4e5))<br />
plt.show(block=False)<br />
</pre><br />
<br />
[[Image:InteractiveMatplotlibExample.png]]<br />
<br />
==Execute external applications==<br />
<br />
How to run external applications from Slicer.<br />
<br />
===Run process in default environment===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.<br />
<br />
==Manage extensions==<br />
<br />
===Download and install extension===<br />
<br />
<pre><br />
extensionName = 'SlicerIGT'<br />
em = slicer.app.extensionsManagerModel()<br />
if not em.isExtensionInstalled(extensionName):<br />
extensionMetaData = em.retrieveExtensionMetadataByName(extensionName)<br />
url = em.serverUrl().toString()+'/download/item/'+extensionMetaData['item_id']<br />
extensionPackageFilename = slicer.app.temporaryPath+'/'+extensionMetaData['md5']<br />
slicer.util.downloadFile(url, extensionPackageFilename)<br />
em.installExtension(extensionPackageFilename)<br />
slicer.util.restart()<br />
</pre></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/LineMarkerRegistration&diff=62780Documentation/Nightly/Modules/LineMarkerRegistration2019-12-20T14:27:14Z<p>Pieper: </p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
This work is part of the National Center for Image Guided Therapy (NCIGT) (P41 EB015898), National Alliance for Medical Image Computing (NA-MIC) (U54 EB005149), and other research grants (R01 CA111288, R01 CA138586) funded by National Institutes of Health and CIMIT. <br><br />
Author: Junichi Tokuda, BWH<br><br />
Contact: Junichi Tokuda, <email>tokuda@bwh.harvard.edu</email><br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-logo-gallery<br />
|{{collaborator|logo|spl}}|{{collaborator|longname|spl}} <br />
}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
The Line Marker Registration CLI automatically detects a fiducial marker consisting of multiple lines on a 3D image and register the marker to the image coordinate system. The users can configure the CLI to use their own fiducial marker design by creating a marker configuration file formatted in CSV.<br />
<!--<br />
TODO: the description should be extracted from the corresponding XML description. This could be done automatically using the following wiki template:<pre>{{documentation/{{documentation/version}}/module-description}}<br />
{{documentation/{{documentation/version}}/module-description}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
Example applications of this modules:<br />
<br />
*CT-guided percutaneous intervention (biopsy/RF ablation/cryo etc): Registration of a needle holder to the CT image coordinate system<br />
*MRI-compatible manipulator for needle placement: Registration of the manipulator to the MRI image coordinate system<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Tutorials}}<br />
<br />
==Installation==<br />
Currently this CLI is not available in Slicer Extension. Please obtain the source code from [https://github.com/SNRLab/LineMarkerRegistration Git repository] and build it by following [[Documentation/Nightly/Developers/Build_Module| the instruction]].<br />
<br />
==Example Data==<br />
Example fiducial marker data (MR-visible z-frame) must be downloaded.<br />
<br />
[[File:Documentation_Nightly_Modules_LineMarkerRegistration_Example.zip]].<br />
<br />
The file is zip-archived. It includes:<br />
<br />
*zframe-config.csv (Example fiducial marker configuration file)<br />
*zframe-image.nrrd (MR image of example fiducial marker)<br />
*zframe-model.vtk (VTK model of fiducial marker)<br />
<br />
==Step by step instruction==<br />
First, load the MR image and the VTK model to the Scene.<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_LoadData.png|400px]]<br />
<br />
You will see the model and the image in the 2D/3D viewer.<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_VisualizeData.png|400px]]<br />
<br />
From the Modules menu, go to "IGT" &gt; "LineMarkerRegistration. The panel of the CLI looks:<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_GUI.png|400px]]<br />
<br />
Select input volume, marker configuration file, output volume (where the result of marker segmentation is stored) and marker transform<br />
from IO menu as follows:<br />
<br />
*Input Volume: "zframe-image"<br />
*Marker Config File: Click the button next to the text input, and choose "zframe-config.csv" which is in the Examples folder in the source.<br />
*Output Volume: Choose "Create new Volume" to create a new scalar volume node.<br />
*Marker Volume: Choose "Create new LinearTransform" to create a new linear transform node. (The name will be "Marker transform".)<br />
<br />
Leave other parameters as they are (the default values are adjusted for the example data). Now you can click the "Apply" button to run the CLI. It usually takes 2-10 seconds to complete. If it is successful, you could see the segmented line markers on the 2D viewers as below:<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_Segmented.png|400px]]<br />
<br />
To confirm that the line marker is registered to the image, you need to apply the marker transform to the Z-frame model. To do this, open the "Data" module and drag the "zframe-model" node to the "Marker transform".<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_DataModule.png|400px]]<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_ApplyTransform.png|400px]]<br />
<br />
By clicking the "eye" icon on the red slice, you could show the axial image and the model in the 3D viewer at the same time. Move the slice with the slider in the 2D viewer and check if the model is well overlapped with the segmented marker. <br />
<br />
[[Image:LineMarkerRegistration_Screenshot_Confirmation.png|400px]]<br />
<br />
<br />
<br />
<!-- ---------------------------- -->{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
N/A<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}<br />
<br />
https://github.com/SNRLab/LineMarkerRegistration<br />
<br />
<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/LineMarkerRegistration&diff=62779Documentation/Nightly/Modules/LineMarkerRegistration2019-12-20T14:25:49Z<p>Pieper: </p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
This work is part of the National Center for Image Guided Therapy (NCIGT) (P41 EB015898), National Alliance for Medical Image Computing (NA-MIC) (U54 EB005149), and other research grants (R01 CA111288, R01 CA138586) funded by National Institutes of Health and CIMIT. <br><br />
Author: Junichi Tokuda, BWH<br><br />
Contact: Junichi Tokuda, <email>tokuda@bwh.harvard.edu</email><br><br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-logo-gallery<br />
|{{collaborator|logo|spl}}|{{collaborator|longname|spl}} <br />
}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
The Line Marker Registration CLI automatically detects a fiducial marker consisting of multiple lines on a 3D image and register the marker to the image coordinate system. The users can configure the CLI to use their own fiducial marker design by creating a marker configuration file formatted in CSV.<br />
<!--<br />
TODO: the description should be extracted from the corresponding XML description. This could be done automatically using the following wiki template:<pre>{{documentation/{{documentation/version}}/module-description}}<br />
{{documentation/{{documentation/version}}/module-description}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
Example applications of this modules:<br />
<br />
*CT-guided percutaneous intervention (biopsy/RF ablation/cryo etc): Registration of a needle holder to the CT image coordinate system<br />
*MRI-compatible manipulator for needle placement: Registration of the manipulator to the MRI image coordinate system<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Tutorials}}<br />
<br />
==Installation==<br />
Currently this CLI is not available in Slicer Extension. Please obtain the source code from [https://github.com/SNRLab/LineMarkerRegistration Git repository] and build it by following [[Documentation/Nightly/Developers/Build_Module| the instruction]].<br />
<br />
==Example Data==<br />
Example fiducial marker data (MR-visible z-frame) must be downloaded.<br />
<br />
[[File:Documentation_Nightly_Modules_LineMarkerRegistration_Example.zip]].<br />
<br />
The file is zip-archived. It includes:<br />
<br />
*zframe-config.csv (Example fiducial marker configuration file)<br />
*zframe-image.nrrd (MR image of example fiducial marker)<br />
*zframe-model.vtk (VTK model of fiducial marker)<br />
<br />
==Step by step instruction==<br />
First, load the MR image and the VTK model to the Scene.<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_LoadData.png|400px]]<br />
<br />
You will see the model and the image in the 2D/3D viewer.<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_VisualizeData.png|400px]]<br />
<br />
From the Modules menu, go to "IGT" &gt; "LineMarkerRegistration. The panel of the CLI looks:<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_GUI.png|400px]]<br />
<br />
Select input volume, marker configuration file, output volume (where the result of marker segmentation is stored) and marker transform<br />
from IO menu as follows:<br />
<br />
*Input Volume: "zframe-image"<br />
*Marker Config File: Click the button next to the text input, and choose "zframe-config.csv" which is in the Examples folder in the source.<br />
*Output Volume: Choose "Create new Volume" to create a new scalar volume node.<br />
*Marker Volume: Choose "Create new LinearTransform" to create a new linear transform node. (The name will be "Marker transform".)<br />
<br />
Leave other parameters as they are (the default values are adjusted for the example data). Now you can click the "Apply" button to run the CLI. It usually takes 2-10 seconds to complete. If it is successful, you could see the segmented line markers on the 2D viewers as below:<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_Segmented.png|400px]]<br />
<br />
To confirm that the line marker is registered to the image, you need to apply the marker transform to the Z-frame model. To do this, open the "Data" module and drag the "zframe-model" node to the "Marker transform".<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_DataModule.png|400px]]<br />
<br />
[[Image:LineMarkerRegistration_Screenshot_ApplyTransform.png|400px]]<br />
<br />
By clicking the "eye" icon on the red slice, you could show the axial image and the model in the 3D viewer at the same time. Move the slice with the slider in the 2D viewer and check if the model is well overlapped with the segmented marker. <br />
<br />
[[Image:LineMarkerRegistration_Screenshot_Confirmation.png|400px]]<br />
<br />
<br />
<br />
<!-- ---------------------------- -->{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
N/A<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}https://github.com/SNRLab/LineMarkerRegistration<nowiki/>{{documentation/{{documentation/version}}/module-developerinfo}}<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Main_Page/SlicerCommunity&diff=62769Main Page/SlicerCommunity2019-12-09T16:13:33Z<p>Pieper: /* 3D Slicer Enabled Research */ Add contact info.</p>
<hr />
<div><includeonly>----<br />
Go to <big>[[Main_Page/SlicerCommunity/2019|2019]] :: [[Main_Page/SlicerCommunity/2018|2018]] :: [[Main_Page/SlicerCommunity/2017|2017]] :: [[Main_Page/SlicerCommunity/2016|2016]] :: [[Main_Page/SlicerCommunity/2015|2015]] :: [[Main_Page/SlicerCommunity/2011-2014|2014-2011]] :: [[Main_Page/SlicerCommunity/2005-2010|2010-2005]]</big><br />
----</includeonly><br />
<noinclude><br />
=3D Slicer Enabled Research=<br />
[[Documentation/{{documentation/currentversion}}/Slicer|3D Slicer]] is a free open source software package distributed under a BSD style [[License|license]] for analysis, integration, and visualization of medical images. 3D Slicer allows even those with limited image processing experience to effectively explore and quantify their imaging data for hypothesis-driven research. <br />
</noinclude><br />
<br />
The community that relies on 3D Slicer is large and active: (numbers below updated on Nov 18, 2019)<br />
<br />
*[https://download.slicer.org/download-stats/ 575,000+ downloads] in the last 8 years (145,000+ in last year)<br />
*[https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= over 9,300+ literature search results on Google Scholar]<br />
*[https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 5400+ cancer research citations on Google Scholar] ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28prostate%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1300+ prostate]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28brain%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 3,000+ brain]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28lung%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1,800+ lung]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28breast%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1,390+ breast])<br />
*[https://na-mic.github.io/ProjectWeek/ 32+ events in open source hackathon series] continuously running since 2005 with 2537 total participants<br />
*[https://discourse.slicer.org/ Slicer Forum] with 2,929 subscribers has approximately 250 posts every week<br />
<br />
<!--<br />
The research of Slicer community is represented in the [http://www.slicer.org/publications/pages/display/?collection=11 publication database].<br />
--><br />
<br />
The following is a sample of the research performed<includeonly> in {{#titleparts: {{PAGENAME}} | 2 | 3 }}</includeonly> using 3D Slicer outside of the group that develops it. (Research performed by groups that are also actively developing 3D Slicer is represented in the [http://www.spl.harvard.edu/publications/pages/display/?collection=11&collection=11 publication database]).<br />
<noinclude><br />
<br />
*[[Main_Page/SlicerCommunity/2019|2019]]<br />
*[[Main_Page/SlicerCommunity/2018|2018]]<br />
*[[Main_Page/SlicerCommunity/2017|2017]]<br />
*[[Main_Page/SlicerCommunity/2016|2016]]<br />
*[[Main_Page/SlicerCommunity/2015|2015]]<br />
*[[Main_Page/SlicerCommunity/2011-2014|2014-2011]]<br />
*[[Main_Page/SlicerCommunity/2005-2010|2010-2005]]<br />
<br />
We invite you to provide information using our [https://discourse.slicer.org/ discussion forum] on how you are using 3D Slicer to produce peer-reviewed research. Information about the scientific impact of this tool is helpful in raising funding for the continued support.<br />
</noinclude><br />
<br />
We monitor PubMed and related databases to update these lists, but if you know of other research related to the Slicer community that should be included here please email: marianna (at) bwh.harvard.edu.</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=62706Documentation/Nightly/Developers/Build Instructions/Prerequisites2019-12-02T10:46:45Z<p>Pieper: /* Mac OSX 10.14 (Mojave) */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev libxkbcommon-x11-0<br />
<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554/21</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
<br />
* Build qt from homebrew<br />
<pre><br />
brew install qt<br />
ccmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 -DQt5_DIR=/usr/local//Cellar/qt/5.13.2/lib/cmake/Qt5 ~/slicer/latest/Slicer<br />
<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk make -j20<br />
</pre><br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download download Qt 5.10.0 installer from [https://download.qt.io/archive/qt/5.10/5.10.0 here] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components (5.10.x version is not available in the [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe unified installer]). For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 or 2019 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=62705Documentation/Nightly/Developers/Build Instructions/Prerequisites2019-12-02T10:45:12Z<p>Pieper: /* Mac OSX 10.14 (Mojave) */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev libxkbcommon-x11-0<br />
<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554/21</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
<br />
* Build qt from homebrew<br />
<pre><br />
brew install qt<br />
ccmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 -DQt5_DIR=/usr/local//Cellar/qt/5.13.2/lib/cmake/Qt5 ~/slicer/latest/Slicer<br />
<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk make -j20<br />
</pre><br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download download Qt 5.10.0 installer from [https://download.qt.io/archive/qt/5.10/5.10.0 here] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components (5.10.x version is not available in the [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe unified installer]). For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 or 2019 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/KidneyStoneCalculator&diff=61585Documentation/Nightly/Modules/KidneyStoneCalculator2019-11-19T19:31:57Z<p>Pieper: </p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
<br />
<br><br />
'''Authors:''' Frédéric Panthier (1 & 2), Lounès Illoul (3), Laurent Berthe (3), Steeve Doizi (1), Oliver Traxer (1)<br />
<br><br />
(1) Urology Department, Tenon Hospital, APHP, Paris<br />
<br><br />
(2) Urology Department, Hôpital Européen Georges Pompidou, APHP, Paris"<br />
<br><br />
(3) PIMM lab, Arts et Métiers Paris Tech, Paris<br />
<br />
Contact: Frédéric Panthier, fredericpanthier(at)gmail.com<br><br />
<br />
'''License:''' [https://github.com/Slicer/Slicer/blob/master/License.txt Slicer License]<br />
<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
<!--<br />
Here comes a description what the module is good for. Explain briefly how it works and point to the [[documentation/{{documentation/version}}/Modules/{{documentation/modulename}}#References|references]] giving more details on the algorithm.<br />
<br />
{{documentation/{{documentation/version}}/module-description}}<br />
--><br />
{|<br />
|[[File:calculator.png|thumb|left]]<br />
|“KidneyStoneCalculator” consists in a volumetric evaluation of kidney stones and also provides information on surgical duration. Moreover, it provides the estimated time of lithotripsy and consequently the operative time. It provides in a few seconds a 3D view of the stone(s) and axial, coronal and sagittal views with CT-scan DICOMs.<br />
|}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Tutorials}}<br />
<br />
Below, we describe the Instructions Guide to use KidneyStoneCalculator:<br />
<br />
#Open 3DSlicer and download “KidneyStoneCalculator” using the “Extension Manager”<br />
#Import DICOM data from a CT-scan (better with non-enhanced series)<br />
#Open “Crop Volume” module <br />
#*“Create a new annotation ROI” (Region Of Interest)<br />
#*Size the ROI in x, y and z axes using axial (red), sagittal (yellow) and coronal (green) view in four-up disposition.<br />
#*Create a new volume (name will be: “name cropped”)<br />
#*Click “Apply” button<br />
#Open “KidneyStoneCalculator” module:<br />
#*Select volume: “name cropped”<br />
#*Choose threshold (houndfields units) minimum and maximun to fit to the stone<br />
#*If there are multiples stones select “split islands into segments” with a recommended “minimum size” of 40 voxels<br />
#*Select a mode of treatment (laser source, core-diameter of laser fiber, laser settings and stone type)<br />
#*Select “show 3D” if you want to visualize in 3D-view the segmented stone(s)<br />
#*Click “Apply” button. A table will appear to show the segment’s name, volume (mm3) and time of lithotripsy (min)<br />
<br />
{|<br />
|[[File:Calculator-Screenshot.jpg|800px|thumb|left|Screenshot]]<br />
|}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
N/A<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}<br />
<br />
See source code here: https://github.com/fredericpanthier/SlicerKidneyStoneCalculator<br />
<br />
{{documentation/{{documentation/version}}/module-developerinfo}}<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/KidneyStoneCalculator&diff=61584Documentation/Nightly/Modules/KidneyStoneCalculator2019-11-19T19:30:28Z<p>Pieper: </p>
<hr />
<div><noinclude>{{documentation/versioncheck}}<br />
</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-header}}<br />
<!-- ---------------------------- --><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}}<br />
{{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
<br />
<br><br />
'''Authors:''' Frédéric Panthier (1 & 2), Lounès Illoul (3), Laurent Berthe (3), Steeve Doizi (1), Oliver Traxer (1)<br />
<br><br />
(1) Urology Department, Tenon Hospital, APHP, Paris<br />
<br><br />
(2) Urology Department, Hôpital Européen Georges Pompidou, APHP, Paris"<br />
<br><br />
(3) PIMM lab, Arts et Métiers Paris Tech, Paris<br />
<br />
Contact: Frédéric Panthier, fredericpanthier(at)gmail.com<br><br />
<br />
'''License:''' [https://github.com/Slicer/Slicer/blob/master/License.txt Slicer License]<br />
<br />
{{documentation/{{documentation/version}}/module-introduction-row}}<br />
{{documentation/{{documentation/version}}/module-introduction-end}}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Module Description}}<br />
<!--<br />
Here comes a description what the module is good for. Explain briefly how it works and point to the [[documentation/{{documentation/version}}/Modules/{{documentation/modulename}}#References|references]] giving more details on the algorithm.<br />
<br />
{{documentation/{{documentation/version}}/module-description}}<br />
--><br />
{|<br />
|[[File:calculator.png|thumb|left]]<br />
|“KidneyStoneCalculator” consists in a volumetric evaluation of kidney stones and also provides information on surgical duration. Moreover, it provides the estimated time of lithotripsy and consequently the operative time. It provides in a few seconds a 3D view of the stone(s) and axial, coronal and sagittal views with CT-scan DICOMs.<br />
|}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Use Cases}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Tutorials}}<br />
<br />
Below, we describe the Instructions Guide to use KidneyStoneCalculator:<br />
<br />
#Open 3DSlicer and download “KidneyStoneCalculator” using the “Extension Manager”<br />
#Import DICOM data from a CT-scan (better with non-enhanced series)<br />
#Open “Crop Volume” module <br />
#*“Create a new annotation ROI” (Region Of Interest)<br />
#*Size the ROI in x, y and z axes using axial (red), sagittal (yellow) and coronal (green) view in four-up disposition.<br />
#*Create a new volume (name will be: “name cropped”)<br />
#*Click “Apply” button<br />
#Open “KidneyStoneCalculator” module:<br />
#*Select volume: “name cropped”<br />
#*Choose threshold (houndfields units) minimum and maximun to fit to the stone<br />
#*If there are multiples stones select “split islands into segments” with a recommended “minimum size” of 40 voxels<br />
#*Select a mode of treatment (laser source, core-diameter of laser fiber, laser settings and stone type)<br />
#*Select “show 3D” if you want to visualize in 3D-view the segmented stone(s)<br />
#*Click “Apply” button. A table will appear to show the segment’s name, volume (mm3) and time of lithotripsy (min)<br />
<br />
{|<br />
|[[File:Calculator-Screenshot.jpg|800px|thumb|left|Screenshot]]<br />
|}<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Panels and their use}}<br />
N/A<br />
<!--<br />
{{documentation/{{documentation/version}}/module-parametersdescription}}<br />
--><br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Similar Modules}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|References}}<br />
N/A<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-section|Information for Developers}}[https://github.com/fredericpanthier/SlicerKidneyStoneCalculator See source code here: https://github.com/fredericpanthier/SlicerKidneyStoneCalculator]{{documentation/{{documentation/version}}/module-developerinfo}}<br />
<br />
<br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/module-footer}}<br />
<!-- ---------------------------- --></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Compile&diff=61583Documentation/Nightly/Developers/Build Instructions/Compile2019-11-18T21:31:10Z<p>Pieper: /* BUILD Slicer */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
==BUILD Slicer==<br />
<br />
After configuration, start the build process in the <code>Slicer-SuperBuild</code> directory<br />
<br />
{| width="100%"<br />
! width="50%" style="border-bottom: 1px solid darkgrey;font-size: 75%;" |Linux or MacOSX (Makefile)<br />
! width="50%" style="border-bottom: 1px solid darkgrey;font-size: 75%;" |Windows (Visual Studio)<br />
|-<br />
| valign="top" |<br />
Start a terminal.<br />
{{pre2|scroll|<br />
$ cd ~/Projects/Slicer-SuperBuild<br />
<br />
$ make -j<NUMBEROFCORES>}}<br />
<br />
In case of file download hash mismatch error, you need to acquire the latest wget, and build cmake with OpenSSL turned on. For more information, see [http://slicer-devel.65872.n3.nabble.com/How-to-solve-wget-error-certificate-common-name-c-ssl-fastly-net-doesn-t-match-requested-host-name-p-td4031491.html here] and [http://slicer-devel.65872.n3.nabble.com/Re-Hash-Error-td4031386.html here]<br />
<br />
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.<br />
<br />
| valign="top" |<br />
Start Windows Explorer. [http://www.wikihow.com/Open-Windows-Explorer Need help?]<br />
<ol start="1" style="list-style-type: decimal;"><br />
<li>Open <code>Slicer-SuperBuild\Slicer.sln</code> in Visual Studio</li><br />
<li>Select build configuration. Usually '''Release''' or '''Debug'''</li><br />
<li>Select menu <code>Project -> Build Solution</code></li><br />
</ol><br />
<br />
If you make local changes to Slicer, open the solution file located in the directory <code>Slicer-SuperBuild/Slicer-build</code> instead. You should then be able to either build all projects or just a specific one.<br />
|}</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61509Documentation/Labs/ImageStacks2019-10-28T20:31:38Z<p>Pieper: /* Options */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-alone processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing<br />
* option: displays available computer memory before trying to load (maybe use https://psutil.readthedocs.io/en/latest/#memory)<br />
<br />
== Work in Progress ==<br />
<br />
As of October 2019, the repo below has an implementation that works for slice files ready by SimpleITK (png, tiff, etc) extracting one component and options for 50% downsampling.<br />
<br />
* https://github.com/pieper/SlicerImageStacks</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61497Documentation/Labs/ImageStacks2019-10-21T14:22:00Z<p>Pieper: /* Work in Progress */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-alone processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing<br />
<br />
== Work in Progress ==<br />
<br />
As of October 2019, the repo below has an implementation that works for slice files ready by SimpleITK (png, tiff, etc) extracting one component and options for 50% downsampling.<br />
<br />
* https://github.com/pieper/SlicerImageStacks</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61496Documentation/Labs/ImageStacks2019-10-17T20:23:12Z<p>Pieper: /* Work in Progres */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-alone processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing<br />
<br />
== Work in Progress ==<br />
<br />
* https://github.com/pieper/SlicerImageStacks</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61495Documentation/Labs/ImageStacks2019-10-16T20:05:07Z<p>Pieper: /* Options */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-alone processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing<br />
<br />
== Work in Progres ==<br />
<br />
* https://github.com/pieper/SlicerImageStacks</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61494Documentation/Labs/ImageStacks2019-10-16T20:03:07Z<p>Pieper: /* Options */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-along processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing<br />
<br />
== Work in Progres ==<br />
<br />
* https://github.com/pieper/SlicerImageStacks</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61492Documentation/Labs/Slicer5-roadmap2019-10-16T15:23:10Z<p>Pieper: /* Additional proposed changes to be discussed */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well. See https://github.com/Slicer/Slicer/commit/b7650af3c27f34fc894bfdd587f2a4c02ba62a8b<br />
<br />
====Model Hierarchies====<br />
<br />
Remove Model Hierarchy feature and make sure that Subject Hierarchy covers all use cases. This will impact ModelMaker, which should be converted to a simpler version that only returns models and not semantics. Need to check extensions, especially SlicerDMRI, for any dependencies on Model Hierarchy.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Usability ===<br />
<br />
==== Volume Rendering Activation Method ====<br />
<br />
We have had lots of issues with people finding the eye icon.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer - Slicer Jupyter extension has been added, do we need more?<br />
** Do we want history across sessions?<br />
** Quick access to script repository<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom' (See also: https://github.com/Slicer/Slicer/pull/1231)<br />
* Remove DICOM Networking (DIMSE) code https://discourse.slicer.org/t/dicom-retrieve-on-windows-10-there-is-no-service-listening-dicom-communications-no-telnet-connection-to-ports<br />
* Update the logo along [https://discourse.slicer.org/t/slicer-module-panel-icon-in-dark-mode/8353/3 as discussed here].</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61490Documentation/Labs/Slicer5-roadmap2019-10-11T18:22:28Z<p>Pieper: /* Acquisition transform */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well. See https://github.com/Slicer/Slicer/commit/b7650af3c27f34fc894bfdd587f2a4c02ba62a8b<br />
<br />
====Model Hierarchies====<br />
<br />
Remove Model Hierarchy feature and make sure that Subject Hierarchy covers all use cases. This will impact ModelMaker, which should be converted to a simpler version that only returns models and not semantics. Need to check extensions, especially SlicerDMRI, for any dependencies on Model Hierarchy.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Usability ===<br />
<br />
==== Volume Rendering Activation Method ====<br />
<br />
We have had lots of issues with people finding the eye icon.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer - Slicer Jupyter extension has been added, do we need more?<br />
** Do we want history across sessions?<br />
** Quick access to script repository<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom'<br />
* Remove DICOM Networking (DIMSE) code https://discourse.slicer.org/t/dicom-retrieve-on-windows-10-there-is-no-service-listening-dicom-communications-no-telnet-connection-to-ports<br />
* Update the logo along [https://discourse.slicer.org/t/slicer-module-panel-icon-in-dark-mode/8353/3 as discussed here].</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61486Documentation/Labs/ImageStacks2019-10-01T17:34:02Z<p>Pieper: /* Desired properties */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
* handle greater than 8 bits per pixel if the source format supports it<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-along processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61485Documentation/Labs/ImageStacks2019-10-01T17:18:41Z<p>Pieper: /* Options */</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-along processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option with reusable logic<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/ImageStacks&diff=61484Documentation/Labs/ImageStacks2019-10-01T17:17:48Z<p>Pieper: Created page with "== Background == Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources. Examples are like: <pre> directoryPath = '/V..."</p>
<hr />
<div>== Background ==<br />
<br />
Image stacks are generated in a lot of scenarios, like microscopes, microCT, and other non-medical sources.<br />
<br />
Examples are like:<br />
<pre><br />
directoryPath = '/Volumes/SSD2T/data/SlicerMorph/Sample_for_steve/1326_Rec'<br />
pathFormat = '%s/1326__rec%04d.png'<br />
</pre><br />
<br />
== Common issues ==<br />
* Data is really grayscale, but encoded using jpg or other color format<br />
* Data may be large and needs to be cropped or resampled for use<br />
* Data will not have any consistent metadata<br />
** pixel / slice spacing<br />
** orientation (e.g. mapping to anatomical coordinates)<br />
** acquisition time, equipment, operator, etc<br />
* Only a subset of data may actually be wanted (ROI or subsampled volume)<br />
<br />
== Current reader ==<br />
<br />
Delegates to ITK via vtkITKArchetype mechanism. Lots of hidden logic to guess possible mapping from image files to volumes.<br />
* Guesses filename pattern, which can easily fail<br />
* Creates vector volume for RGB and user must manually convert to scalar<br />
* Reads full volume and then must crop or resample<br />
* Can be slow to read / decompress large files<br />
<br />
== Available readers ==<br />
<br />
* vtkPNGReader<br />
* itk image IO<br />
* Qt Image<br />
* PIL / PILLOW python packages (not currently bundled in Slicer, but pip installable)<br />
* QWebEngine<br />
<br />
== Desired properties ==<br />
<br />
* fast, ideally multithreaded<br />
* compatible with multiple file formats (at least jpg, tiff, bmp, png)<br />
* clean slicer integration<br />
* support arbitrary file naming conventions with different ways of mapping names to volumes or other structures<br />
* ability to map anatomical coordinates (e.g. click on nose, right ear, and top of head)<br />
* ability to convert to scalar and/or crop/resample while reading<br />
* store pyramid encoded volumes for later reuse<br />
* use a single-file format for simplicity (avoid creating even more single slice image files)<br />
* store metadata for later reuse<br />
* allow scripted use outside slicer<br />
* use reference volume or ROI to define desired geometry (e.g. reuse crop volume logic)<br />
<br />
== Options ==<br />
<br />
Implementation<br />
* stand-along processing script with command line options -- too difficult for users in general, no interactive feedback<br />
* plugin for Add Data module that exposes some options -- not enough room for gui, not interactive<br />
* Independent importer module -- probably the best option<br />
<br />
Features<br />
* store pyramid files in nrrd or some minimal dicom<br />
* option: put dicom files in database for reuse<br />
* option: use low-res as visualization proxy but use fill res for processing</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs&diff=61479Documentation/Labs2019-10-01T13:59:49Z<p>Pieper: Add image stacks lab</p>
<hr />
<div>This is the place where we will keep track of our experiments and projects.<br />
<br />
__TOC__<br />
<br />
= On-going =<br />
<br />
== Roadmap ==<br />
* [[{{FULLPAGENAME}}/Slicer5-roadmap|Slicer 5]]<br />
<br />
== Internals ==<br />
* [[{{FULLPAGENAME}}/NrrdReading_Writing_Optimizations|NrrdReading_Writing_Optimizations]]<br />
* [[{{FULLPAGENAME}}/OpenGLFilters|OpenGLFilters]]<br />
* [[{{FULLPAGENAME}}/DeprecatedModules|DeprecatedModules extension]]<br />
* [[{{FULLPAGENAME}}/FHSCompliantDirectoryStructure|FHS compliant directory structure]]<br />
* [[{{FULLPAGENAME}}/FiberTractMeasurementAndVisualization|Fiber Tract measurement and visualization]]<br />
* [[{{FULLPAGENAME}}/VTKWidgets|VTK Widgets improvements]]<br />
* [[{{FULLPAGENAME}}/CLIInfrastructureCleanupAndRefactoring|CLI infrastructure cleanup and refactoring]]<br />
* [[{{FULLPAGENAME}}/UpgradingCompilerInfrastructure|Upgrading Compiler Infrastructure]]<br />
* [[{{FULLPAGENAME}}/ViewInfrastructureImprovements| View Infrastructure Improvements]]<br />
* [[{{FULLPAGENAME}}/CDash Improvements|CDash Improvements]]<br />
* [[{{FULLPAGENAME}}/SlicerBridge|SlicerBridge]]<br />
<!--<br />
* [[{{FULLPAGENAME}}/Display2dText|Display 2D text in viewers]]<br />
--><br />
* [[{{FULLPAGENAME}}/CI-and-NightlyPackagesGeneration|Continuous Integration and Nightly packages build infrastructure]]<br />
* [[{{FULLPAGENAME}}/ParameterSerializer|Parameter Serializer support for CLIs]]<br />
* [[{{FULLPAGENAME}}/Augmented Reality and Virtual Reality support|Augmented Reality and Virtual Reality support]]<br />
* [[{{FULLPAGENAME}}/Infrastucture Status|Infrastucture Status]]<br />
* [[{{FULLPAGENAME}}/Improving Slicer Packages Download Experience|Improving Slicer Packages Download experience]]<br />
* [[{{FULLPAGENAME}}/Sequences|Sequences]]<br />
* [[{{FULLPAGENAME}}/Improving Markups|Improving Markups]]<br />
* [[{{FULLPAGENAME}}/Surface Toolbox update|Surface Toolbox update]]<br />
* [[{{FULLPAGENAME}}/SampleDataModuleImprovements|Sample Data Module Improvements]]<br />
* [[{{FULLPAGENAME}}/BuildSystem_ImproveCMakeConfigurationTime|BuildSystem: Improve CMake configuration time]]<br />
<br />
== Libraries ==<br />
* [[{{FULLPAGENAME}}/VTK-Orientation|Design: Addition of orientation to VTK data structures]]<br />
* [[{{FULLPAGENAME}}/VTK-String|Design: Make VTK strings encoding aware]]<br />
<br />
== Python ==<br />
* [[{{FULLPAGENAME}}/CallingPythonMethodsFromCpp|Calling Python methods from Cpp]]<br />
* [[{{FULLPAGENAME}}/IPython|IPython]]<br />
* [[{{FULLPAGENAME}}/PythonCondaBuild|Python conda build]]<br />
<br />
== Compilers & IDE ==<br />
* [[{{FULLPAGENAME}}/ModernizeC++|Modernize to c++11 and beyond]]<br />
<br />
== Virtual Machines ==<br />
* [[{{FULLPAGENAME}}/GPU Virtualization|GPU Virtualization]]<br />
<br />
== Documentation ==<br />
* [[{{FULLPAGENAME}}/DocumentationImprovments|Documentation Improvements (Wiki, website, ...)]]<br />
* [[{{FULLPAGENAME}}/ModulesAndEvents|Intermediate documentation for developers]]<br />
<br />
== Tutorials ==<br />
* [[{{FULLPAGENAME}}/IPythonSlicerTutorials|IPython Slicer Tutorials]]<br />
<br />
== Source code management ==<br />
* [[{{FULLPAGENAME}}/TransitionToGit|Transition to GitHub as authoritative version control system]]<br />
<br />
== Extension ==<br />
* [[{{FULLPAGENAME}}/ExtensionsServer|Extensions Server (also described as Extensions Manager or Catalog)]]<br />
* [[{{FULLPAGENAME}}/ExtensionsFrameworkRoadmap|Extensions Framework Roadmap]]<br />
* [[{{FULLPAGENAME}}/CustomSlicerGenerator|Custom Slicer Generator]]<br />
* [[{{FULLPAGENAME}}/ExtensionsMetadata|Improving Extensions Metadata]]<br />
<br />
== Functionalities ==<br />
* [[{{FULLPAGENAME}}/FlyThroughNavigation|Fly-through Navigation]]<br />
* [[{{FULLPAGENAME}}/AutomaticUpdateAndInstallationFramework|Automatic Update and Installation Framework]]<br />
* [[{{FULLPAGENAME}}/ApplicationUsageAnalytics|Application usage analytics]]<br />
* [[{{FULLPAGENAME}}/Plotting2DLineSegments|Plotting 2D Line Segments]]<br />
* [[{{FULLPAGENAME}}/Slicer_Visualization_module|Brain Connectome Visualization]]<br />
<br />
== Packaging ==<br />
* [[{{FULLPAGENAME}}/HomebrewCask|Homebrew Cask]]<br />
<br />
== Image Stacks ==<br />
* [[{{FULLPAGENAME}}/ImageStacks|Image Stacks]]<br />
<br />
= Completed =<br />
<br />
* [[Slicer4:Developers|Developer Projects]]<br />
<br />
== Extension ==<br />
* [[{{FULLPAGENAME}}/EasyExtensionContribution|Easy Extension Contribution]] - See [[Documentation/Nightly/Developers/ExtensionWizard|ExtensionWizard]]<br />
<br />
== Internals ==<br />
* [[{{FULLPAGENAME}}/StartupTimeImprovement|Slicer startup time improvement]]<br />
* [[{{FULLPAGENAME}}/CMake-ified Python|CMake-ified Python]] - See [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21911 r21911], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21912 r21912], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21913 r21913]<br />
* [[{{FULLPAGENAME}}/NonlinearTransforms|Full support for non-linear transforms]]<br />
<br />
== Libraries ==<br />
* [[{{FULLPAGENAME}}/Qt5-and-VTK8|Migration to Qt5 and VTK8]]<br />
* [[{{FULLPAGENAME}}/OpenCV|Integration with OpenCV]]<br />
* [[{{FULLPAGENAME}}/ITKv4|ITKv4]]<br />
* [[{{FULLPAGENAME}}/Qt484|Qt484]]<br />
* [[{{FULLPAGENAME}}/VTK6|VTK6]]<br />
* [[{{FULLPAGENAME}}/VTK7|VTK7]]<br />
<br />
== Python ==<br />
* [[{{FULLPAGENAME}}/Pip|Pip]]<br />
* [[{{FULLPAGENAME}}/DevelopmentWithGit|Development with Git]] - See [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21863 r21863], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21867 r21867], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21869 r21869], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21879 r21879], [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=21891 r21891]<br />
* [[{{FULLPAGENAME}}/PythonObserverCallbacks|Python observer callbacks]]<br />
<br />
== Compilers & IDE ==<br />
* [[{{FULLPAGENAME}}/Ninja|Ninja]]<br />
* [[{{FULLPAGENAME}}/VS2012|VS2012]]<br />
* [[{{FULLPAGENAME}}/NUMPY171|Support for Numpy 1.7.1]]<br />
<br />
== Modules ==<br />
* [[{{FULLPAGENAME}}/SimpleFilters|Simple Filters]]<br />
* [[{{FULLPAGENAME}}/Editor|Editor]]<br />
<br />
== Tutorials testing ==<br />
* [[{{FULLPAGENAME}}/TutorialTesting/4.3-Release|4.3 Release]]<br />
<br />
== Debug ==<br />
* [[{{FULLPAGENAME}}/BRAINS_and_ITKv4_issue|BRAINS and ITKv4 issue]]<br />
<br />
== Internals ==<br />
* [[{{FULLPAGENAME}}/Segmentations|Segmentations]]<br />
* [[{{FULLPAGENAME}}/MultiDimensional Data Management|MultiDimensional Data Management]]<br />
* [[{{FULLPAGENAME}}/DICOMExport|DICOM Export]]<br />
* [[{{FULLPAGENAME}}/SliceViewAnnotations|Slice View Annotations]]<br />
* [[{{FULLPAGENAME}}/SubjectHierarchy|Subject hierarchy module and plugins]]<br />
* [[{{FULLPAGENAME}}/I18N|Internationalization]]<br />
* [[{{FULLPAGENAME}}/Units|Units]]<br />
* [https://github.com/TubeTK/SlicerExecutionModel/wiki/SlicerExecutionModel-Parameter-Serialization SlicerExecutionModel Parameter Serialization]<br />
<br />
<br />
= Abandoned =<br />
<br />
* [[{{FULLPAGENAME}}/SlicerConfigAndUseSlicerTweaks|SlicerConfig and UseSlicer Tweaks]]</div>Pieperhttps://www.slicer.org/w/index.php?title=User:ChrisR&diff=61458User:ChrisR2019-09-21T16:27:44Z<p>Pieper: Creating user page for new user.</p>
<hr />
<div>neuroimager based at the University of South Carolina. I server as co-director for the McCausland Center for Brain Imaging and as an endowed chair in the Department of Psychology. I have developed the MRIcroGL, Surfice and dcm2niix tools. My research examines recovery from stroke. The team explores how brain imaging can help us understand aphasia and neglect.</div>Pieperhttps://www.slicer.org/w/index.php?title=User_talk:ChrisR&diff=61459User talk:ChrisR2019-09-21T16:27:44Z<p>Pieper: Welcome!</p>
<hr />
<div>'''Welcome to ''SlicerWiki''!'''<br />
We hope you will contribute much and well.<br />
You will probably want to read the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents help pages].<br />
Again, welcome and have fun! [[User:Pieper|Pieper]] ([[User talk:Pieper|talk]]) 12:27, 21 September 2019 (EDT)</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Python_scripting&diff=61417Documentation/Nightly/Developers/Python scripting2019-09-12T13:58:35Z<p>Pieper: /* Start Here */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
<br />
= Start Here =<br />
<br />
Please read [https://docs.google.com/presentation/d/1JXIfs0rAM7DwZAho57Jqz14MRn2BIMrjB17Uj_7Yztc/edit?usp=sharing these slides] and [https://www.slicer.org/wiki/Documentation/Nightly/Training#PerkLab.27s_Slicer_bootcamp_training_materials these slides]. This will give you an overall idea what is possible, and you can use a binary download of Slicer to work through the example code. <br />
<br />
Refer to [http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python this description that includes links to all the documentation].<br />
<br />
Look at the [[Documentation/{{documentation/version}}/ScriptRepository |Script Repository]] for examples and inspiration.<br />
<br />
= Background =<br />
<br />
This is an evolution of the [[Slicer3:Python|python implementation in slicer3]]. Slicer's APIs are now natively wrapped in python. <br />
<br />
Topics like plotting are still experimental in slicer4.<br />
<br />
See [http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python this 2012 presentation on the state of python in slicer4].<br />
<br />
'''See [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Programming_Tutorial|the python slicer4 tutorial for more examples]].'''<br />
<br />
[[Documentation/{{documentation/version}}/Developers/Tutorials/SelfTestModule|Slicer Self Tests]] can be written in python, and provide a good source of examples for manipulating the data, logic, and gui of slicer.<br />
<br />
= Start Here for Scripted Module and Extension Development=<br />
An extensive tutorial and reference page was created [http://www.na-mic.org/Wiki/index.php/2013_Project_Week_Breakout_Session:Slicer4Python for the Slicer/Python breakout session at the NA-MIC 2013 Summer Project Week].<br />
<br />
= Usage options =<br />
<br />
==Python Interactor==<br />
<br />
Use the Window->Python Interactor (Control-3 on window/linux, Command-3 on mac) to bring up the Qt-based console with access to the vtk, Qt, and Slicer wrapped APIs.<br />
<br />
Most python code can be installed and run from this window, but because it exists in the event driven Qt GUI environment, some operations like, like parallel processing or headless operation, are not easily supported.<br />
<br />
=== Examples ===<br />
<br />
Start Slicer and bring up python console. Load a sample volume like this:<br />
<br />
<pre><br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
sampleDataLogic.downloadMRHead()<br />
</pre><br />
<br />
Get the volume node for that volume:<br />
<br />
<pre><br />
n = getNode('MRHead')<br />
</pre><br />
<br />
You can use Tab to see lists of methods for a class instance.<br />
<br />
==== Accessing Volume data as numpy array ====<br />
<br />
You can easily inspect and manipulate volume data using numpy and related code. In slicer you can do this:<br />
<br />
<pre><br />
a = array('MRHead')<br />
</pre><br />
<br />
and 'a' will be a pointer to the appropriate data (no data copying). If you get an error that 'array' is not defined then run 'import slicer.util' and use 'slicer.util.array'. Scalar volumes become three-dimensional arrays, while vector volumes become 4D, and tensor volumes are 5D. All arrays can be manipulated directly. After modification is completed, call Modified() method of the volume node to indicate that the image is modified and trigger display update.<br />
<br />
The '''array''' method is intended for quick testing only, as multiple nodes may have the same name and various arrays may be retrieved from MRML nodes. In Slicer modules, it is recommended to use '''arrayFromVolume''' instead, which takes a MRML node as input.<br />
<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
a = arrayFromVolume(volumeNode)<br />
# Increase image contrast<br />
a[:] = a * 2.0<br />
arrayFromVolumeModified(volumeNode)<br />
</pre><br />
<br />
If you don't process the data in-place but you have computation results in a numpy array, then you have to copy the contents of a numpy array into a volume, using '''updateVolumeFromArray''':<br />
<br />
<pre><br />
import numpy as np<br />
import math<br />
<br />
def some_func(x, y, z):<br />
return 0.5*x*x + 0.3*y*y + 0.5*z*z<br />
<br />
a = np.fromfunction(some_func,(30,20,15))<br />
<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode')<br />
volumeNode.CreateDefaultDisplayNodes()<br />
updateVolumeFromArray(volumeNode, a)<br />
setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
==== Accessing Model data as numpy array ====<br />
<br />
You can easily inspect and manipulate point coordinates of a model using numpy and related code by calling `arrayFromModelPoints`. After modification is completed, call Modified() method on the polydata to indicate that the model is modified and trigger display update.<br />
<br />
<pre><br />
# Create a model containing a sphere<br />
sphere = vtk.vtkSphereSource()<br />
sphere.SetRadius(30.0)<br />
sphere.Update()<br />
modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode')<br />
modelNode.SetAndObservePolyData(sphere.GetOutput())<br />
modelNode.CreateDefaultDisplayNodes()<br />
a = arrayFromModelPoints(modelNode)<br />
# change Y scaling<br />
a[:,2] = a[:,2] * 2.5<br />
arrayFromModelPointsModified(modelNode)<br />
</pre><br />
<br />
==== Running a CLI from Python ====<br />
<br />
Here's an example to create a model from a volume using the [[Documentation/4.0/Modules/GrayscaleModelMaker|Grayscale Model Maker]]<br />
<pre><br />
def grayModel(volumeNode):<br />
parameters = {}<br />
parameters["InputVolume"] = volumeNode.GetID()<br />
outModel = slicer.vtkMRMLModelNode()<br />
slicer.mrmlScene.AddNode( outModel )<br />
parameters["OutputGeometry"] = outModel.GetID()<br />
grayMaker = slicer.modules.grayscalemodelmaker<br />
return (slicer.cli.runSync(grayMaker, None, parameters))<br />
</pre><br />
<br />
To try this, download the MRHead dataset from the [[Documentation/4.0/Modules/SampleData|Sample Data]] and paste the code into the python console and then run this:<br />
<pre><br />
v = getNode('MRHead')<br />
cliNode = grayModel(v)<br />
</pre><br />
<br />
Here is an example for running a CLI module from a scripted module:<br />
https://github.com/fedorov/ChangeTrackerPy/blob/master/ChangeTracker/ChangeTrackerWizard/ChangeTrackerRegistrationStep.py#L56-L67<br />
<br />
'' Passing Fiducials to CLIs via a Python Script ''<br />
<br />
<pre><br />
<br />
import SampleData<br />
sampleDataLogic = SampleData.SampleDataLogic()<br />
head = sampleDataLogic.downloadMRHead()<br />
volumesLogic = slicer.modules.volumes.logic()<br />
headLabel = volumesLogic.CreateLabelVolume(slicer.mrmlScene, head, 'head-label')<br />
<br />
fiducialNode = slicer.vtkMRMLAnnotationFiducialNode()<br />
fiducialNode.SetFiducialWorldCoordinates((1,0,5))<br />
fiducialNode.SetName('Seed Point')<br />
fiducialNode.Initialize(slicer.mrmlScene)<br />
fiducialsList = getNode('Fiducials List')<br />
<br />
params = {'inputVolume': head.GetID(), 'outputVolume': headLabel.GetID(), 'seed' : fiducialsList.GetID(), 'iterations' : 2} <br />
<br />
cliNode = slicer.cli.runSync(slicer.modules.simpleregiongrowingsegmentation, None, params)<br />
<br />
</pre><br />
<br />
''Running CLI in the background''<br />
<br />
If the CLI module is executed using slicer.cli.run method then the CLI module runs in a background thread, so the call to grayModel will return right away and the user interface will not be blocked. The slicer.cli.run call returns a cliNode (an instance of [http://slicer.org/doc/html/classvtkMRMLCommandLineModuleNode.html vtkMRMLCommandLineModuleNode]) which can be used to monitor the progress of the module.<br />
<br />
In this example we create a simple callback that will be called whenever the cliNode is modified. The status will tell you if the nodes is Pending, Running, or Completed.<br />
<br />
<pre><br />
def printStatus(caller, event):<br />
print("Got a %s from a %s" % (event, caller.GetClassName()))<br />
if caller.IsA('vtkMRMLCommandLineModuleNode'):<br />
print("Status is %s" % caller.GetStatusString())<br />
<br />
cliNode.AddObserver('ModifiedEvent', printStatus)<br />
</pre><br />
<br />
If you need to cancel the CLI, call<br />
<pre><br />
cliNode.Cance()<br />
</pre><br />
<br />
To get the log info for the process you can call <pre>cliNode.GetOutputText()</pre> and <pre>cliNode.GetErrorText()</pre>.<br />
<br />
''Get list of parameter names''<br />
<br />
The following script prints all the parameter names of a CLI parameter node:<br />
<br />
<pre><br />
cliModule = slicer.modules.grayscalemodelmaker<br />
n=cliModule.cliModuleLogic().CreateNode()<br />
for groupIndex in xrange(0,n.GetNumberOfParameterGroups()):<br />
for parameterIndex in xrange(0,n.GetNumberOfParametersInGroup(groupIndex)):<br />
print(' Parameter ({0}/{1}): {2} ({3})'.format(groupIndex, parameterIndex, n.GetParameterName(groupIndex, parameterIndex), n.GetParameterLabel(groupIndex, parameterIndex)))<br />
</pre><br />
<br />
==== Accessing slice vtkRenderWindows from slice views ====<br />
<br />
The example below shows how to get the rendered slice window.<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
redWidget = lm.sliceWidget('Red')<br />
redView = redWidget.sliceView()<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInput(redView.renderWindow())<br />
wti.Update()<br />
v = vtk.vtkImageViewer()<br />
v.SetColorWindow(255)<br />
v.SetColorLevel(128)<br />
v.SetInputConnection(wti.GetOutputPort())<br />
v.Render()<br />
</pre><br />
<br />
= Script Repository = <br />
<br />
See [[Documentation/Nightly/ScriptRepository|ScriptRepository]] for a larger collection of example code.<br />
<br />
{{:Documentation/{{documentation/version}}/Developers/FAQ/Python Scripting|Python Scripting}}</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61411Documentation/Labs/Slicer5-roadmap2019-09-10T11:55:19Z<p>Pieper: /* Additional proposed changes to be discussed */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well.<br />
<br />
====Model Hierarchies====<br />
<br />
Remove Model Hierarchy feature and make sure that Subject Hierarchy covers all use cases. This will impact ModelMaker, which should be converted to a simpler version that only returns models and not semantics. Need to check extensions, especially SlicerDMRI, for any dependencies on Model Hierarchy.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Usability ===<br />
<br />
==== Volume Rendering Activation Method ====<br />
<br />
We have had lots of issues with people finding the eye icon.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer - Slicer Jupyter extension has been added, do we need more?<br />
** Do we want history across sessions?<br />
** Quick access to script repository<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom'<br />
* Remove DICOM Networking (DIMSE) code https://discourse.slicer.org/t/dicom-retrieve-on-windows-10-there-is-no-service-listening-dicom-communications-no-telnet-connection-to-ports<br />
* Update the logo along [https://discourse.slicer.org/t/slicer-module-panel-icon-in-dark-mode/8353/3 as discussed here].</div>Pieperhttps://www.slicer.org/w/index.php?title=New_users&diff=61381New users2019-08-26T23:28:01Z<p>Pieper: Add link to install instructions</p>
<hr />
<div>__TOC__ <br />
<br />
==Welcome to 3D Slicer==<br />
[[File:WelcomeToSlicer-2016-05-31.jpg|thumb|200x200px]]<br />
<br />
Welcome to the 3D Slicer community. Here you will learn the basics of using Slicer including installing 3D Slicer, the basics of the main application GUI, how to use Slicer and where to find tutorials and more information.<br />
<br />
{|class = "wikitable" style="font-size: 150%;"<br />
!The [https://discourse.slicer.org/ 3D Slicer Discourse forum] <br />
is a place for you to ask any questions regarding Slicer and its usage.<br />
|}<br />
<br />
===What is Slicer ?===<br />
{{:Documentation/{{documentation/currentversion}}/Slicer}}<br />
<br />
===Hardware Requirements===<br />
3D Slicer is an open-source package that can be used on Mac, Linux and Windows. In order to run 3D Slicer your computer must have the graphics capabilities and memory to hold the original image data and process results. A 64-bit system is required. Click [[Documentation/{{documentation/currentversion}}/SlicerApplication/HardwareConfiguration|here]] more information.<br />
[[File:Screen_Shot_2016-06-01_at_1.21.15_PM.png|thumb|600px]]<br />
<br />
===Installing 3D Slicer===<br />
To install Slicer, click [http://download.slicer.org here]<br />
<br />
The Nightly version of 3D Slicer is updated nightly as groups of developers make changes. The Stable version of 3D Slicer is not updated nightly and is more rigorously tested.<br />
<br />
Once downloaded, [[Documentation/{{documentation/currentversion}}/SlicerApplication/Installation|follow the instructions]] to complete installation.<br />
<br />
===Further Documentation===<br />
If you're interested in extending your knowledge, access the [[Documentation/{{documentation/currentversion}}|User Manual]]. See also the [http://slicer-users.65878.n3.nabble.com archives of the users mailing list] . The archive is searchable so most answers to questions can be found there.<br />
<br />
If you're a developer looking for more information, access the [[Documentation/Nightly/Developers|Developer Manual]]. See also [http://slicer-devel.65872.n3.nabble.com archives of the developer's mailing list] . Similar to the Users Mailing List archive, it is searchable.<br />
<br />
==Main Application GUI==<br />
3D Slicer is built on a modular architecture. The Main Application GUI is divided into six components: the Application Menu Bar, the Application Toolbar, the Module GUI Panel, the Data Probe Panel, the 2D Slice Viewers, and the 3D Viewer. This section will introduce you to the basic functions on the main application's GUI. If you require detailed information, visit this [[Documentation/{{documentation/currentversion}}/SlicerApplication/MainApplicationGUI|page]].<br />
<br />
<br />
Open 3D Slicer and load your own data or download sample data to explore. Go ahead and click around the user interface.<br />
<br />
<br />
<gallery widths=400px heights=450px class="center"><br />
File:LoadingData-16-06-06.png<br />
File:SampleData-06-06.png<br />
</gallery><br />
<br />
<br />
<br />
From the Welcome panel, you can load your own data or download sample data. Sample data is often useful for exploring the features of 3D Slicer if you don't have data of your own.<br />
<br />
<br />
<br />
<gallery widths=500px heights=350px class="center"><br />
File:SliceViewOptions-2016-06-01.png<br />
File:ModuleExtensionAccess-2016-06-01.png<br />
</gallery><br />
<br />
<br />
Click on the push pin in the top left corner of each of the Slice Viewers or the 3D Viewer to see more options. In the Slice Viewers, the horizontal bar can be used to scroll through slices or select a slice. You can explore the various options using your loaded data or downloaded sample data.<br />
<br />
[[File:TutorialPreview-2016-05-31.png|313x313px|right]]<br />
==Tutorials==<br />
The 3D Slicer documentation has an abundance of tutorials to help you familiarize yourself with the basics of 3D Slicer and with specific<br />
modules.<br />
<br />
Try the [[Documentation/{{documentation/currentversion}}/Training#Slicer_Welcome_Tutorial|Welcome Tutorial]] and the [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Data_Loading_and_3D_Visualization|Data Loading and 3D Visualization Tutorial]] to learn the basics of using 3D Slicer.<br />
*To learn about using Slicer for 3D Printing, visit [[Documentation/{{documentation/currentversion}}/Training#Slicer4_3D_Printing|this tutorial]]. <br />
*To learn about Neurosurgical Planning with Slicer, visit [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Neurosurgical_Planning_Tutorial|this tutorial]].<br />
*To learn about DTI, visit [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Diffusion_Tensor_Imaging_Tutorial|this tutorial]].<br />
<br />
For more tutorials, visit the [[Documentation/{{documentation/currentversion}}/Training|Tutorial page]] to see a comprehensive list. Additionally, visit our [https://www.youtube.com/channel/UC11x1iQ7ydSIFYw4L6wveXg?view_as=public YouTube page] for video tutorials.<br />
<br />
If you would like to see a list of example cases with data sets and steps to achieve the same result, visit the [[Documentation/{{documentation/currentversion}}/Registration/RegistrationLibrary|Registration Library]]<br />
<br />
==Modules==<br />
3D Slicer has an abundance of modules to allow it's variety of functionalities. Refer to the [[Documentation/{{documentation/currentversion}}#Modules_by_category|documentation page]] for a comprehensive list of modules. Each module has it's own documentation page that has information about the module and may include a tutorial. 3D Slicer has more than 10 core modules that are displayed in the top section of the Modules drop down menu.<br />
<br />
===Core Modules===<br />
'''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.<br />
<br />
'''[[Documentation/{{documentation/currentversion}}/Modules/Annotations|Annotations]]: '''Allows the creation and editing of annotations or supplementary information. Currently, rulers and regions of interest (ROIs) are supported. See the [[Documentation/{{documentation/currentversion}}/Modules/Markups|Markups Module]] for fiducials.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/Markups|'''Markups''']]: Allows the creation and editing of markups associated with a scene. Currently, lists of fiducially are supported as markups.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/Data|'''Data''']]: Lists the objects currently in the scene and allows basic operations such as search, rename, delete and move.<br />
<br />
'''DataStore''': Allows users to download and upload data sets.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/DICOM|'''DICOM''']]: Integrates [http://dicom.nema.org DICOM] support from [http://commontk.org CTK] and [http://dicom.offis.de DCMTK].<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/Editor|'''Editor''']]: Allows manual segmentation of volumes.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/Models|'''Models''']]''':''' Loads and adjusts display parameters of models. Allows the user to change the appearance of and organize 3D surface models.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/SceneViews|'''Scene Views''']]: Tool for organizing multiple 'live views' of the data in the scene. The user can create any number of views and control parameters<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/SubjectHierarchy|'''Subject Hierarchy''']]''':''' The SubjectHierarchy module acts as a central data-organizing point in Slicer. Subject hierarchy nodes provide features for the underlying data nodes, including cloning, bulk transforming, bulk show/hide, type-specific features, and basic node operations such as delete or rename.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/Transforms|'''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.<br />
<br />
[[Documentation/{{documentation/currentversion}}/Modules/VolumeRendering|'''Volume Rendering''']]''':''' Provides interactive visualization of 3D image data.<br />
<br />
'''[[Documentation/{{documentation/currentversion}}/Modules/Volumes|Volumes]]: '''Used for changing the appearance of various volume types.<br />
<br />
[[File:Extension Manager.png|thumb|350x350px]]<br />
<br />
==Extensions==<br />
3D Slicer supports plug-ins that are called extensions. An extension could be seen as a delivery package bundling together one or more Slicer modules. After installing an extension, the associated modules will be presented to the user as built-in ones. Extensions can be downloaded from the extension manager to selectively install features that are useful for the end-user.<br />
* For details about downloading extensions, see the [[Documentation/{{documentation/currentversion}}/SlicerApplication/ExtensionsManager|Extension Manager page]].<br />
* Click [[Documentation/{{documentation/currentversion}}/ModuleExtensionListing/Extensions_by_category|here]] for a full list of extensions. The links on the page will provide documentation for each extension.<br />
* See the [[Documentation/{{documentation/currentversion}}/Announcements#Slicer_Extensions Announcements|page]] for descriptions of the latest 3D Slicer extensions.<br />
* Slicer is extensible. If you are a programmer who wants to add functionality to Slicer, click [[Documentation/{{documentation/currentversion}}/Developers/Tutorials/BuildTestPackageDistributeExtensions|here]].<br />
<br />
==Use Cases==<br />
{| class="wikitable" <br />
|-<br />
! Diffusion Tensor Imaging<br />
! Neurosurgical Planning<br />
|-<br />
|[[ File:Slicer4DTI Tutorial.png|thumb|350px|centre]]<br />
|[[File:NeurosurgicalPlanningTutorial.png|thumb|360px|centre]]<br />
|-<br />
|style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Diffusion_Tensor_Imaging_Tutorial|Diffusion Tensor Imaging Tutorial]] course guides through the basics of loading Diffusion Weighted images in Slicer, estimating tensors and generating fiber tracts.<br />
|style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Neurosurgical_Planning_Tutorial|Neurosurgical Planning Tutorial]] course guides through the generation of fiber tracts in the vicinity of a tumor.<br />
<br />
|-<br />
! Visualization of DICOM Images for Radiology Applications<br />
! Quantitative Imaging<br />
|-<br />
|[[File:Slicer4RSNA_2.png|thumb|400px|centre]]<br />
|[[File:QuantitaiveImaging_tutorial.png|thumb|350px|centre]]<br />
|-<br />
|style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_3D_Visualization_of_DICOM_images_for_Radiology_Applications|3D Visualization of DICOM Images for Radiology Applications Tutorial]] guides through 3D data loading and visualization of DICOM images for radiology applications.<br />
| style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Quantitative_Imaging_tutorial|Quantitative Imaging Tutorial]] guides through the use of 3D Slicer for quanitifying small volumetric changes in slow-growing tumors and for calculating Standardized Uptake Value (SUV) from PET/CT data.<br />
|-<br />
! Surgical Navigation<br />
! Radiation Therapy<br />
|-<br />
|[[File:SlicerIGTLogo250x250.png|thumb|300px|centre]]<br />
|[[File:SlicerRTUseCaseImage.png|thumb|354px|centre]]<br />
|-<br />
| style="text-align:center;"|The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_IGT|Slicer IGT tutorials]] are designed for end-users interested in using Slicer for real-time navigated procedures. <br />
| style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#SlicerRT|SlicerRT tutorial]] demonstrates how to perform a radiation therapy research workflow using the SlicerRT extension.<br />
<br />
|-<br />
! scope="col" style="text-align:center;width: 540px" |Image Registration<br />
! scope="col" style="text-align:center;width: 540px" |3D Printing<br />
|-<br />
|[[File:Registration_Slicer4.png|thumb|340px|centre]]<br />
|[[File:3DPrinting_tutorial.png|thumb|390px|centre]]<br />
|-<br />
| style="text-align:center;" |The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_Image_Registration|Image Registration Tutorial]] show how to perform intra- and inter-subject registration within 3D Slicer.<br />
| style="text-align:center;" |<br />
The [[Documentation/{{documentation/currentversion}}/Training#Slicer4_3D_Printing|3D Printing tutorial]] shows how to prepare 3D Slicer Data for 3D printing.<br />
|}</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=61380Documentation/Nightly/Developers/Build Instructions/Prerequisites2019-08-22T21:00:55Z<p>Pieper: /* Common Prerequisites */ add ubuntu 18.04 dependency</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev libxkbcommon-x11-0<br />
<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554/21</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk make -j20<br />
</pre><br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download download Qt 5.10.0 installer from [https://download.qt.io/archive/qt/5.10/5.10.0 here] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components (5.10.x version is not available in the [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe unified installer]). For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 or 2019 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/SlicerApplication/Installation&diff=61311Documentation/Nightly/SlicerApplication/Installation2019-07-24T13:25:52Z<p>Pieper: /* Linux */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/slicerapplication-header}}<br />
<!-- ---------------------------- --><br />
<br />
<br />
=Overview=<br />
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.<br />
<br />
If you run into mysterious problems with your installation you can try deleting the [[Documentation/{{documentation/version}}/SlicerApplication/Settings|Settings Files]]<br />
<br />
== Installation / Uninstallation ==<br />
<br />
===Windows===<br />
*Run the installer<br />
*Run Slicer from the start menu<br />
*Use the uninstaller to remove the application<br />
===Mac===<br />
<br />
*Drag the Slicer application to your Applications folder or other location of your choice.<br />
* You cannot install extensions into the read-only volume so you must copy before installing extensions.<br />
* Delete the <tt>Slicer.app</tt> folder to uninstall<br />
<br />
===Linux===<br />
<br />
* Open the tar.gz archive and copy directory to the location of your choice. Run the Slicer executable.<br />
* Remove the directory to uninstall<br />
<br />
==== Platform Notes ====<br />
The following may be needed on fresh debian or ubuntu<br />
sudo apt-get install libpulse-dev libnss3 libglu1-mesa</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/4.10/SlicerApplication/Installation&diff=61310Documentation/4.10/SlicerApplication/Installation2019-07-24T13:25:04Z<p>Pieper: /* Platform Notes */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
<!-- ---------------------------- --><br />
{{documentation/{{documentation/version}}/slicerapplication-header}}<br />
<!-- ---------------------------- --><br />
<br />
<br />
=Overview=<br />
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.<br />
<br />
If you run into mysterious problems with your installation you can try deleting the [[Documentation/{{documentation/version}}/SlicerApplication/Settings|Settings Files]]<br />
<br />
== Installation / Uninstallation ==<br />
<br />
===Windows===<br />
*Run the installer<br />
*Run Slicer from the start menu<br />
*Use the uninstaller to remove the application<br />
===Mac===<br />
<br />
*Drag the Slicer application to your Applications folder or other location of your choice.<br />
* You cannot install extensions into the read-only volume so you must copy before installing extensions.<br />
* Delete the <tt>Slicer.app</tt> folder to uninstall<br />
<br />
===Linux===<br />
<br />
* Open the tar.gz archive and copy directory to the location of your choice. Run the Slicer executable.<br />
* Remove the directory to uninstall<br />
<br />
==== Platform Notes ====<br />
The following may be needed on fresh debian or ubuntu<br />
sudo apt-get install libpulse-dev libnss3 libglu1-mesa</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61305Documentation/Labs/Slicer5-roadmap2019-07-23T14:29:18Z<p>Pieper: /* Acquisition transform */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well.<br />
<br />
====Model Hierarchies====<br />
<br />
Remove Model Hierarchy feature and make sure that Subject Hierarchy covers all use cases. This will impact ModelMaker, which should be converted to a simpler version that only returns models and not semantics. Need to check extensions, especially SlicerDMRI, for any dependencies on Model Hierarchy.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom'<br />
* Remove DICOM Networking (DIMSE) code https://discourse.slicer.org/t/dicom-retrieve-on-windows-10-there-is-no-service-listening-dicom-communications-no-telnet-connection-to-ports</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61287Documentation/Labs/Slicer5-roadmap2019-07-12T13:42:44Z<p>Pieper: /* Additional proposed changes to be discussed */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom'<br />
* Remove DICOM Networking (DIMSE) code https://discourse.slicer.org/t/dicom-retrieve-on-windows-10-there-is-no-service-listening-dicom-communications-no-telnet-connection-to-ports</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/ScriptRepository&diff=61225Documentation/Nightly/ScriptRepository2019-06-17T12:49:18Z<p>Pieper: /* How to access tags of DICOM images imported into Slicer? For example, to print the first patient's first study's first series' "0020,0032" field: */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
<br />
=Community-contributed modules=<br />
<br />
The examples in this section are [[Documentation/{{documentation/version}}/Developers/Modules#Scripted_Modules| Scripted Modules]] that provide a user interface in the module panel along with specialized implementation logic.<br />
<br />
Usage: save the .py file to a directory, add the directory to the additional module paths in the Slicer application settings (choose in the menu: Edit / Application settings, click Modules, click >> next to Additional module paths, click Add, and choose the .py file's location).<br />
<br />
More information about python scripted modules and more usage examples can be found in the [[Documentation/{{documentation/version}}/Developers/Python_scripting | Python scripting]] wiki page.<br />
<br />
==Filters==<br />
* [https://raw.github.com/pieper/VolumeMasker/master/VolumeMasker.py VolumeMasker.py]: Update a target volume with the results of setting all input volume voxels to 0 except for those that correspond to a selected label value in an input label map (Used for example in the volume rendering in [https://www.youtube.com/watch?v=dfu2gugHLHs this video).<br />
<br />
==DICOM==<br />
* [https://gist.github.com/pieper/6186477 dicom header browser] to easily scroll through dicom files using dcmdump.<br />
* [https://github.com/SlicerRt/SlicerRT/tree/master/BatchProcessing SlicerRT batch processing] to batch convert RT structure sets to labelmap NRRD files.<br />
<br />
==Informatics==<br />
* [https://gist.github.com/lassoan/bf0954d93cacc8cbe27cd4a3ad503f2f MarkupsInfo.py]: Compute the total length between all the points of a markup list.<br />
* [https://github.com/lassoan/SlicerLineProfile/blob/master/LineProfile/LineProfile.py LineProfile.py]: Compute intensity profile in a volume along a line.<br />
<br />
=Community-contributed examples=<br />
<br />
Usage: Copy-paste the shown code lines or linked .py file contents into Python console in Slicer. Or save them to a file and run them using execfile.<br />
<br />
==Capture==<br />
* Capture the full Slicer screen and save it into a file<br />
img = qt.QPixmap.grabWidget(slicer.util.mainWindow()).toImage()<br />
img.save('c:/tmp/test.png')<br />
* Capture all the views save it into a file:<br />
<pre><br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
cap.showViewControllers(False)<br />
cap.captureImageFromView(None,'c:/tmp/test.png')<br />
cap.showViewControllers(True)<br />
</pre><br />
* Capture a single view:<br />
<pre><br />
viewNodeID = 'vtkMRMLViewNode1'<br />
import ScreenCapture<br />
cap = ScreenCapture.ScreenCaptureLogic()<br />
view = cap.viewFromNode(slicer.mrmlScene.GetNodeByID(viewNodeID))<br />
cap.captureImageFromView(view,'c:/tmp/test.png')<br />
</pre><br />
Common values for viewNodeID: vtkMRMLSliceNodeRed, vtkMRMLSliceNodeYellow, vtkMRMLSliceNodeGreen, vtkMRMLViewNode1, vtkMRMLViewNode2. <br />
The ScreenCapture module can also create video animations of rotating views, slice sweeps, etc.<br />
<br />
* 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, ...<br />
<br />
<pre><br />
import ScreenCapture<br />
ScreenCapture.ScreenCaptureLogic().captureSliceSweep(getNode('vtkMRMLSliceNodeRed'), -125.0, 75.0, 30, "c:/tmp", "image_%05d.png")<br />
</pre><br />
<br />
* Capture 3D view into PNG file with transparent background<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderWindow.SetAlphaBitPlanes(1)<br />
wti = vtk.vtkWindowToImageFilter()<br />
wti.SetInputBufferTypeToRGBA()<br />
wti.SetInput(renderWindow)<br />
writer = vtk.vtkPNGWriter()<br />
writer.SetFileName("c:/tmp/screenshot.png")<br />
writer.SetInputConnection(wti.GetOutputPort())<br />
writer.Write()<br />
</pre><br />
<br />
==Launching Slicer==<br />
* How to open an .mrb file with Slicer at the command line?<br />
Slicer.exe --python-code "slicer.util.loadScene( 'f:/2013-08-23-Scene.mrb' )"<br />
* How to run a script in the Slicer environment in batch mode (without showing any graphical user interface)?<br />
Slicer.exe --python-code "doSomething; doSomethingElse; etc." --testing --no-splash --no-main-window<br />
<br />
==Load volume from file==<br />
When loading a volume from file, it is recommended to set returnNode=True to retrieve the loaded volume node.<br />
<pre><br />
[success, loadedVolumeNode] = slicer.util.loadVolume('c:/Users/abc/Documents/MRHead.nrrd', returnNode=True)<br />
</pre><br />
<br />
* Get a MRML node in the scene based on the node name and call methods of that object. For the MRHead sample data:<br />
vol=slicer.util.getNode('MR*')<br />
vol.GetImageData().GetDimensions()<br />
<br />
<br />
==Show volume rendering automatically when a volume is loaded==<br />
<br />
To show volume rendering of a volume automatically when it is loaded, add the lines below to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
<pre><br />
@vtk.calldata_type(vtk.VTK_OBJECT)<br />
def onNodeAdded(caller, event, calldata):<br />
node = calldata<br />
if isinstance(node, slicer.vtkMRMLVolumeNode):<br />
# Call showVolumeRendering using a timer instead of calling it directly<br />
# to allow the volume loading to fully complete.<br />
qt.QTimer.singleShot(0, lambda: showVolumeRendering(node))<br />
<br />
def showVolumeRendering(volumeNode):<br />
print("Show volume rendering of node "+volumeNode.GetName())<br />
volRenLogic = slicer.modules.volumerendering.logic()<br />
displayNode = volRenLogic.CreateDefaultVolumeRenderingNodes(volumeNode)<br />
displayNode.SetVisibility(True)<br />
scalarRange = volumeNode.GetImageData().GetScalarRange()<br />
if scalarRange[1]-scalarRange[0] < 1500:<br />
# small dynamic range, probably MRI<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('MR-Default'))<br />
else:<br />
# larger dynamic range, probably CT<br />
displayNode.GetVolumePropertyNode().Copy(volRenLogic.GetPresetByName('CT-Chest-Contrast-Enhanced'))<br />
<br />
slicer.mrmlScene.AddObserver(slicer.vtkMRMLScene.NodeAddedEvent, onNodeAdded)<br />
</pre><br />
<br />
== Automatically load volumes that are copied into a folder ==<br />
<br />
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.<br />
<br />
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.<br />
<br />
<pre><br />
incomingVolumeFolder = "c:/tmp/incoming"<br />
incomingVolumesProcessed = []<br />
<br />
def checkForNewVolumes():<br />
# Check if there is a new file in the <br />
from os import listdir<br />
from os.path import isfile, join<br />
for f in listdir(incomingVolumeFolder):<br />
if f in incomingVolumesProcessed:<br />
# this is an incoming file, it was already there<br />
continue<br />
filePath = join(incomingVolumeFolder, f)<br />
if not isfile(filePath):<br />
# ignore directories<br />
continue<br />
logging.info("Loading new file: "+f)<br />
incomingVolumesProcessed.append(f)<br />
slicer.util.loadVolume(filePath)<br />
# Check again in 3000ms<br />
qt.QTimer.singleShot(3000, checkForNewVolumes)<br />
<br />
# Start monitoring<br />
checkForNewVolumes()<br />
</pre><br />
<br />
==DICOM==<br />
=== How to 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:===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# Note, fileValue accesses the database of cached top level tags<br />
# (nested tags are not included)<br />
print(db.fileValue(fileList[0],'0020,0032'))<br />
<br />
=== How to access DICOM tags nested in a sequence ===<br />
db=slicer.dicomDatabase<br />
patientList=db.patients()<br />
studyList=db.studiesForPatient(patientList[0])<br />
seriesList=db.seriesForStudy(studyList[0])<br />
fileList=db.filesForSeries(seriesList[0])<br />
# use pydicom to access the full header, which requires<br />
# re-reading the dataset instead of using the database cache<br />
import pydicom<br />
pydicom.dcmread(fileList[0])<br />
ds.CTExposureSequence[0].ExposureModulationType<br />
<br />
=== How to access tag of a volume loaded from DICOM? For example, get the patient position stored in a volume:===<br />
volumeName='2: ENT IMRT'<br />
n=slicer.util.getNode(volumeName)<br />
instUids=n.GetAttribute('DICOM.instanceUIDs').split()<br />
filename=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0018,5100'))<br />
<br />
=== How to access tag of an item in the Subject Hierachy tree? For example, get the content time tag of a structure set:===<br />
rtStructName = '3: RTSTRUCT: PROS'<br />
rtStructNode = slicer.util.getNode(rtStructName)<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
rtStructShItemID = shNode.GetItemByDataNode(rtStructNode)<br />
ctSliceInstanceUids = shNode.GetItemAttribute(rtStructShItemID, 'DICOM.ReferencedInstanceUIDs').split()<br />
filename = slicer.dicomDatabase.fileForInstance(ctSliceInstanceUids[0])<br />
print(slicer.dicomDatabase.fileValue(filename,'0008,0033'))<br />
<br />
=== How to get path and filename of a loaded DICOM volume?===<br />
def pathFromNode(node):<br />
storageNode=node.GetStorageNode()<br />
if storageNode is not None: # loaded via drag-drop<br />
filepath=storageNode.GetFullNameFromFileName()<br />
else: # loaded via DICOM browser<br />
instanceUIDs=node.GetAttribute('DICOM.instanceUIDs').split()<br />
filepath=slicer.dicomDatabase.fileForInstance(instUids[0])<br />
return filepath<br />
<br />
# example:<br />
node=slicer.util.getNode('volume1')<br />
path=self.pathFromNode(node)<br />
print("DICOM path=%s" % path)<br />
<br />
=== How can I convert DICOM to NRRD on the command line?===<br />
<br />
/Applications/Slicer-4.6.2.app/Contents/MacOS/Slicer --no-main-window --python-code "node=slicer.util.loadVolume('/tmp/series/im0.dcm', returnNode=True)[1]; slicer.util.saveNode(node, '/tmp/output.nrrd'); exit()"<br />
<br />
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.<br />
<br />
=== Export a volume to DICOM file format ===<br />
<br />
<pre><br />
volumeNode = getNode('CTChest')<br />
outputFolder = "c:/tmp/dicom-output"<br />
<br />
# Create patient and study and put the volume under the study<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
patientItemID = shNode.CreateSubjectItem(shNode.GetSceneItemID(), "test patient")<br />
studyItemID = shNode.CreateStudyItem(patientItemID, "test study")<br />
volumeShItemID = shNode.GetItemByDataNode(volumeNode)<br />
shNode.SetItemParent(volumeShItemID, studyItemID)<br />
<br />
import DICOMScalarVolumePlugin<br />
exporter = DICOMScalarVolumePlugin.DICOMScalarVolumePluginClass()<br />
exportables = exporter.examineForExport(volumeShItemID)<br />
for exp in exportables:<br />
exp.directory = outputFolder<br />
<br />
exporter.export(exportables)<br />
</pre><br />
<br />
=== Customize table columns in DICOM browser ===<br />
<br />
<pre><br />
# Get browser and database<br />
dicomBrowser = slicer.modules.dicom.widgetRepresentation().self().dicomBrowser<br />
dicomDatabase = dicomBrowser.database() # Need to go this way, do not use slicer.dicomDatabase for this<br />
<br />
# Change column order<br />
dicomDatabase.setWeightForField('Series', 'SeriesDescription', 7)<br />
dicomDatabase.setWeightForField('Studies', 'StudyDescription', 6)<br />
# Change column visibility<br />
dicomDatabase.setVisibilityForField('Patients', 'PatientsBirthDate', False)<br />
# Change column name<br />
dicomDatabase.setDisplayedNameForField('Series', 'DisplayedCount', 'Number of images')<br />
# Customize table manager in DICOM browser<br />
dicomTableManager = dicomBrowser.dicomTableManager()<br />
dicomTableManager.selectionMode = qt.QAbstractItemView.SingleSelection<br />
dicomTableManager.autoSelectSeries = False<br />
</pre><br />
<br />
==Toolbar functions==<br />
* How to turn on slice intersections in the crosshair menu on the toolbar:<br />
<pre><br />
viewNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
for viewNode in viewNodes:<br />
viewNode.SetSliceIntersectionVisibility(1)<br />
</pre><br />
<br />
How to find similar functions? 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.<br />
<br />
==Manipulating objects in the slice viewer==<br />
* How to define/edit a circular region of interest in a slice viewer?<br />
<br />
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.<br />
<br />
<pre><br />
# Update the sphere from the fiducial points<br />
def UpdateSphere(param1, param2): <br />
import math<br />
centerPointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(0,centerPointCoord)<br />
circumferencePointCoord = [0.0, 0.0, 0.0]<br />
markups.GetNthFiducialPosition(1,circumferencePointCoord)<br />
sphere.SetCenter(centerPointCoord)<br />
radius=math.sqrt((centerPointCoord[0]-circumferencePointCoord[0])**2+(centerPointCoord[1]-circumferencePointCoord[1])**2+(centerPointCoord[2]-circumferencePointCoord[2])**2)<br />
sphere.SetRadius(radius)<br />
sphere.SetPhiResolution(30)<br />
sphere.SetThetaResolution(30)<br />
sphere.Update()<br />
<br />
# Get markup node from scene<br />
markups=slicer.util.getNode('F')<br />
sphere = vtk.vtkSphereSource()<br />
UpdateSphere(0,0)<br />
<br />
# Create model node and add to scene<br />
modelsLogic = slicer.modules.models.logic()<br />
model = modelsLogic.AddModel(sphere.GetOutput())<br />
model.GetDisplayNode().SetSliceIntersectionVisibility(True)<br />
model.GetDisplayNode().SetSliceIntersectionThickness(3)<br />
model.GetDisplayNode().SetColor(1,1,0)<br />
<br />
# Call UpdateSphere whenever the fiducials are changed<br />
markups.AddObserver("ModifiedEvent", UpdateSphere, 2)<br />
</pre><br />
<br />
<br />
==Set slice position and orientation from 3 markup fiducials==<br />
<br />
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.<br />
<br />
<pre><br />
# Update plane from fiducial points<br />
def UpdateSlicePlane(param1=None, param2=None):<br />
# Get point positions as numpy array<br />
import numpy as np<br />
nOfFiduciallPoints = markups.GetNumberOfFiducials()<br />
if nOfFiduciallPoints < 3:<br />
return # not enough points<br />
points = np.zeros([3,nOfFiduciallPoints])<br />
for i in range(0, nOfFiduciallPoints):<br />
markups.GetNthFiducialPosition(i, points[:,i])<br />
# Compute plane position and normal<br />
planePosition = points.mean(axis=1)<br />
planeNormal = np.cross(points[:,1] - points[:,0], points[:,2] - points[:,0])<br />
planeX = points[:,1] - points[:,0]<br />
sliceNode.SetSliceToRASByNTP(planeNormal[0], planeNormal[1], planeNormal[2],<br />
planeX[0], planeX[1], planeX[2],<br />
planePosition[0], planePosition[1], planePosition[2], 0)<br />
<br />
# Get markup node from scene<br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
markups = slicer.util.getNode('F')<br />
<br />
# Update slice plane manually<br />
UpdateSlicePlane()<br />
<br />
# Update slice plane automatically whenever points are changed<br />
markupObservation = [markups, markups.AddObserver("ModifiedEvent", UpdateSlicePlane, 2)]<br />
</pre><br />
<br />
To stop automatic updates, run this:<br />
<pre><br />
markupObservation[0].RemoveObserver(markupObservation[1])<br />
</pre><br />
<br />
== Switching to markup fiducial placement mode ==<br />
<br />
To activate a fiducial placement mode, both interaction mode has to be set and a fiducial node has to be selected:<br />
<br />
<pre><br />
interactionNode = slicer.app.applicationLogic().GetInteractionNode()<br />
selectionNode = slicer.app.applicationLogic().GetSelectionNode()<br />
selectionNode.SetReferenceActivePlaceNodeClassName("vtkMRMLMarkupsFiducialNode")<br />
fiducialNode = slicer.vtkMRMLMarkupsFiducialNode()<br />
slicer.mrmlScene.AddNode(fiducialNode)<br />
fiducialNode.CreateDefaultDisplayNodes() <br />
selectionNode.SetActivePlaceNodeID(fiducialNode.GetID())<br />
interactionNode.SetCurrentInteractionMode(interactionNode.Place)<br />
</pre><br />
<br />
Alternatively, ''qSlicerMarkupsPlaceWidget'' widget can be used to initiate markup placement:<br />
<br />
<pre><br />
# Temporary markups node<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
<br />
def placementModeChanged(active):<br />
print("Placement: " +("active" if active else "inactive"))<br />
# You can inspect what is in the markups node here, delete the temporary markup node, etc.<br />
<br />
# Create and set up widget that contains a single "place markup" button. The widget can be placed in the module GUI.<br />
placeWidget = slicer.qSlicerMarkupsPlaceWidget()<br />
placeWidget.setMRMLScene(slicer.mrmlScene)<br />
placeWidget.setCurrentNode(markupsNode)<br />
placeWidget.buttonsVisible=False<br />
placeWidget.placeButton().show()<br />
placeWidget.connect('activeMarkupsFiducialPlaceModeChanged(bool)', placementModeChanged)<br />
placeWidget.show()<br />
</pre><br />
<br />
== Change markup fiducial display properties ==<br />
<br />
Display properties are stored in display node(s) associated with the fiducial node.<br />
<br />
<pre><br />
fiducialNode = getNode('F')<br />
fiducialDisplayNode = fiducialNode.GetDisplayNode()<br />
fiducialDisplayNode.SetVisibility(False) # Hide all points<br />
fiducialDisplayNode.SetVisibility(True) # Show all points<br />
fiducialDisplayNode.SetSelectedColor(1,1,0) # Set color to yellow<br />
fiducialDisplayNode.SetViewNodeIDs(["vtkMRMLSliceNodeRed", "vtkMRMLViewNode1"]) # Only show in red slice view and first 3D view<br />
</pre><br />
<br />
== Get a notification if a markup point position is modified ==<br />
<br />
<pre><br />
def onMarkupsNodeModified(markupsNode, unusedArg2=None, unusedArg3=None):<br />
sliceView = markupsNode.GetAttribute('Markups.MovingInSliceView')<br />
if not sliceView:<br />
print("Markup list was modified")<br />
return<br />
movingMarkupIndex = markupsNode.GetAttribute('Markups.MovingMarkupIndex')<br />
pos = [0,0,0]<br />
markupsNode.GetNthFiducialPosition(int(movingMarkupIndex), pos) <br />
print("Markup {0} was moved in slice view {1} to {2}".format(movingMarkupIndex, sliceView, pos))<br />
<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode")<br />
markupsNode.CreateDefaultDisplayNodes()<br />
markupsNode.AddFiducial(0,0,0)<br />
markupsNode.AddObserver(vtk.vtkCommand.ModifiedEvent, onMarkupsNodeModified)<br />
</pre><br />
<br />
<br />
== Get a notification if a transform is modified ==<br />
<br />
<pre><br />
def onTransformNodeModified(transformNode, unusedArg2=None, unusedArg3=None):<br />
transformMatrix = vtk.vtkMatrix4x4()<br />
transformNode.GetMatrixTransformToWorld(transformMatrix)<br />
print("Position: [{0}, {1}, {2}]".format(transformMatrix.GetElement(0,3), transformMatrix.GetElement(1,3), transformMatrix.GetElement(2,3)))<br />
<br />
transformNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTransformNode")<br />
transformNode.AddObserver(slicer.vtkMRMLTransformNode.TransformModifiedEvent, onTransformNodeModified)<br />
</pre><br />
<br />
== Show a context menu when a markup point is clicked in a slice or 3D view ==<br />
<br />
<pre><br />
<br />
# Example actions to perform<br />
<br />
def action1():<br />
print('Action1 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
def action2():<br />
print('Action2 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
def action3():<br />
print('Action3 on markup '+str(slicer.clickedMarkupIndex))<br />
<br />
# Clicked markup index is saved here to let the action<br />
# know which markup needs to be manipulated.<br />
slicer.clickedMarkupIndex = -1<br />
<br />
# Create a simple menu<br />
<br />
menu = qt.QMenu()<br />
a1 = qt.QAction("Test", slicer.util.mainWindow())<br />
a1.connect('triggered()', action1)<br />
menu.addAction(a1)<br />
a2 = qt.QAction("Action", slicer.util.mainWindow())<br />
a2.connect('triggered()', action1)<br />
menu.addAction(a2)<br />
a3 = qt.QAction("Here", slicer.util.mainWindow())<br />
a3.connect('triggered()', action1)<br />
menu.addAction(a3)<br />
<br />
# Add observer to a markup fiducial list<br />
<br />
@vtk.calldata_type(vtk.VTK_INT)<br />
def markupClickedCallback(caller, eventId, callData):<br />
slicer.clickedMarkupIndex = callData<br />
print('Open menu on markup '+str(slicer.clickedMarkupIndex))<br />
menu.move(qt.QCursor.pos())<br />
menu.show()<br />
<br />
markupsNode = getNode('F')<br />
observerTag = markupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointClickedEvent, markupClickedCallback)<br />
<br />
</pre><br />
<br />
== Write markup positions to JSON file ==<br />
<br />
<pre><br />
markupNode = getNode('F')<br />
outputFileName = 'c:/tmp/test.json'<br />
<br />
# Get markup positions<br />
data = []<br />
for fidIndex in range(markupNode.GetNumberOfFiducials()):<br />
coords=[0,0,0]<br />
markupNode.GetNthFiducialPosition(fidIndex,coords)<br />
data.append({'label': markupNode.GetNthFiducialLabel(), 'position': coords})<br />
<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
== Write annotation ROI to JSON file ==<br />
<br />
<pre><br />
roiNode = getNode('R')<br />
outputFileName = "c:/tmp/test.json"<br />
<br />
# Get annotation ROI data<br />
center = [0,0,0]<br />
radius = [0,0,0]<br />
roiNode.GetControlPointWorldCoordinates(0, center)<br />
roiNode.GetControlPointWorldCoordinates(1, radius)<br />
data = {'center': radius, 'radius': radius}<br />
<br />
# Write to json file<br />
import json<br />
with open(outputFileName, 'w') as outfile:<br />
json.dump(data, outfile)<br />
</pre><br />
<br />
== Show a simple surface mesh as a model node ==<br />
<br />
This example shows how to display a simple surface mesh (a box, created by a VTK source filter) as a model node.<br />
<br />
<pre><br />
# Create and set up polydata source<br />
box = vtk.vtkCubeSource()<br />
box.SetXLength(30)<br />
box.SetYLength(20)<br />
box.SetZLength(15)<br />
box.SetCenter(10,20,5)<br />
<br />
# Create a model node that displays output of the source<br />
boxNode = slicer.modules.models.logic().AddModel(box.GetOutputPort())<br />
<br />
# Adjust display properties<br />
boxNode.GetDisplayNode().SetColor(1,0,0)<br />
boxNode.GetDisplayNode().SetOpacity(0.8)<br />
</pre><br />
<br />
== Add a texture mapped plane to the scene as a model ==<br />
Note that model textures are not exposed in the GUI and are not saved in the scene<br />
<pre><br />
# use dummy image data here<br />
e = vtk.vtkImageEllipsoidSource()<br />
<br />
scene = slicer.mrmlScene<br />
<br />
# Create model node<br />
model = slicer.vtkMRMLModelNode()<br />
model.SetScene(scene)<br />
model.SetName(scene.GenerateUniqueName("2DImageModel"))<br />
<br />
planeSource = vtk.vtkPlaneSource()<br />
model.SetAndObservePolyData(planeSource.GetOutput())<br />
<br />
# Create display node<br />
modelDisplay = slicer.vtkMRMLModelDisplayNode()<br />
modelDisplay.SetColor(1,1,0) # yellow<br />
modelDisplay.SetBackfaceCulling(0)<br />
modelDisplay.SetScene(scene)<br />
scene.AddNode(modelDisplay)<br />
model.SetAndObserveDisplayNodeID(modelDisplay.GetID())<br />
<br />
# Add to scene<br />
modelDisplay.SetAndObserveTextureImageData(e.GetOutput())<br />
scene.AddNode(model) <br />
<br />
<br />
transform = slicer.vtkMRMLLinearTransformNode()<br />
scene.AddNode(transform) <br />
model.SetAndObserveTransformNodeID(transform.GetID())<br />
<br />
vTransform = vtk.vtkTransform()<br />
vTransform.Scale(50,50,50)<br />
vTransform.RotateX(30)<br />
transform.SetAndObserveMatrixTransformToParent(vTransform.GetMatrix())<br />
</pre><br />
<br />
== Get scalar values at surface of a model ==<br />
<br />
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.<br />
<br />
<pre><br />
modelNode = getNode('sphere')<br />
modelPointValues = modelNode.GetPolyData().GetPointData().GetArray("Normals")<br />
markupsNode = getNode('F')<br />
<br />
if not markupsNode:<br />
markupsNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLMarkupsFiducialNode","F")<br />
<br />
pointsLocator = vtk.vtkPointLocator() # could try using vtk.vtkStaticPointLocator() if need to optimize<br />
pointsLocator.SetDataSet(modelNode.GetPolyData())<br />
pointsLocator.BuildLocator()<br />
<br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
if markupsNode.GetNumberOfFiducials() == 0:<br />
markupsNode.AddFiducial(*ras)<br />
else:<br />
markupsNode.SetNthFiducialPosition(0,*ras)<br />
closestPointId = pointsLocator.FindClosestPoint(ras)<br />
closestPointValue = modelPointValues.GetTuple(closestPointId)<br />
print("RAS = " + repr(ras) + " value = " + repr(closestPointValue))<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
observationId = crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
<br />
# To stop printing of values run this:<br />
# crosshairNode.RemoveObserver(observationId)<br />
</pre><br />
<br />
== Export entire scene as VRML ==<br />
<br />
Save all surface meshes displayed in the scene (models, markups, etc). Solid colors and coloring by scalar is preserved. Textures are not supported.<br />
<br />
<pre><br />
exporter = vtk.vtkVRMLExporter()<br />
exporter.SetRenderWindow(slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow())<br />
exporter.SetFileName('C:/tmp/something.wrl')<br />
exporter.Write()<br />
</pre><br />
<br />
== Export model to Blender, including color by scalar ==<br />
<br />
<pre><br />
modelNode = getNode("Model")<br />
plyFilePath = "c:/tmp/model.ply"<br />
<br />
modelDisplayNode = modelNode.GetDisplayNode()<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputConnection(modelDisplayNode.GetOutputPolyDataConnection())<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputConnection(triangles.GetOutputPort())<br />
lut = vtk.vtkLookupTable()<br />
lut.DeepCopy(modelDisplayNode.GetColorNode().GetLookupTable())<br />
lut.SetRange(modelDisplayNode.GetScalarRange())<br />
plyWriter.SetLookupTable(lut)<br />
plyWriter.SetArrayName(modelDisplayNode.GetActiveScalarName())<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
== Export a tract (FiberBundle) to Blender, including color ==<br />
<div id="Export_a_fiber_tracts_to_Blender.2C_including_color"></div><br />
Note: an interactive version of this script is now included in the [http://dmri.slicer.org/ SlicerDMRI extension] ([https://github.com/SlicerDMRI/SlicerDMRI/tree/master/Modules/Scripted/TractographyExportPLY module code]). <br />
After installing SlicerDMRI, go to ''Modules -> Diffusion -> Import and Export -> Export tractography to PLY (mesh)''.<br />
<br />
The example below shows how to export a tractography "FiberBundleNode" to a PLY file:<br />
<br />
<pre><br />
lineDisplayNode = getNode("*LineDisplay*")<br />
plyFilePath = "/tmp/fibers.ply"<br />
<br />
tuber = vtk.vtkTubeFilter()<br />
tuber.SetInputData(lineDisplayNode.GetOutputPolyData())<br />
tuber.Update()<br />
tubes = tuber.GetOutputDataObject(0)<br />
scalars = tubes.GetPointData().GetArray(0)<br />
scalars.SetName("scalars")<br />
<br />
triangles = vtk.vtkTriangleFilter()<br />
triangles.SetInputData(tubes)<br />
triangles.Update()<br />
<br />
colorNode = lineDisplayNode.GetColorNode()<br />
lookupTable = vtk.vtkLookupTable()<br />
lookupTable.DeepCopy(colorNode.GetLookupTable())<br />
lookupTable.SetTableRange(0,1)<br />
<br />
plyWriter = vtk.vtkPLYWriter()<br />
plyWriter.SetInputData(triangles.GetOutput())<br />
plyWriter.SetLookupTable(lookupTable)<br />
plyWriter.SetArrayName("scalars")<br />
<br />
plyWriter.SetFileName(plyFilePath)<br />
plyWriter.Write()<br />
</pre><br />
<br />
== Iterate over tract (FiberBundle) streamline points ==<br />
<br />
This example shows how to access the points in each line of a FiberBundle as a numpy array (view).<br />
<br />
<pre><br />
from vtk.util.numpy_support import vtk_to_numpy<br />
<br />
fb = getNode("FiberBundle_F") # <- fill in node ID here<br />
<br />
# get point data as 1d array<br />
points = slicer.util.arrayFromModelPoints(fb)<br />
<br />
# get line cell ids as 1d array<br />
line_ids = vtk_to_numpy(fb.GetPolyData().GetLines().GetData())<br />
<br />
# VTK cell ids are stored as<br />
# [ N0 c0_id0 ... c0_id0<br />
# N1 c1_id0 ... c1_idN1 ]<br />
# so we need to<br />
# - read point count for each line (cell)<br />
# - grab the ids in that range from `line_ids` array defined above<br />
# - index the `points` array by those ids<br />
cur_idx = 1<br />
for _ in range(pd.GetLines().GetNumberOfCells()):<br />
# - read point count for this line (cell)<br />
count = lines[cur_idx - 1]<br />
<br />
# - grab the ids in that range from `lines`<br />
index_array = line_ids[ cur_idx : cur_idx + count]<br />
# update to the next range <br />
cur_idx += count + 1<br />
<br />
# - index the point array by those ids<br />
line_points = points[index_array]<br />
<br />
# do work here<br />
</pre><br />
<br />
== Clone a node ==<br />
<br />
This example shows how to make a copy of any node that appears in Subject Hierarchy (in Data module).<br />
<br />
<pre><br />
# Get a node from SampleData that we will clone<br />
import SampleData<br />
nodeToClone = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Clone the node<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
itemIDToClone = shNode.GetItemByDataNode(nodeToClone)<br />
clonedItemID = slicer.modules.subjecthierarchy.logic().CloneSubjectHierarchyItem(shNode, itemIDToClone)<br />
clonedNode = shNode.GetItemDataNode(clonedItemID)<br />
</pre><br />
<br />
== Clone a volume ==<br />
This example shows how to clone the MRHead sample volume, including its pixel data and display settings.<br />
<pre><br />
sourceVolumeNode = slicer.util.getNode('MRHead')<br />
volumesLogic = slicer.modules.volumes.logic()<br />
clonedVolumeNode = volumesLogic.CloneVolume(slicer.mrmlScene, sourceVolumeNode, 'Cloned volume')<br />
</pre><br />
<br />
== Create a new volume ==<br />
This example shows how to create a new empty volume.<br />
<pre><br />
nodeName = "MyNewVolume"<br />
imageSize = [512, 512, 512]<br />
voxelType=vtk.VTK_UNSIGNED_CHAR<br />
imageOrigin = [0.0, 0.0, 0.0]<br />
imageSpacing = [1.0, 1.0, 1.0]<br />
imageDirections = [[1,0,0], [0,1,0], [0,0,1]]<br />
fillVoxelValue = 0<br />
<br />
# Create an empty image volume, filled with fillVoxelValue<br />
imageData = vtk.vtkImageData()<br />
imageData.SetDimensions(imageSize)<br />
imageData.AllocateScalars(voxelType, 1)<br />
thresholder = vtk.vtkImageThreshold()<br />
thresholder.SetInputData(imageData)<br />
thresholder.SetInValue(fillVoxelValue)<br />
thresholder.SetOutValue(fillVoxelValue)<br />
thresholder.Update()<br />
# Create volume node<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", nodeName)<br />
volumeNode.SetOrigin(imageOrigin)<br />
volumeNode.SetSpacing(imageSpacing)<br />
volumeNode.SetIJKToRASDirections(imageDirections)<br />
volumeNode.SetAndObserveImageData(thresholder.GetOutput())<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
</pre><br />
<br />
== Modify voxels in a volume ==<br />
<br />
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:<br />
<br />
<pre><br />
nodeName = 'MRHead'<br />
thresholdValue = 100<br />
voxelArray = array(nodeName) # get voxels as numpy array<br />
voxelArray[voxelArray < thresholdValue] = 0 # modify voxel values<br />
getNode(nodeName).Modified() # at the end of all processing, notify Slicer that the image modification is completed<br />
</pre><br />
<br />
This example shows how to change voxels values of the MRHead sample volume.<br />
The values will be computed by function f(r,a,s,) = (r-10)*(r-10)+(a+15)*(a+15)+s*s.<br />
<pre><br />
volumeNode=slicer.util.getNode('MRHead')<br />
ijkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(ijkToRas)<br />
imageData=volumeNode.GetImageData()<br />
extent = imageData.GetExtent()<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
position_Ijk=[i, j, k, 1]<br />
position_Ras=ijkToRas.MultiplyPoint(position_Ijk)<br />
r=position_Ras[0]<br />
a=position_Ras[1]<br />
s=position_Ras[2] <br />
functionValue=(r-10)*(r-10)+(a+15)*(a+15)+s*s<br />
imageData.SetScalarComponentFromDouble(i,j,k,0,functionValue)<br />
imageData.SetScalarComponentFromFloat(distortionVectorPosition_Ijk[0], distortionVectorPosition_Ijk[1], distortionVectorPosition_Ijk[2], 0, fillValue)<br />
imageData.Modified()<br />
</pre><br />
<br />
== Get volume voxel coordinates from markup fiducial RAS coordinates ==<br />
<br />
This example shows how to get voxel coordinate of a volume corresponding to a markup fiducial point position.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
markupsIndex = 0<br />
<br />
# Get point coordinate in RAS<br />
point_Ras = [0, 0, 0, 1]<br />
markupsNode.GetNthFiducialWorldCoordinates(markupsIndex, point_Ras)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformRasToVolumeRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(None, volumeNode.GetParentTransformNode(), transformRasToVolumeRas)<br />
point_VolumeRas = transformRasToVolumeRas.TransformPoint(point_Ras[0:3])<br />
<br />
# Get voxel coordinates from physical coordinates<br />
volumeRasToIjk = vtk.vtkMatrix4x4()<br />
volumeNode.GetRASToIJKMatrix(volumeRasToIjk)<br />
point_Ijk = [0, 0, 0, 1]<br />
volumeRasToIjk.MultiplyPoint(np.append(point_VolumeRas,1.0), point_Ijk)<br />
point_Ijk = [ int(round(c)) for c in point_Ijk[0:3] ]<br />
<br />
# Print output<br />
print(point_Ijk)<br />
</pre><br />
<br />
== Get markup fiducial RAS coordinates from volume voxel coordinates ==<br />
<br />
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.<br />
<br />
<pre><br />
# Inputs<br />
volumeNode = getNode('MRHead')<br />
markupsNode = getNode('F')<br />
<br />
# Get voxel position in IJK coordinate system<br />
import numpy as np<br />
volumeArray = slicer.util.arrayFromVolume(volumeNode)<br />
# Get position of highest voxel value<br />
point_Kji = np.where(volumeArray == volumeArray.max())<br />
point_Ijk = [point_Kji[2][0], point_Kji[1][0], point_Kji[0][0]]<br />
<br />
# Get physical coordinates from voxel coordinates<br />
volumeIjkToRas = vtk.vtkMatrix4x4()<br />
volumeNode.GetIJKToRASMatrix(volumeIjkToRas)<br />
point_VolumeRas = [0, 0, 0, 1]<br />
volumeIjkToRas.MultiplyPoint(np.append(point_Ijk,1.0), point_VolumeRas)<br />
<br />
# If volume node is transformed, apply that transform to get volume's RAS coordinates<br />
transformVolumeRasToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(volumeNode.GetParentTransformNode(), None, transformVolumeRasToRas)<br />
point_Ras = transformVolumeRasToRas.TransformPoint(point_VolumeRas[0:3])<br />
<br />
# Add a markup at the computed position and print its coordinates<br />
markupsNode.AddFiducial(point_Ras[0], point_Ras[1], point_Ras[2], "max")<br />
print(point_Ras)<br />
</pre><br />
<br />
== Get the values of all voxels for a label value ==<br />
<br />
If you have a background image called ‘Volume’ and a mask called ‘Volume-label’ created with the Editor you could do something like this:<br />
<br />
<pre><br />
<br />
import numpy<br />
volume = array(‘Volume’)<br />
label = array(‘Volume-label’)<br />
points = numpy.where( label == 1 ) # or use another label number depending on what you segmented<br />
values = volume[points] # this will be a list of the label values<br />
values.mean() # should match the mean value of LabelStatistics calculation as a double-check<br />
numpy.savetxt(‘values.txt’, values)<br />
</pre><br />
<br />
== Access values in a DTI tensor volume ==<br />
This example shows how to access individual tensors at the voxel level.<br />
<br />
First load your DWI volume and estimate tensors to produce a DTI volume called ‘Output DTI Volume’<br />
<br />
Then open the python window: View->Python interactor<br />
<br />
Use this command to access tensors through numpy:<br />
<br />
<pre><br />
tensors = array('Output DTI Volume')<br />
</pre><br />
<br />
Type the following code into the Python window to access all tensor components using vtk commands:<br />
<br />
<pre><br />
volumeNode=slicer.util.getNode('Output DTI Volume')<br />
imageData=volumeNode.GetImageData()<br />
tensors = imageData.GetPointData().GetTensors()<br />
extent = imageData.GetExtent()<br />
idx = 0<br />
for k in range(extent[4], extent[5]+1):<br />
for j in range(extent[2], extent[3]+1):<br />
for i in range(extent[0], extent[1]+1):<br />
tensors.GetTuple9(idx)<br />
idx += 1<br />
</pre><br />
<br />
== Change window/level (brightness/contrast) or colormap of a volume ==<br />
This example shows how to change window/level of the MRHead sample volume.<br />
<pre><br />
volumeNode = getNode('MRHead')<br />
displayNode = volumeNode.GetDisplayNode()<br />
displayNode.AutoWindowLevelOff()<br />
displayNode.SetWindow(50)<br />
displayNode.SetLevel(100)<br />
</pre><br />
<br />
Change color mapping from grayscale to rainbow:<br />
<pre><br />
displayNode.SetAndObserveColorNodeID('vtkMRMLColorTableNodeRainbow')<br />
</pre><br />
<br />
== Make mouse left-click and drag on the image adjust window/level ==<br />
<br />
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:<br />
<br />
<pre><br />
slicer.app.applicationLogic().GetInteractionNode().SetCurrentInteractionMode(slicer.vtkMRMLInteractionNode.AdjustWindowLevel)<br />
</pre><br />
<br />
== Create custom color table ==<br />
This example shows how to create a new color table, for example with inverted color range from the default Ocean color table.<br />
<pre><br />
invertedocean = slicer.vtkMRMLColorTableNode()<br />
invertedocean.SetTypeToUser()<br />
invertedocean.SetNumberOfColors(256)<br />
invertedocean.SetName("InvertedOcean")<br />
<br />
for i in range(0,255):<br />
invertedocean.SetColor(i, 0.0, 1 - (i+1e-16)/255.0, 1.0, 1.0)<br />
<br />
slicer.mrmlScene.AddNode(invertedocean)<br />
</pre><br />
<br />
== Manipulate a Slice View ==<br />
<br />
=== Change slice offset ===<br />
<br />
Equivalent to moving the slider in slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
red = layoutManager.sliceWidget('Red')<br />
redLogic = red.sliceLogic()<br />
# Print current slice offset position<br />
print(redLogic.GetSliceOffset())<br />
# Change slice position<br />
redLogic.SetSliceOffset(20)<br />
</pre><br />
<br />
=== Change slice orientation ===<br />
<br />
Get 'Red' slice node and rotate around X and Y axes.<br />
<br />
<pre><br />
sliceNode = slicer.app.layoutManager().sliceWidget('Red').mrmlSliceNode()<br />
sliceToRas = sliceNode.GetSliceToRAS()<br />
transform=vtk.vtkTransform()<br />
transform.SetMatrix(SliceToRAS)<br />
transform.RotateX(20)<br />
transform.RotateY(15)<br />
sliceToRas.DeepCopy(transform.GetMatrix())<br />
sliceNode.UpdateMatrices()<br />
</pre><br />
<br />
=== Show slice views in 3D window ===<br />
<br />
Equivalent to clicking 'eye' icon in the slice view controller.<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
controller = layoutManager.sliceWidget(sliceViewName).sliceController()<br />
controller.setSliceVisible(True)<br />
</pre><br />
<br />
=== Reset field of view to show background volume maximized ===<br />
<br />
Equivalent to click small rectangle button ("Adjust the slice viewer's field of view...") in the slice view controller.<br />
<br />
<pre><br />
slicer.util.resetSliceViews()<br />
</pre><br />
<br />
=== Rotate slice views to volume plane ===<br />
<br />
Aligns slice views to volume axes, shows original image acquisition planes in slice views.<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('MRHead')<br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
layoutManager.sliceWidget(sliceViewName).mrmlSliceNode().RotateToVolumePlane(volumeNode)<br />
</pre><br />
<br />
=== Iterate over current visible slice views, and set foreground and background images ===<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(background=mrVolume, foreground=ctVolume)<br />
</pre><br />
<br />
Internally, this method performs something like this:<br />
<br />
<pre><br />
for sliceViewName in layoutManager.sliceViewNames():<br />
sliceWidget = layoutManager.sliceWidget(sliceViewName)<br />
# setup background volume<br />
compositeNode.SetBackgroundVolumeID(mrVolume.GetID())<br />
# setup foreground volume<br />
compositeNode.SetForegroundVolumeID(ctVolume.GetID())<br />
# change opacity<br />
compositeNode.SetForegroundOpacity(0.3)<br />
</pre><br />
<br />
== Synchronize zoom factor between slice views ==<br />
<br />
<pre><br />
slicer.sliceNodes = [slicer.app.layoutManager().sliceWidget(viewName).mrmlSliceNode()<br />
for viewName in slicer.app.layoutManager().sliceViewNames()]<br />
<br />
slicer.updatingSliceNodes = False<br />
<br />
def sliceModified(caller, event):<br />
if slicer.updatingSliceNodes:<br />
# prevent infinite loop of slice node updates triggering slice node updates<br />
return<br />
slicer.updatingSliceNodes = True<br />
fov = caller.GetFieldOfView()<br />
for sliceNode in slicer.sliceNodes:<br />
if sliceNode != caller:<br />
sliceNode.SetFieldOfView(*fov)<br />
slicer.updatingSliceNodes = False<br />
<br />
for sliceNode in slicer.sliceNodes:<br />
sliceNode.AddObserver(vtk.vtkCommand.ModifiedEvent, sliceModified)<br />
</pre><br />
<br />
== Show a volume in slice views ==<br />
<br />
Recommended:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
slicer.util.setSliceViewerLayers(background=volumeNode)<br />
</pre><br />
<br />
or<br />
<br />
Show volume in all visible views where volume selection propagation is enabled:<br />
<br />
<pre><br />
volumeNode = slicer.util.getNode('YourVolumeNode')<br />
applicationLogic = slicer.app.applicationLogic()<br />
selectionNode = applicationLogic.GetSelectionNode()<br />
selectionNode.SetSecondaryVolumeID(volumeNode.GetID())<br />
applicationLogic.PropagateForegroundVolumeSelection(0) <br />
</pre><br />
<br />
or<br />
<br />
Show volume in selected views:<br />
<br />
<pre><br />
n = slicer.util.getNode('YourVolumeNode')<br />
for color in ['Red', 'Yellow', 'Green']:<br />
slicer.app.layoutManager().sliceWidget(color).sliceLogic().GetSliceCompositeNode().SetForegroundVolumeID(n.GetID())<br />
</pre><br />
<br />
== Change opacity of foreground volume in slice views ==<br />
<br />
<pre><br />
slicer.util.setSliceViewerLayers(foregroundOpacity=0.4)<br />
</pre><br />
<br />
or<br />
<br />
Change opacity in a selected view<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
sliceLogic = lm.sliceWidget('Red').sliceLogic()<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
compositeNode.SetForegroundOpacity(0.4)<br />
</pre><br />
<br />
== Fit slice plane to markup fiducials ==<br />
<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID("vtkMRMLSliceNodeRed")<br />
markupsNode = slicer.mrmlScene.GetFirstNodeByName("F")<br />
# Get markup point positions as numpy arrays<br />
import numpy as np<br />
p1 = np.array([0,0,0])<br />
p2 = np.array([0,0,0])<br />
p3 = np.array([0,0,0])<br />
markupsNode.GetNthFiducialPosition(0, p1)<br />
markupsNode.GetNthFiducialPosition(1, p2)<br />
markupsNode.GetNthFiducialPosition(2, p3)<br />
# Get plane axis directions<br />
n = np.cross(p2-p1, p2-p3) # plane normal direction<br />
n = n/np.linalg.norm(n)<br />
t = np.cross([0, 0, 1], n) # plane transverse direction<br />
t = t/np.linalg.norm(t)<br />
# Set slice plane orientation and position<br />
sliceNode.SetSliceToRASByNTP(n[0], n[1], n[2], t[0], t[1], t[2], p1[0], p1[1], p1[2], 0)<br />
</pre><br />
<br />
== Save a series of images from a Slice View ==<br />
<br />
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')"<br />
<br />
<pre><br />
layoutName = 'Green'<br />
imagePathPattern = '/tmp/image-%03d.png'<br />
steps = 10<br />
<br />
widget = slicer.app.layoutManager().sliceWidget(layoutName)<br />
view = widget.sliceView()<br />
logic = widget.sliceLogic()<br />
bounds = [0,]*6<br />
logic.GetSliceBounds(bounds)<br />
<br />
for step in range(steps):<br />
offset = bounds[4] + step/(1.*steps) * (bounds[5]-bounds[4])<br />
logic.SetSliceOffset(offset)<br />
view.forceRender()<br />
image = qt.QPixmap.grabWidget(view).toImage()<br />
image.save(imagePathPattern % step)<br />
</pre><br />
<br />
== Save the scene into a new directory ==<br />
<br />
<pre><br />
# Create a new directory where the scene will be saved into<br />
import time<br />
sceneSaveDirectory = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S")<br />
if not os.access(sceneSaveDirectory, os.F_OK):<br />
os.makedirs(sceneSaveDirectory)<br />
<br />
# Save the scene<br />
if slicer.app.applicationLogic().SaveSceneToSlicerDataBundleDirectory(sceneSaveDirectory, None):<br />
logging.info("Scene saved to: {0}".format(sceneSaveDirectory))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
== Save the scene into a single MRB file ==<br />
<pre><br />
# Generate file name<br />
import time<br />
sceneSaveFilename = slicer.app.temporaryPath + "/saved-scene-" + time.strftime("%Y%m%d-%H%M%S") + ".mrb"<br />
<br />
# Save scene<br />
if slicer.util.saveScene(sceneSaveFilename):<br />
logging.info("Scene saved to: {0}".format(sceneSaveFilename))<br />
else:<br />
logging.error("Scene saving failed") <br />
</pre><br />
<br />
== Save a node to file ==<br />
<br />
Save a transform node to file (should work with any other node type, if file extension is set to a supported one):<br />
<br />
<pre><br />
myNode = getNode("LinearTransform_3")<br />
<br />
myStorageNode = myNode.CreateDefaultStorageNode()<br />
myStorageNode.SetFileName("c:/tmp/something.tfm")<br />
myStorageNode.WriteData(myNode)<br />
</pre><br />
<br />
== Center the 3D View on the Scene ==<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.resetFocalPoint()<br />
</pre><br />
<br />
==Rotate the 3D View==<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
threeDWidget = layoutManager.threeDWidget(0)<br />
threeDView = threeDWidget.threeDView()<br />
threeDView.yaw()<br />
</pre><br />
<br />
== Display text in a 3D view or slice view ==<br />
<br />
The easiest way to show information overlaid on a viewer is to use corner annotations.<br />
<br />
<pre><br />
view=slicer.app.layoutManager().threeDWidget(0).threeDView()<br />
# Set text to "Something"<br />
view.cornerAnnotation().SetText(vtk.vtkCornerAnnotation.UpperRight,"Something")<br />
# Set color to red<br />
view.cornerAnnotation().GetTextProperty().SetColor(1,0,0)<br />
# Update the view<br />
view.forceRender()<br />
</pre><br />
<br />
== Hide slice view annotations (DataProbe) ==<br />
<br />
<pre><br />
# Disable slice annotations immediately<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.sliceViewAnnotationsEnabled=False<br />
slicer.modules.DataProbeInstance.infoWidget.sliceAnnotations.updateSliceViewFromGUI()<br />
# Disable slice annotations persistently (after Slicer restarts)<br />
settings = qt.QSettings()<br />
settings.setValue('DataProbe/sliceViewAnnotations.enabled', 0)<br />
</pre><br />
<br />
== Turning off interpolation ==<br />
<br />
You can turn off interpolation for newly loaded volumes with this script from Steve Pieper.<br />
<br />
<pre><br />
def NoInterpolate(caller,event):<br />
for node in slicer.util.getNodes('*').values():<br />
if node.IsA('vtkMRMLScalarVolumeDisplayNode'):<br />
node.SetInterpolate(0)<br />
<br />
slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeAddedEvent, NoInterpolate)<br />
</pre><br />
<br />
The below link explains how to put this in your startup script.<br />
<br />
http://www.na-mic.org/Wiki/index.php/AHM2012-Slicer-Python#Refining_the_code_and_UI_with_slicerrc<br />
<br />
<br />
== Customize viewer layout ==<br />
<br />
Show a custom layout of a 3D view on top of the red slice view:<br />
<br />
<pre><br />
customLayout = """<br />
<layout type="vertical" split="true"><br />
<item><br />
<view class="vtkMRMLViewNode" singletontag="1"><br />
<property name="viewlabel" action="default">1</property><br />
</view><br />
</item><br />
<item><br />
<view class="vtkMRMLSliceNode" singletontag="Red"><br />
<property name="orientation" action="default">Axial</property><br />
<property name="viewlabel" action="default">R</property><br />
<property name="viewcolor" action="default">#F34A33</property><br />
</view><br />
</item><br />
</layout><br />
"""<br />
<br />
# Built-in layout IDs are all below 100, so you can choose any large random number<br />
# for your custom layout ID.<br />
customLayoutId=501<br />
<br />
layoutManager = slicer.app.layoutManager()<br />
layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId, customLayout) <br />
<br />
# Switch to the new custom layout <br />
layoutManager.setLayout(customLayoutId)<br />
</pre><br />
<br />
See description of standard layouts (that can be used as examples) here:<br />
https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Logic/vtkMRMLLayoutLogic.cxx<br />
<br />
== Customize keyboard shortcuts ==<br />
<br />
Keyboard shortcuts can be specified for activating any Slicer feature by adding a couple of lines to your <br />
[[Documentation/{{documentation/version}}/Developers/Python_scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F|.slicerrc file]].<br />
<br />
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.<br />
<br />
<pre><br />
shortcuts = [<br />
('Ctrl+b', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpRedSliceView)),<br />
('Ctrl+n', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpYellowSliceView)),<br />
('Ctrl+m', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutOneUpGreenSliceView)),<br />
('Ctrl+,', lambda: slicer.app.layoutManager().setLayout(slicer.vtkMRMLLayoutNode.SlicerLayoutFourUpView))<br />
]<br />
<br />
for (shortcutKey, callback) in shortcuts:<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence(shortcutKey))<br />
shortcut.connect( 'activated()', callback)<br />
</pre><br />
<br />
== Disable certain user interactions in slice views ==<br />
<br />
For example, disable slice browsing using mouse wheel and keyboard shortcuts in the red slice viewer:<br />
<br />
<pre><br />
interactorStyle = slicer.app.layoutManager().sliceWidget('Red').sliceView().sliceViewInteractorStyle()<br />
interactorStyle.SetActionEnabled(interactorStyle.BrowseSlice, False)<br />
</pre><br />
<br />
Hide all slice view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
lm.sliceWidget(sliceViewName).sliceController().setVisible(False)<br />
</pre><br />
<br />
Hide all 3D view controllers:<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for viewIndex in range(slicer.app.layoutManager().threeDViewCount):<br />
lm.threeDWidget(0).threeDController().setVisible(False)<br />
</pre><br />
<br />
== Change default slice view orientation ==<br />
<br />
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 [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Axial slice axes:<br />
# 1 0 0<br />
# 0 1 0<br />
# 0 0 1<br />
axialSliceToRas=vtk.vtkMatrix3x3()<br />
<br />
# Coronal slice axes:<br />
# 1 0 0 <br />
# 0 0 -1<br />
# 0 1 0<br />
coronalSliceToRas=vtk.vtkMatrix3x3()<br />
coronalSliceToRas.SetElement(1,1, 0)<br />
coronalSliceToRas.SetElement(1,2, -1)<br />
coronalSliceToRas.SetElement(2,1, 1)<br />
coronalSliceToRas.SetElement(2,2, 0)<br />
<br />
# Replace orientation presets in all existing slice nodes and in the default slice node<br />
sliceNodes = slicer.util.getNodesByClass('vtkMRMLSliceNode')<br />
sliceNodes.append(slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceNode'))<br />
for sliceNode in sliceNodes:<br />
orientationPresetName = sliceNode.GetOrientation()<br />
sliceNode.RemoveSliceOrientationPreset("Axial")<br />
sliceNode.AddSliceOrientationPreset("Axial", axialSliceToRas)<br />
sliceNode.RemoveSliceOrientationPreset("Coronal")<br />
sliceNode.AddSliceOrientationPreset("Coronal", coronalSliceToRas)<br />
sliceNode.SetOrientation(orientationPresetName)<br />
</pre><br />
<br />
<br />
== Set all slice views linked by default ==<br />
<br />
You can make slice views linked by default (when application starts or the scene is cleared) by copy-pasting the script below to your [[Documentation/{{documentation/version}}/Developers/FAQ/Python_Scripting#How_to_systematically_execute_custom_python_code_at_startup_.3F| .slicerrc.py file]].<br />
<br />
<pre><br />
# Set linked slice views in all existing slice composite nodes and in the default node<br />
sliceCompositeNodes = slicer.util.getNodesByClass('vtkMRMLSliceCompositeNode')<br />
defaultSliceCompositeNode = slicer.mrmlScene.GetDefaultNodeByClass('vtkMRMLSliceCompositeNode')<br />
if not defaultSliceCompositeNode:<br />
defaultSliceCompositeNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceCompositeNode')<br />
slicer.mrmlScene.AddDefaultNode(defaultSliceCompositeNode)<br />
sliceCompositeNodes.append(defaultSliceCompositeNode)<br />
for sliceCompositeNode in sliceCompositeNodes:<br />
sliceCompositeNode.SetLinkedControl(True)<br />
</pre><br />
<br />
== Set up custom units in slice view ruler ==<br />
<br />
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):<br />
<br />
<pre><br />
lm = slicer.app.layoutManager()<br />
for sliceViewName in lm.sliceViewNames():<br />
sliceView = lm.sliceWidget(sliceViewName).sliceView()<br />
displayableManagerCollection = vtk.vtkCollection()<br />
sliceView.getDisplayableManagers(displayableManagerCollection)<br />
for dmIndex in range(displayableManagerCollection.GetNumberOfItems()):<br />
displayableManager = displayableManagerCollection.GetItemAsObject(dmIndex)<br />
if not displayableManager.IsA("vtkMRMLRulerDisplayableManager"):<br />
continue<br />
displayableManager.RemoveAllRulerScalePresets()<br />
displayableManager.AddRulerScalePreset( 0.001, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.010, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.100, 5, 2, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 0.500, 5, 1, "nm", 1000.0)<br />
displayableManager.AddRulerScalePreset( 1.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 5.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 10.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 50.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 100.0, 5, 2, "um", 1.0)<br />
displayableManager.AddRulerScalePreset( 500.0, 5, 1, "um", 1.0)<br />
displayableManager.AddRulerScalePreset(1000.0, 5, 2, "mm", 0.001)<br />
</pre><br />
<br />
== Show a slice view outside the view layout ==<br />
<br />
<pre><br />
layoutName = "TestSlice"<br />
layoutLabel = "TS"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML nodes<br />
viewNode = slicer.vtkMRMLSliceNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
sliceCompositeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSliceCompositeNode")<br />
sliceCompositeNode.SetLayoutName(layoutName)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLSliceWidget()<br />
viewWidget.sliceViewName = layoutName<br />
viewWidget.sliceViewLabel = layoutLabel<br />
c = viewNode.GetLayoutColor()<br />
viewWidget.sliceViewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLSliceNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
== Show a 3D view outside the view layout ==<br />
<br />
<pre><br />
layoutName = "Test3DView"<br />
layoutLabel = "T3"<br />
# ownerNode manages this view instead of the layout manager (it can be any node in the scene)<br />
viewOwnerNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode")<br />
<br />
# Create MRML node<br />
viewNode = slicer.vtkMRMLViewNode()<br />
viewNode.SetName(layoutName)<br />
viewNode.SetLayoutName(layoutName)<br />
viewNode.SetLayoutLabel(layoutLabel)<br />
viewNode.SetLayoutColor(1, 1, 0)<br />
viewNode.SetAndObserveParentLayoutNodeID(viewOwnerNode.GetID())<br />
viewNode = slicer.mrmlScene.AddNode(viewNode)<br />
<br />
# Create widget<br />
viewWidget = slicer.qMRMLThreeDWidget()<br />
viewWidget.viewLabel = layoutLabel<br />
viewWidget.viewColor = qt.QColor.fromRgbF(c[0],c[1],c[2])<br />
viewWidget.setMRMLScene(slicer.mrmlScene)<br />
viewWidget.setMRMLViewNode(viewNode)<br />
viewWidget.show()<br />
</pre><br />
<br />
== Running an ITK filter in Python using SimpleITK ==<br />
Open the "Sample Data" module and download "MR Head", then paste the following snippet in Python interactor:<br />
<pre><br />
import SampleData<br />
import SimpleITK as sitk<br />
import sitkUtils<br />
<br />
# Get input volume node<br />
inputVolumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
# Create new volume node for output<br />
outputVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLScalarVolumeNode', 'MRHeadFiltered')<br />
<br />
# Run processing<br />
inputImage = sitkUtils.PullVolumeFromSlicer(inputVolumeNode)<br />
filter = sitk.SignedMaurerDistanceMapImageFilter()<br />
outputImage = filter.Execute(inputImage)<br />
sitkUtils.PushVolumeToSlicer(outputImage, outputVolumeNode)<br />
<br />
# Show processing result<br />
slicer.util.setSliceViewerLayers(background=outputVolumeNode)<br />
</pre><br />
<br />
More information:<br />
* See the SimpleITK documentation for SimpleITK examples: http://www.itk.org/SimpleITKDoxygen/html/examples.html<br />
* 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<br />
<br />
== Get current mouse coordinates in a slice view ==<br />
<br />
You can get 3D (RAS) coordinates of the current mouse cursor from the crosshair singleton node as shown in the example below:<br />
<br />
<pre><br />
def onMouseMoved(observer,eventid): <br />
ras=[0,0,0]<br />
crosshairNode.GetCursorPositionRAS(ras)<br />
print(ras)<br />
<br />
crosshairNode=slicer.util.getNode('Crosshair') <br />
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)<br />
</pre><br />
<br />
== Get DataProbe text ==<br />
<br />
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.<br />
<br />
<pre><br />
def printDataProbe():<br />
infoWidget = slicer.modules.DataProbeInstance.infoWidget<br />
for layer in ('B', 'F', 'L'):<br />
print(infoWidget.layerNames[layer].text, infoWidget.layerIJKs[layer].text, infoWidget.layerValues[layer].text)<br />
<br />
s = qt.QShortcut(qt.QKeySequence('.'), mainWindow())<br />
s.connect('activated()', printDataProbe)<br />
</pre><br />
<br />
== Get reformatted image from a slice viewer as numpy array ==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNodeID = 'vtkMRMLSliceNodeRed'<br />
<br />
# Get image data from slice view<br />
sliceNode = slicer.mrmlScene.GetNodeByID(sliceNodeID)<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslicedImage = vtk.vtkImageData()<br />
reslicedImage.DeepCopy(reslice.GetOutput())<br />
<br />
# Create new volume node using resliced image<br />
volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode")<br />
volumeNode.SetIJKToRASMatrix(sliceNode.GetXYToRAS())<br />
volumeNode.SetAndObserveImageData(reslicedImage)<br />
volumeNode.CreateDefaultDisplayNodes()<br />
volumeNode.CreateDefaultStorageNode()<br />
<br />
# Get voxels as a numpy array<br />
voxels = slicer.util.arrayFromVolume(volumeNode)<br />
print voxels.shape<br />
</pre><br />
<br />
== Thick slab reconstruction and maximum/minimum intensity volume projections ==<br />
<br />
Set up 'red' slice viewer to show thick slab reconstructed from 3 slices:<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMean()<br />
reslice.SetSlabNumberOfSlices(10) # mean of 10 slices will computed<br />
reslice.SetSlabSliceSpacingFraction(0.3) # spacing between each slice is 0.3 pixel (total 10 * 0.3 = 3 pixel neighborhood)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
Set up 'red' slice viewer to show maximum intensity projection (MIP):<br />
<pre><br />
sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')<br />
appLogic = slicer.app.applicationLogic()<br />
sliceLogic = appLogic.GetSliceLogic(sliceNode)<br />
sliceLayerLogic = sliceLogic.GetBackgroundLayer()<br />
reslice = sliceLayerLogic.GetReslice()<br />
reslice.SetSlabModeToMax()<br />
reslice.SetSlabNumberOfSlices(600) # use a large number of slices (600) to cover the entire volume<br />
reslice.SetSlabSliceSpacingFraction(0.5) # spacing between slices are 0.5 pixel (supersampling is useful to reduce interpolation artifacts)<br />
sliceNode.Modified()<br />
</pre><br />
<br />
The projected image is available in a ''vtkImageData'' object by calling ''reslice.GetOutput()''.<br />
<br />
== Change default file type for nodes (that have never been saved yet) ==<br />
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 STL for model nodes:<br />
<pre><br />
defaultModelStorageNode = slicer.vtkMRMLModelStorageNode()<br />
defaultModelStorageNode.SetDefaultWriteFileExtension('stl')<br />
slicer.mrmlScene.AddDefaultNode(defaultModelStorageNode)<br />
</pre><br />
<br />
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).<br />
<br />
== Change file type for saving for all volumes (with already existing storage nodes) ==<br />
<br />
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.<br />
<br />
<pre><br />
# Delete existing model storage nodes so that they will be recreated with default settings<br />
existingModelStorageNodes = slicer.util.getNodesByClass('vtkMRMLModelStorageNode')<br />
for modelStorageNode in existingModelStorageNodes:<br />
slicer.mrmlScene.RemoveNode(modelStorageNode)<br />
</pre><br />
<br />
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):<br />
<br />
<pre><br />
requiredFileExtension = '.nia'<br />
originalFileExtension = '.nrrd'<br />
volumeNodes = slicer.util.getNodesByClass('vtkMRMLScalarVolumeNode')<br />
for volumeNode in volumeNodes:<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
if not volumeStorageNode:<br />
volumeNode.AddDefaultStorageNode()<br />
volumeStorageNode = volumeNode.GetStorageNode()<br />
volumeStorageNode.SetFileName(volumeNode.GetName()+requiredFileExtension)<br />
else:<br />
volumeStorageNode.SetFileName(volumeStorageNode.GetFileName().replace(originalFileExtension, requiredFileExtension))<br />
</pre><br />
<br />
To set all volume nodes to save uncompressed by default (add this to .slicerrc.py so it takes effect for the whole session):<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLVolumeArchetypeStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Volume nodes will be stored uncompressed by default")<br />
</pre><br />
<br />
Same thing as above, but applied to all segmentations instead of volumes:<br />
<pre><br />
#set the default volume storage to not compress by default<br />
defaultVolumeStorageNode = slicer.vtkMRMLSegmentationStorageNode()<br />
defaultVolumeStorageNode.SetUseCompression(0)<br />
slicer.mrmlScene.AddDefaultNode(defaultVolumeStorageNode)<br />
logging.info("Segmentation nodes will be stored uncompressed <br />
</pre><br />
<br />
== Sequences ==<br />
<br />
=== Concatenate all sequences in the scene into a new sequence ===<br />
<br />
<pre><br />
# Get all sequence nodes in the scene<br />
sequenceNodes = slicer.util.getNodesByClass('vtkMRMLSequenceNode')<br />
mergedSequenceNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceNode', 'Merged sequence')<br />
<br />
# Merge all sequence nodes into a new sequence node<br />
mergedIndexValue = 0<br />
for sequenceNode in sequenceNodes:<br />
for itemIndex in range(sequenceNode.GetNumberOfDataNodes()):<br />
dataNode = sequenceNode.GetNthDataNode(itemIndex)<br />
mergedSequenceNode.SetDataNodeAtValue(dataNode, str(mergedIndexValue))<br />
mergedIndexValue += 1<br />
# Delete the sequence node we copied the data from, to prevent sharing of the same<br />
# node by multiple sequences<br />
slicer.mrmlScene.RemoveNode(sequenceNode)<br />
<br />
# Create a sequence browser node for the new merged sequence<br />
mergedSequenceBrowserNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSequenceBrowserNode', 'Merged')<br />
mergedSequenceBrowserNode.AddSynchronizedSequenceNode(mergedSequenceNode)<br />
slicer.modules.sequencebrowser.setToolBarActiveBrowserNode(mergedSequenceBrowserNode)<br />
# Show proxy node in slice viewers<br />
mergedProxyNode = mergedSequenceBrowserNode.GetProxyNode(mergedSequenceNode)<br />
slicer.util.setSliceViewerLayers(background=mergedProxyNode)<br />
</pre><br />
<br />
== Segmentations ==<br />
<br />
=== Create a segmentation from a labelmap volume and display in 3D ===<br />
<br />
<pre><br />
labelmapVolumeNode = getNode('label')<br />
seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')<br />
slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)<br />
seg.CreateClosedSurfaceRepresentation()<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
</pre><br />
<br />
The last line is optional. It removes the original labelmap volume so that the same information is not shown twice.<br />
<br />
=== Export labelmap node from segmentation node ===<br />
<br />
Export smallest possible labelmap:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToLabelmapNode(seg, labelmapVolumeNode)<br />
</pre><br />
<br />
Export labelmap that matches geometry of a chosen reference volume:<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
</pre><br />
<br />
Export by pressing Ctrl+Shift+s key:<br />
<br />
<pre><br />
outputPath = "c:/tmp"<br />
<br />
def exportLabelmap():<br />
segmentationNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")<br />
referenceVolumeNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLScalarVolumeNode")<br />
labelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')<br />
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(segmentationNode, labelmapVolumeNode, referenceVolumeNode)<br />
filepath = outputPath + "/" + referenceVolumeNode.GetName()+"-label.nrrd"<br />
slicer.util.saveNode(labelmapVolumeNode, filepath)<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode.GetDisplayNode().GetColorNode())<br />
slicer.mrmlScene.RemoveNode(labelmapVolumeNode)<br />
slicer.util.delayDisplay("Segmentation saved to "+filepath)<br />
<br />
shortcut = qt.QShortcut(slicer.util.mainWindow())<br />
shortcut.setKey(qt.QKeySequence('Ctrl+Shift+s'))<br />
shortcut.connect( 'activated()', exportLabelmap)<br />
</pre><br />
<br />
=== Export model nodes from segmentation node ===<br />
<br />
<pre><br />
seg = getNode('Segmentation')<br />
exportedModelsNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelHierarchyNode')<br />
slicer.modules.segmentations.logic().ExportAllSegmentsToModelHierarchy(seg, exportedModelsNode)<br />
</pre><br />
<br />
=== Show a segmentation in 3D ===<br />
Segmentation can only be shown in 3D if closed surface representation (or other 3D-displayable representation) is available. To create closed surface representation:<br />
<pre><br />
segmentation.CreateClosedSurfaceRepresentation()<br />
</pre><br />
<br />
=== Get a representation of a segment ===<br />
Access binary labelmap stored in a segmentation node (without exporting it to a volume node) - if it does not exist, it will return None:<br />
<pre><br />
image = segmentationNode.GetBinaryLabelmapRepresentation(segmentID)<br />
</pre><br />
Get closed surface, if it does not exist, it will return None:<br />
<pre><br />
polydata = segmentationNode.GetClosedSurfaceRepresentation(segmentID)<br />
</pre><br />
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):<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
outputOrientedImageData = vtkSegmentationCore.vtkOrientedImageData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentBinaryLabelmapRepresentation(segmentationNode, segmentID, outputOrientedImageData)<br />
</pre><br />
Same as above, for closed surface representation:<br />
<pre><br />
outputPolyData = vtk.vtkPolyData()<br />
slicer.vtkSlicerSegmentationsModuleLogic.GetSegmentClosedSurfaceRepresentation(segmentationNode, segmentID, outputPolyData)<br />
</pre><br />
<br />
=== Convert all segments using default path and conversion parameters ===<br />
<pre><br />
segmentationNode.CreateBinaryLabelmapRepresentation()<br />
</pre><br />
<br />
=== Convert all segments using custom path or conversion parameters ===<br />
Change reference image geometry parameter based on an existing referenceImageData image:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
referenceGeometry = vtkSegmentationCore.vtkSegmentationConverter.SerializeImageGeometry(referenceImageData)<br />
segmentation.SetConversionParameter(vtkSegmentationCore.vtkSegmentationConverter.GetReferenceImageGeometryParameterName(), referenceGeometry)<br />
</pre><br />
<br />
=== Re-convert using a modified conversion parameter ===<br />
Changing smoothing factor for closed surface generation:<br />
<pre><br />
import vtkSegmentationCorePython as vtkSegmentationCore<br />
segmentation = getNode('Segmentation').GetSegmentation()<br />
<br />
# Turn of surface smoothing<br />
segmentation.SetConversionParameter('Smoothing factor','0.0')<br />
<br />
# Recreate representation using modified parameters (and default conversion path)<br />
segmentation.RemoveRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
segmentation.CreateRepresentation(vtkSegmentationCore.vtkSegmentationConverter.GetSegmentationClosedSurfaceRepresentationName())<br />
</pre><br />
<br />
=== Get centroid of a segment in world (RAS) coordinates ===<br />
<br />
This example shows how to get centroid of a segment in world coordinates and show that position in all slice views.<br />
<br />
<pre><br />
segmentationNode = getNode('Segmentation')<br />
segmentId = 'Segment_1'<br />
<br />
# Get array voxel coordinates<br />
import numpy as np<br />
seg=arrayFromSegment(segmentation_node, segmentId)<br />
# numpy array has voxel coordinates in reverse order (KJI instead of IJK)<br />
# and the array is cropped to minimum size in the segmentation<br />
mean_KjiCropped = [coords.mean() for coords in np.nonzero(seg)]<br />
<br />
# Get segmentation voxel coordinates<br />
segImage = segmentationNode.GetBinaryLabelmapRepresentation(segmentId)<br />
segImageExtent = segImage.GetExtent()<br />
# origin of the array in voxel coordinates is determined by the start extent<br />
mean_Ijk = [mean_KjiCropped[2], mean_KjiCropped[1], mean_KjiCropped[0]] + np.array([segImageExtent[0], segImageExtent[2], segImageExtent[4]])<br />
<br />
# Get segmentation physical coordinates<br />
ijkToWorld = vtk.vtkMatrix4x4()<br />
segImage.GetImageToWorldMatrix(ijkToWorld)<br />
mean_World = [0, 0, 0, 1]<br />
ijkToRas.MultiplyPoint(np.append(mean_Ijk,1.0), mean_World)<br />
mean_World = mean_World[0:3]<br />
<br />
# If segmentation node is transformed, apply that transform to get RAS coordinates<br />
transformWorldToRas = vtk.vtkGeneralTransform()<br />
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(segmentationNode.GetParentTransformNode(), None, transformWorldToRas)<br />
mean_Ras = transformWorldToRas.TransformPoint(mean_World)<br />
<br />
# Show mean position value and jump to it in all slice viewers<br />
print(mean_Ras)<br />
slicer.modules.markups.logic().JumpSlicesToLocation(mean_Ras[0], mean_Ras[1], mean_Ras[2], True)<br />
</pre><br />
<br />
=== How to run segment editor effects from a script ===<br />
<br />
Editor effects are complex because they need to handle changing master volumes, undo/redo, masking operations, etc. Therefore, instead of using a segment editor effect, it is simpler to run the underlying filters directly from script.<br />
<br />
This example demonstrates how to use Segment editor effects (without GUI, using qMRMLSegmentEditorWidget):<br />
<br />
* [https://gist.github.com/lassoan/2d5a5b73645f65a5eb6f8d5f97abf31b brain tumor segmentation using grow from seeds effect]<br />
* [https://gist.github.com/lassoan/1673b25d8e7913cbc245b4f09ed853f9 skin surface extraction using thresholding and smoothing]<br />
* [https://gist.github.com/lassoan/2f5071c562108dac8efe277c78f2620f mask a volume with segments and compute histogram for each region]<br />
* [https://gist.github.com/lassoan/5ad51c89521d3cd9c5faf65767506b37 create fat/muscle/bone segment by thresholding and report volume of each segment]<br />
<br />
This example shows how to perform operations on segmentations using VTK filters:<br />
* [https://gist.github.com/lassoan/7c94c334653010696b2bf96abc0ac8e7 brain tumor segmentation using grow from seeds effect]<br />
<br />
== Accessing views, renderers, and cameras ==<br />
<br />
Iterate through all 3D views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for threeDViewIndex in range(layoutManager.threeDViewCount) :<br />
view = layoutManager.threeDWidget(threeDViewIndex).threeDView()<br />
threeDViewNode = view.mrmlViewNode()<br />
cameraNode = slicer.modules.cameras.logic().GetViewActiveCameraNode(threeDViewNode)<br />
print('View node for 3D widget ' + str(threeDViewIndex))<br />
print(' Name: ' + threeDViewNode .GetName())<br />
print(' ID: ' + threeDViewNode .GetID())<br />
print(' Camera ID: ' + cameraNode.GetID())<br />
</pre><br />
<br />
Iterate through all slice views in current layout:<br />
<br />
<pre><br />
layoutManager = slicer.app.layoutManager()<br />
for sliceViewName in layoutManager.sliceViewNames():<br />
view = layoutManager.sliceWidget(sliceViewName).sliceView()<br />
sliceNode = view.mrmlSliceNode()<br />
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)<br />
compositeNode = sliceLogic.GetSliceCompositeNode()<br />
print('Slice view ' + str(sliceViewName))<br />
print(' Name: ' + sliceNode.GetName())<br />
print(' ID: ' + sliceNode.GetID())<br />
print(' Background volume: {0}'.format(compositeNode.GetBackgroundVolumeID()))<br />
print(' Foreground volume: {0} (opacity: {1})'.format(compositeNode.GetForegroundVolumeID(), compositeNode.GetForegroundOpacity()))<br />
print(' Label volume: {0} (opacity: {1})'.format(compositeNode.GetLabelVolumeID(), compositeNode.GetLabelOpacity()))<br />
</pre><br />
<br />
For low-level manipulation of views, it is possible to access VTK render windows, renderers and cameras of views in the current layout.<br />
<pre><br />
renderWindow = view.renderWindow()<br />
renderers = renderWindow.GetRenderers()<br />
renderer = renderers.GetItemAsObject(0)<br />
camera = cameraNode.GetCamera()<br />
</pre><br />
<br />
== Hide view controller bars ==<br />
<br />
<pre><br />
slicer.app.layoutManager().threeDWidget(0).threeDController().setVisible(False)<br />
slicer.app.layoutManager().sliceWidget('Red').sliceController().setVisible(False)<br />
slicer.app.layoutManager().plotWidget(0).plotController().setVisible(False)<br />
slicer.app.layoutManager().tableWidget(0).tableController().setVisible(False)<br />
</pre><br />
<br />
== Change 3D view background color ==<br />
<br />
<pre><br />
renderWindow = slicer.app.layoutManager().threeDWidget(0).threeDView().renderWindow()<br />
renderer = renderWindow.GetRenderers().GetFirstRenderer()<br />
renderer.SetBackground(1,0,0)<br />
renderer.SetBackground2(1,0,0)<br />
renderWindow.Render()<br />
</pre><br />
<br />
== Subject hierarchy == <br />
==== Get the pseudo-singleton subject hierarchy node ====<br />
It manages the whole hierarchy and provides functions to access and manipulate<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
<br />
==== Create subject hierarchy item ====<br />
# If it is for a data node, it is automatically created, but the create function can be used to set parent:<br />
shNode.CreateItem(parentItemID, dataNode)<br />
# If it is a hierarchy item without a data node, then the create function must be used:<br />
shNode.CreateSubjectItem(parentItemID, name)<br />
shNode.CreateFolderItem(parentItemID, name)<br />
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)<br />
<br />
==== Get subject hierarchy item ====<br />
Items in subject hierarchy are uniquely identified by integer IDs<br />
# Get scene item ID first because it is the root item:<br />
sceneItemID = shNode.GetSceneItemID()<br />
# Get direct child by name<br />
subjectItemID = shNode.GetItemChildWithName(sceneItemID, 'Subject_1')<br />
# Get item for data node<br />
itemID = shNode.GetItemByDataNode(dataNode)<br />
# Get item by UID (such as DICOM)<br />
itemID = shNode.GetItemByUID(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMUIDName(), seriesInstanceUid)<br />
itemID = shNode.GetItemByUIDList(slicer.vtkMRMLSubjectHierarchyConstants.GetDICOMInstanceUIDName(), instanceUID)<br />
# Invalid item ID for checking validity of a given ID (most functions return the invalid ID when item is not found)<br />
invalidItemID = slicer.vtkMRMLSubjectHierarchyNode.GetInvalidItemID()<br />
<br />
==== Traverse children of a subject hierarchy item ====<br />
children = vtk.vtkIdList()<br />
shNode.GetItemChildren(parent, children)<br />
for i in range(children.GetNumberOfIds()):<br />
child = children.GetId(i)<br />
...<br />
<br />
==== Manipulate subject hierarchy item ====<br />
Instead of node operations on the individual subject hierarchy nodes, item operations are performed on the one subject hierarchy node.<br />
# Set item name<br />
shNode.SetItemName(itemID, 'NewName')<br />
# Set item parent (reparent)<br />
shNode.SetItemParent(itemID, newParentItemID)<br />
# Set visibility of data nodes associated to items in a branch (or a leaf item)<br />
shNode.SetDisplayVisibilityForBranch(itemID, 1)<br />
<br />
==== Filter items in TreeView or ComboBox ====<br />
Displayed items can be filtered using ''setAttributeFilter'' method. An example of the usage can be found in the [https://github.com/Slicer/Slicer/blob/e66e3b08e35384526528e6ae678e9ec9f079f286/Applications/SlicerApp/Testing/Python/SubjectHierarchyGenericSelfTest.py#L352-L360 unit test]. Modified version here:<br />
print(shTreeView.displayedItemCount()) # 5<br />
shTreeView.setAttributeFilter('DICOM.Modality') # Nodes must have this attribute<br />
print(shTreeView.displayedItemCount()) # 3<br />
shTreeView.setAttributeFilter('DICOM.Modality','CT') # Have attribute and equal 'CT'<br />
print(shTreeView.displayedItemCount()) # 1<br />
shTreeView.removeAttributeFilter()<br />
print(shTreeView.displayedItemCount()) # 5<br />
<br />
=== Listen to subject hierarchy item events ===<br />
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).<br />
<br />
class MyListenerClass(VTKObservationMixin):<br />
def __init__(self):<br />
VTKObservationMixin.__init__(self)<br />
<br />
shNode = slicer.vtkMRMLSubjectHierarchyNode.GetSubjectHierarchyNode(slicer.mrmlScene)<br />
self.addObserver(shNode, shNode.SubjectHierarchyItemModifiedEvent, self.shItemModifiedEvent)<br />
<br />
@vtk.calldata_type(vtk.VTK_LONG) <br />
def shItemModifiedEvent(self, caller, eventId, callData):<br />
print("SH Node modified")<br />
print("SH item ID: {0}".format(callData))<br />
<br />
== Plotting ==<br />
<br />
=== Create histogram plot of a volume ===<br />
<br />
<pre><br />
# Get a volume from SampleData<br />
import SampleData<br />
volumeNode = SampleData.SampleDataLogic().downloadMRHead()<br />
<br />
# Compute histogram values<br />
import numpy as np<br />
histogram = np.histogram(arrayFromVolume(volumeNode), bins=50)<br />
<br />
# Save results to a new table node<br />
tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")<br />
updateTableFromArray(tableNode, histogram)<br />
tableNode.GetTable().GetColumn(0).SetName("Count")<br />
tableNode.GetTable().GetColumn(1).SetName("Intensity")<br />
<br />
# Create plot<br />
plotSeriesNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode", volumeNode.GetName() + ' histogram')<br />
plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())<br />
plotSeriesNode.SetXColumnName("Intensity")<br />
plotSeriesNode.SetYColumnName("Count")<br />
plotSeriesNode.SetPlotType(plotSeriesNode.PlotTypeScatterBar)<br />
plotSeriesNode.SetColor(0, 0.6, 1.0)<br />
<br />
# Create chart and add plot<br />
plotChartNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode")<br />
plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())<br />
plotChartNode.YAxisRangeAutoOff()<br />
plotChartNode.SetYAxisRange(0, 500000)<br />
<br />
# Show plot in layout<br />
slicer.modules.plots.logic().ShowChartInLayout(plotChartNode)<br />
<br />
</pre><br />
<br />
== Execute external applications ==<br />
<br />
How to run external applications from Slicer.<br />
<br />
=== Run process in default environment ===<br />
<br />
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 ''slicer.util.startupEnvironment()''.<br />
<br />
Example: run Python3 script from Slicer:<br />
<br />
<pre><br />
command_to_execute = ["/usr/bin/python3", "-c", "print('hola')"]<br />
from subprocess import check_output<br />
check_output(<br />
command_to_execute, <br />
env=slicer.util.startupEnvironment()<br />
)<br />
</pre><br />
<br />
will output:<br />
<pre><br />
'hola\n'<br />
</pre><br />
<br />
On some systems, ''shell=True'' must be specified as well.</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/Plots&diff=61223Documentation/Nightly/Modules/Plots2019-06-14T22:10:16Z<p>Pieper: </p>
<hr />
<div>For now, see [[Documentation/{{documentation/version}}/Developers/Plots| the developer documentation]]</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Modules/Plots&diff=61222Documentation/Nightly/Modules/Plots2019-06-14T22:10:06Z<p>Pieper: Created page with "Fro now, see the developer documentation"</p>
<hr />
<div>Fro now, see [[Documentation/{{documentation/version}}/Developers/Plots| the developer documentation]]</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Labs/Slicer5-roadmap&diff=61080Documentation/Labs/Slicer5-roadmap2019-04-25T20:27:06Z<p>Pieper: /* SceneViews */</p>
<hr />
<div>The major version number upgrade to 5 provides an opportunity to make changes that affect the<br />
application, the API, or the code in a way that was not possible in the past seven or so years.<br />
<br />
This page collects community suggestions related to the transition plan for Slicer 4.10 and major changes for Slicer 5.0.<br />
<br />
Related forum post: https://discourse.slicer.org/t/slicer-5-0-deprecation-discussion-wiki/2377<br />
<br />
== Overall Goals ==<br />
<br />
* Improve user experience<br />
** More logical interface<br />
** Perform most common tasks easily<br />
** Easier to discover advanced features<br />
** Improve asynchronous behavior (like loading data in a background thread)<br />
* Defining core and extensions<br />
** Core functionality is:<br />
*** DICOM and other format I/O, Subject management<br />
*** Visualization 2D/3D/4D<br />
*** Segmentation<br />
*** Transforms and Registration<br />
*** Annotations and Markups<br />
*** Programmability and Extensibility<br />
** Move some extensions to core (Sequences, DICOMPlugins...)<br />
** Move some core to extensions (SimpleITK, Editor...)<br />
* Simplify maintenance<br />
** Remove legacy code that adds more complexity than value<br />
** Deprecate support for older build options and platforms (old libs, old compilers, etc)<br />
** Simplify documentation creation and use<br />
** Streamline the build and release process<br />
** Use unmodified upstream libraries<br />
* Developer experience<br />
** Improve API / Scripting documentation organization / search engine optimization<br />
** Simplify/accelerate build process on all platforms (options to use prebuilt sdk for example)<br />
** Use standard packages (Qt, Python, VTK, ITK)<br />
<br />
<hr><br />
<br />
== Specific Change Proposals ==<br />
<br />
=== Slicer 5.0: Backward incompatible changes ===<br />
<br />
==== Build System Simplification ====<br />
<br />
* Pick the most recent reasonable CMake version and remove any complexities in the build system are only there to work around limitations of old CMake versions.<br />
<br />
* Consider any ways to streamline/simplify the configure and build process, even it if may require changing extensions.<br />
<br />
* Look for ways to minimize the effect of long directory path-related build issues. Currently on mac and windows we are pushing the limit of path length unless very short paths are used (e.g. /s5 or d:\s5). Reorganizing the build tree might give us more headroom.<br />
<br />
==== Python3 ====<br />
<br />
Switch to Python3 and use the same compiler as official Python distribution. This would allow installation of any Python package inside Slicer's Python environment.<br />
<br />
Tasks:<br />
* Update of CTK: Build system, CTK Python console and [https://github.com/commontk/CTK/blob/master/CMake/ctkWrapPythonQt.py ctkWrapPythonQt.py] - '''DONE''' {{done}}<br />
* Update [https://github.com/Slicer/Slicer/blob/master/SuperBuild/External_python.cmake External_python.cmake] - '''DONE''' {{done}}<br />
* Update of "C++ to python bridge" classes ([https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedUtils_p.h qSlicerScriptedUtils_p.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModule.h qSlicerScriptedLoadableModule.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedFileDialog.h qSlicerScriptedFileDialog.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTGUI/qSlicerScriptedLoadableModuleWidget.h qSlicerScriptedLoadableModuleWidget.h], [https://github.com/Slicer/Slicer/blob/master/Base/QTCore/qSlicerScriptedFileWriter.h qSlicerScriptedFileWriter.h], ...) - '''DONE''' {{done}}<br />
* Update of install rules and macos fixup - '''DONE''' {{done}}<br />
* Update of python scripts to be compliant with python 3 - '''DONE''' {{done}}<br />
<br />
Some of the issues discovered after integration of Python 3:<br />
* Fix iomodule.c build error with VS2017. See https://github.com/Slicer/Slicer/pull/1118#issuecomment-482436689. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28138 r28138] - '''DONE''' {{done}}<br />
* Fix crash in Debug build. See https://github.com/lassoan/Slicer/tree/python-startup-hang-in-debug-mode. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28141 r28141] - '''DONE''' {{done}}<br />
* <tt>restart()</tt> Python function does not work. Fixed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=28143 r28143] - '''DONE''' {{done}}<br />
<br />
References:<br />
* discourse post: https://discourse.slicer.org/t/updating-slicer-to-work-with-python-3/4662/14<br />
* GitHub PR: https://github.com/Slicer/Slicer/pull/1118<br />
<br />
==== SceneViews ====<br />
<br />
The scene views feature does not work well for a long time now, and there is no consensus about what should be the scope it supports.<br />
<br />
Suggestion:<br />
* Do not save the state of all nodes: Support only display, view and hierarchy nodes. <br />
* Make SceneViews as stable as possible for these cases and remove support for data notes etc.<br />
* If a node is removed, update associated scene views<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we should keep the SceneView functionality. All data should be associated with the "master" view, and scene view should be different combination of viewing parameters (layout, camera, visibility, etc ...). A mrb to consider for testing is the [http://slicer.kitware.com/midas3/slicerdatastore/view?itemId=126553&layout=layout LungSegments_scene.mrb]<br />
* Another suggestion from Sonia is that SceneViews could be read-only for certain classes of nodes. It's not clear how that would be implemented, but it could address the instability problems while enabling the use of SceneViews for training.<br />
<br />
==== Undo/Redo ====<br />
Similarly to SceneViews, it is a great feature but in time it started breaking.<br />
Need to decide if we want to keep it, and if yes fix it.<br />
<br />
Potential fix (currently being tested by Kyle Sunderland and Andras Lasso): Add an "undo enabled" flag to vtkMRMLNode, disable it by default, only enable it for nodes that undo/redo has tested to work correctly. Preliminary tests show that the feature largely works, but there are complications with undo/redo of node add/remove actions and node references.<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, would be nice to also have undo/redo for camera settings, field of view, etc ... within a given view. It is easy to inadvertently modify settings ... (e.g when trying to pan using a trackpad with shift+left click but inadvertently using only left click)<br />
<br />
==== Removal of Charts based infrastructure ====<br />
<br />
With Slicer 5.0, the idea is to remove the [[Documentation/Nightly/Developers/Charts|Charts]] infrastructure based on jqPlot, and only keep<br />
the [[Documentation/Nightly/Developers/Plots|Plots]] infrastructure based on VTK Charts.<br />
<br />
Ron's request: enable anti-aliasing (MSAA or FXAA) and use less subtle default colors (https://www.slicer.org/wiki/Slicer4:2012_GenericChartColors) to improve appearance.<br />
<br />
==== Revisit MRML Copy API ====<br />
<br />
Copy method does not perform complete deep-copy in some classes. For Sequences, we need both DeepCopy (for node modifications) and ShallowCopy (for fast replay possible).<br />
<br />
There are also too many variants of node copy methods, which makes it difficult to use them correctly.<br />
<br />
See also https://issues.slicer.org/view.php?id=2608.<br />
<br />
==== Remove remote data support from MRML ====<br />
<br />
MRML theoretically supports downloading files through http, but this feature has not seen much use. This will not likely to change in the future because there is a wide range of data access and authentication protocols, which would not be practical at MRML level.<br />
<br />
It would be better to remove remote data support from MRML to simplify data storage. We can keep useful utility classes, such as cache manager for keeping track of local temporary files (downloaded using SampleData or other modules that download significant amount of temporary data).<br />
<br />
See also https://discourse.slicer.org/t/improving-testing-data-management-for-self-test/5014/4.<br />
<br />
==== Improve layout manager ====<br />
<br />
* Support multiple displays: Currently, it is very hard to leverage multiple displays (need to stretch the Slicer window over multiple screens and align splitter manually to the screen boundary). Allow defining single-display and multi-display layouts. Single-display layouts could be selected for each display independently, while multi-monitor layouts would set views on several displays at once. Keeping a single layout manager (and enhance it with to allow creation of multiple widgets) would make it easier to maintain backward compatibility for existing modules.<br />
* View layout IDs: View layout IDs are currently integer values, which makes it difficult to ensure that modules always choose unique IDs. We should switch to using string IDs. String IDs can may be prefixed with modulename+"." as we do it for singleton tags and node attributes. We may remain somewhat backward compatible by having SetLayoutID(int) method that maps known layout integer IDs to the new string IDs. See discussion here: https://github.com/Slicer/Slicer/pull/1061#discussion_r241825827<br />
<br />
====Coordinate system in files====<br />
<br />
To be consistent with the rest of the world: Save models and markups in LPS coordinate system by default. If no coordinate system is specified in input file, assume LPS.<br />
<br />
====Acquisition transform====<br />
<br />
Enable acquisition transform by default, to show correct loading of tilted gantry images. It has proven to work well.<br />
<br />
=== Remove deprecated modules and/or Migrate to extension ===<br />
<br />
==== Editor ====<br />
The module already directs users to Segment Editor, which provides all the functionality of Editor and more, and<br />
is the successor module that will be improved and maintained. Removing it would decrease confusion of both old<br />
and new Slicer users<br />
<br />
* Potentially the hack about modules with names ending with the string "Lib" can also be removed after the Editor module will not require it. It is [http://viewvc.slicer.org/viewvc.cgi/Slicer4/trunk/Base/QTCore/qSlicerUtils.cxx?r1=26891&r2=26890&pathrev=26891 around here].<br />
<br />
* '''Make Editor hidden in 4.10, advertise its removal (some extensions still use it), then remove it in 5.0'''. Remove it from toolbar, move Editor to legacy category in 4.10<br />
<br />
* Investigate if the module could easily be moved to an extension<br />
<br />
==== VectorToScalarVolume ====<br />
<br />
The plan would be to improve the Volume module so that vector volume could be converted to scalar volume, similarly to scalar to labelmap conversion option. Then, this module could be removed.<br />
<br />
==== Unused module code ====<br />
<br />
* <s>MultiVolumeRendering: A [https://github.com/Slicer/Slicer/tree/master/Modules/Loadable/MultiVolumeRendering module] that was effectively not developed since 2012, and is not currently compiled with Slicer.</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>Measurements: Same argument as MultiVolumeRendering</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27087 r27087]<br />
* <s>AtlasCreator Loadable module logic</s> - Removed in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27088 r27088]<br />
<br />
==== CLI modules====<br />
<br />
* Model to Label Map: Has too many limitations and bugs (cannot handle concave structures, can cause Slicer to hang or crash, etc.), and is not maintained any more. It might be better to remove it than to fix it, especially that there is an algorithm for the same thing in Slicer that works much better<br />
** The model node to labelmap node conversion feature could be added as a subject hierarchy plugin, if the route via segmentation node is not convenient enough<br />
<br />
* Review CLI modules<br />
** BlobDetection<br />
** ConnectedComponent<br />
** GrayscaleModelMaker, ModelMaker: The modules are too different to combine them. Each have specific use cases.<br />
** DiffusionTensorTest, ROITest, TestGridTransformRegistration: Already excluded from package by specifying <tt>NO_INSTALL</tt><br />
** Resample Scalar Volume: Resample Scalar/Vector/DWI Volume module (which Crop Volume uses as well) can do everything it does already, except for four extra interpolation options. Probably can be removed<br />
<br />
==== Migrate to extension ====<br />
<br />
Existing [https://github.com/Slicer/Slicer/blob/master/Modules/Scripted/DMRIInstall/DMRIInstall.py DMRIInstall] scripted module will be re-factored and moved into a <tt>Modules/Scripted/InstallSuggestions</tt> directory.<br />
<br />
Then, after transitioning them to extension, the following module will be added to the "InstallSuggestions" so that the user knows how to install them:<br />
<br />
* BRAINSTools (also add SlicerElastix to the suggestions)<br />
* SimpleITK: Only used in the editor<br />
* EMSegment: already disabled in Slicer-4.10, so it may be completely removed from build scripts instead of moving it to an extension<br />
<br />
Notes:<br />
* 2018-12-13: Jc: Following discussion with Ron, we need to make sure to have at least one non-rigid registration method and one ICP based method (e.g Landmark Registration) available in the main distribution.<br />
<br />
==== PETStandardUptakeValueComputation ====<br />
<br />
Remove PETStandardUptakeValueComputation from Slicer core, as a more advanced version of this is available in an extension: https://github.com/QIICR/Slicer-PETDICOMExtension. See details here: https://github.com/Slicer/Slicer/pull/1068#issuecomment-450905887<br />
<br />
=== Coding Style===<br />
<br />
==== Slicer 5.0: Indentation of curly braces ====<br />
In Slicer the curly braces have a two-space indentation everywhere within functions. As this was inherited from VTK, but VTK changed its convention to align the braces with the statements (if etc.), it could make sense to make the change in Slicer too. This is considered a major change because it affects almost all cxx files.<br />
<br />
==== Simpler VTK smart pointer usage ====<br />
Use <code>vtkNew<type> var;</code> instead of <code>vtkSmartPointer<type> var = vtkSmartPointer<type>::New();</code> and remove now unnecessary <code>.GetPointer()</code> calls.<br />
<br />
=== Miscellaneous ===<br />
<br />
==== Tcl codes ====<br />
<s>Most of the TCL code seems to be a heritage from Slicer3. Can they be removed?</s> - Done in [http://viewvc.slicer.org/viewvc.cgi/Slicer4?view=revision&revision=27091 r27091]<br />
<br />
==== Remove self-test modules from the All modules list ====<br />
Users already find the all modules list very long, and as the self tests are for developers only (and can be found in the modules list under the Testing category), they could be removed from the list. Make sure they are available in Developer mode.<br />
<br />
==== Remove BTX/ETX pairs ====<br />
Once VTK7 is no longer supported, the old way for disabling python wrapping is no longer needed. According to my tests (Csaba), wrapping works fine in all of those cases, so the new way (#ifndef __VTK_WRAP__) is not needed either.<br />
<br />
== Additional proposed changes to be discussed ==<br />
<br />
* Bundle IPython package in Slicer installer<br />
* [https://discourse.slicer.org/t/add-slicer-nightly-to-homebrew-macos/811 Install using brew]<br />
* Add opt-in collection of usage statistics for various features (e.g. could be triggered when a module is entered).<br />
* Enable geometry correction by default (e.g. gantry tilt as [https://discourse.slicer.org/t/actual-size-of-stl-models/5005/21 discussed here]).<br />
* Remove legacy 1.0 pydicom and only bundle latest (see https://pydicom.github.io/pydicom/stable/transition_to_pydicom1.html#). Import name changed from 'dicom' to 'pydicom'</div>Pieperhttps://www.slicer.org/w/index.php?title=Main_Page/SlicerCommunity&diff=61057Main Page/SlicerCommunity2019-04-22T19:15:50Z<p>Pieper: </p>
<hr />
<div><includeonly>----<br />
Go to <big>[[Main_Page/SlicerCommunity/2019|2019]] :: [[Main_Page/SlicerCommunity/2018|2018]] :: [[Main_Page/SlicerCommunity/2017|2017]] :: [[Main_Page/SlicerCommunity/2016|2016]] :: [[Main_Page/SlicerCommunity/2015|2015]] :: [[Main_Page/SlicerCommunity/2011-2014|2014-2011]] :: [[Main_Page/SlicerCommunity/2005-2010|2010-2005]]</big><br />
----</includeonly><br />
<noinclude>=3D Slicer Enabled Research=<br />
[[Documentation/{{documentation/currentversion}}/Slicer|3D Slicer]] is a free open source software package distributed under a BSD style [[License|license]] for analysis, integration, and visualization of medical images. 3D Slicer allows even those with limited image processing experience to effectively explore and quantify their imaging data for hypothesis-driven research. <br />
</noinclude><br />
<br />
The community that relies on 3D Slicer is large and active: <br />
*[https://download.slicer.org/download-stats/ 500,000+ downloads] in the past eight years (120,000+ in 2018)<br />
*[https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= over 8,100+ literature search results on Google Scholar] <br />
*[https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 4000+ cancer research citations on Google Scholar] ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28prostate%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1000+ prostate]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28brain%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 2,320+ brain]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28lung%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1,310+ lung]), ([https://scholar.google.com/scholar?hl=en&as_sdt=1%2C22&as_vis=1&q=%28breast%29+AND+%28cancer+OR+tumor+OR+radiation%29+AND+%28%223D+Slicer%22+OR+%22slicer+org%22+OR+Slicer3D%29+-Slic3r+&btnG= 1010+ breast])<br />
*[https://na-mic.github.io/ProjectWeek/ 30+ events in open source hackathon series] continuously running since 2005<br />
<br />
<!--<br />
The research of Slicer community is represented in the [http://www.slicer.org/publications/pages/display/?collection=11 publication database].<br />
--><br />
<br />
The following is a sample of the research performed<includeonly> in {{#titleparts: {{PAGENAME}} | 2 | 3 }}</includeonly> using 3D Slicer outside of the group that develops it. (Research performed by groups that are also actively developing 3D Slicer is represented in the [http://www.spl.harvard.edu/publications/pages/display/?collection=11&collection=11 publication database]).<br />
<noinclude><br />
* [[Main_Page/SlicerCommunity/2019|2019]]<br />
* [[Main_Page/SlicerCommunity/2018|2018]]<br />
* [[Main_Page/SlicerCommunity/2017|2017]]<br />
* [[Main_Page/SlicerCommunity/2016|2016]]<br />
* [[Main_Page/SlicerCommunity/2015|2015]]<br />
* [[Main_Page/SlicerCommunity/2011-2014|2014-2011]]<br />
* [[Main_Page/SlicerCommunity/2005-2010|2010-2005]]<br />
<br />
We invite you to provide information using our [https://discourse.slicer.org/ discussion forum] on how you are using 3D Slicer to produce peer-reviewed research. Information about the scientific impact of this tool is helpful in raising funding for the continued support.<br />
</noinclude></div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=61054Documentation/Nightly/Developers/Build Instructions/Prerequisites2019-04-18T21:53:44Z<p>Pieper: /* Mac OSX 10.14 (Mojave) */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554/21</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk make -j20<br />
</pre><br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe qt-unified-windows-x86-online.exe] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components. For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/Nightly/Developers/Build_Instructions/Prerequisites&diff=61053Documentation/Nightly/Developers/Build Instructions/Prerequisites2019-04-18T21:52:45Z<p>Pieper: /* Mac OSX 10.14 (Mojave) */</p>
<hr />
<div><noinclude>{{documentation/versioncheck}}</noinclude><br />
__TOC__<br />
<br />
== PREREQUISITES ==<br />
<br />
<!-- For all systems be sure git-lfs is properly installed for use with VTKv9 (required for VTKm checkout) --><br />
<br />
As of early 2018, Slicer migrated to Qt5. Qt4 support was removed in version 4.11. See instructions at this labs page for the new requirements: https://www.slicer.org/wiki/Documentation/Labs/Qt5-and-VTK8<br />
<br />
<br><br />
<!--<br />
Please check that the following tools are installed on your machine.<br />
<br />
We try to keep current with the most recent releases of these prerequisites, but sometimes it's critical to use the exact versions specified here. If you run into issues please do whatever you can to find a combination that works or contact the developer mailing list for suggestions.<br />
<br />
<br><br />
{{mbox<br />
| type = protection<br />
| text = Qt libraries are '''required'''. Consider reading [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt|Qt requirements]].<br />
| image= [[{{tool|logo|qt}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = CMake is '''required'''.<br />
| image= [[{{tool|logo|cmake}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Git is '''required'''.<br />
| image= [[{{tool|logo|git}}|x40px]]<br />
}}<br />
<br><br />
<br />
{{mbox<br />
| type = protection<br />
| text = SVN is '''required'''.<br />
| image= [[{{tool|logo|svn}}|x40px]]<br />
}}<br />
<br />
<br><br />
--><br />
The prerequisites listed below are required to be able to configure/build/package/test Slicer.<br />
<br />
=== Linux ===<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
* Qt 5.11: <b>tested and recommended</b><br />
** To build Slicer: install Qt using the distribution package manager.<br />
** To package and redistribute Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run qt-unified-linux-x64-online.run], install Qt, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
* GCC suite<br />
<br />
==== Debian ====<br />
<br />
* Debian squeeze/wheezy/testing(jessie) users, start by pasting the following lines in a terminal<br />
sudo apt-get install subversion git-core git-svn<br />
sudo apt-get install build-essential libx11-dev libxt-dev libgl1-mesa-dev libosmesa6-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
sudo apt-get install cmake<br />
sudo apt-get install qt-sdk<br />
<br />
<hr><br />
<br />
<br />
==== Ubuntu ====<br />
<br />
===== Common Prerequisites =====<br />
<br />
<br />
sudo apt-get install subversion git-core git-svn<br />
<br />
sudo apt-get install make gcc g++ libx11-dev libxt-dev libgl1-mesa-dev libglu1-mesa-dev libfontconfig-dev libxrender-dev libncurses5-dev<br />
<br />
sudo apt-get install libosmesa6-dev # Only for Ubuntu < 14.04.3<br />
<br />
{{remark|red|On Ubuntu 14.04.3 LTS, attempting to install <tt>libosmesa6-dev</tt> results in an error.<pre><br />
The following packages have unmet dependencies:<br />
libosmesa6-dev : Depends: libosmesa6 (= 10.1.3-0ubuntu0.4) but it is not going to be installed<br />
E: Unable to correct problems, you have held broken packages.<br />
</pre><br />
For more details, see [https://bugs.launchpad.net/ubuntu/+source/mesa-lts-utopic/+bug/1424059 Bug 1424059].}}<br />
<br />
{{remark|green|Slicer compiles successfully without that package, but <tt>VTK_OPENGL_HAS_OSMESA</tt> is disabled.}}<br />
<br />
* For Qt5.5 on Ubuntu 16.04<br />
sudo apt-get install libgstreamer-plugins-base0.10-dev<br />
<br />
===== CMake =====<br />
<br />
<ol><br />
<li>Open a terminal and copy the command reported below</li><br />
<li>Download stable version of CMake and extract the archive:<br />
<pre><br />
sudo apt-get install curl<br />
mkdir ~/Support && cd ~/Support<br />
curl -O https://cmake.org/files/v3.13/cmake-3.13.4-Linux-x86_64.tar.gz<br />
tar -xzvf cmake-3.13.4-Linux-x86_64.tar.gz<br />
</pre><br />
</li><br />
<li>Create symbolic links into <code>~/bin</code><br />
<pre><br />
mkdir -p ~/bin<br />
for name in cmake ctest cpack ccmake cmake-gui; do<br />
ln -s ~/Support/cmake-3.13.4-Linux-x86_64/bin/$name ~/bin/$name<br />
done<br />
</pre><br />
</li><br />
</ol><br />
<br />
{{remark|red|You <b>MUST</b> download the standard CMake binary because the distributed version of CMake cannot be used to build slicer.<br><br />
See [[Documentation/{{documentation/version}}/Developers/FAQ/Building#Why_distributed_CMake_can_not_be_used_on_Ubuntu_12.04_and_above_.3F|here]] for more details.}}<br />
<br />
==== CentOS ====<br />
<br />
See instructions reported in [https://github.com/Slicer/SlicerBuildEnvironment/blob/master/Docker/qt5-centos7/Dockerfile qt5-centos7/Dockerfile]<br />
<br />
<!--<br />
*CentOS user type:<br />
yum install make gcc-c++ libX11-devel libXt-devel libXrender-devel libXext-devel libGLU-devel mesa-libOSMesa-devel mesa-libGL-devel mesa-libGLU-devel ncurses<br />
--><br />
<br />
<!--<br />
Todo: This will have to be added in FAQ: Troubleshoot section<br />
''glx-utils'' provides ''glxgears'' that can be used to test rendering<br />
--><br />
<br />
=== MacOSX ===<br />
* XCode command line tools must be installed:<br />
xcode-select --install<br />
* El Capitan is what most developers use.<br />
* CMake 3.12.2 is recommended. Check the minimum required CMake version [https://github.com/Slicer/Slicer/blob/master/CMakeLists.txt#L1 here]<br />
* Qt 5: <b>tested and recommended</b>.<br />
** For building Slicer: download and execute [https://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg qt-unified-mac-x64-online.dmg], install Qt 5.10, make sure to select <tt>qtscript</tt> and <tt>qtwebengine</tt> components.<br />
** For packaging and redistributing Slicer: build Qt using [https://github.com/jcfr/qt-easy-build#readme qt-easy-build]<br />
<br />
====MacOSX 10.9.4 (Mavericks)====<br />
<br />
''' (1) Make sure to install this update: http://support.apple.com/kb/DL1754'''<br />
<br />
''' (2) Use CMake 3.12.2 - it is known to be working and is supported''' (if you want to use CMake already installed on your system, 2.8.12.2 is known to work on Mac OS X 10.9.5)<br />
<br />
* Mac Os X >= 10.5 (Leopard)<br />
* [{{tool|download|cmake}} CMake] >= 2.8.9<br />
** For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) and/or recent XCode >= 4.5.X - [{{tool|download|cmake}} CMake] >= 2.8.11 is required. See http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
<!-- Waiting for the official release, get the release candidate rc1 [http://www.cmake.org/files/v2.8/cmake-2.8.11-rc1-Darwin64-universal.tar.gz here]. For explanation, see [[Documentation/{{documentation/version}}/Developers/Build_Instructions#ld:_framework_not_found_QtWebKit|here]] and [[Documentation/{{documentation/version}}/Developers/Build_Instructions#On_MacOSX_10.8.2C_CMake_hangs_forever|here]]. These versions are also known to work: exact version 20130121-g92bd8 [http://www.cmake.org/files/dev/cmake-2.8.10.20130121-g92bd8-Darwin-universal.tar.gz here] (or version >= 2.8.10.20130220 [http://www.cmake.org/files/dev/?C=M;O=D here]).<br />
--><br />
<br />
$ curl -O http://www.cmake.org/files/v2.8/cmake-2.8.11-Darwin64-universal.tar.gz<br />
$ tar -xzvf cmake-2.8.11-Darwin64-universal.tar.gz --strip-components=1<br />
<br />
$ CMake\ 2.8-11.app/Contents/bin/cmake --version<br />
cmake version 2.8.11<br />
<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
* [{{tool|download|svn}} Svn] >= 1.7<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 2.8.9<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.6.5<br />
--><br />
* XCode (for the SDK libs)<br />
** After installing XCode, install XCode command line developer tools: <br />
<pre><br />
xcode-select --install<br />
</pre><br />
* XQuartz - For Mac Os X >= 10.8 ([http://en.wikipedia.org/wiki/OS_X_Mountain_Lion Mountain Lion]) install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
* Qt 4 >= 4.8.5. We recommend you install the following two packages:<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1.dmg qt-opensource-mac-4.8.6-1.dmg]<br />
** Download and install [http://download.qt-project.org/official_releases/qt/4.8/4.8.6/qt-opensource-mac-4.8.6-1-debug-libs.dmg qt-opensource-mac-4.8.6-1-debug-libs.dmg]<br />
** For more details [[Documentation/{{documentation/version}}/Developers/Build_Instructions/Prerequisites/Qt#Mac|here]]<br />
<!--<br />
** Newer Xcode versions (e.g. 4.3.2) use clang as the default compiler and '''clang is not compatible with ITK version 3'''. You should use ITK version 4 with recent versions of Xcode.<br />
** Xcode with gcc should ork with either version of ITK.<br />
** OS X Mountain Lion: In Xcode 4.5 you now need to install command line tools (no longer included by default). Install within Xcode under the Xcode->Preferences->Downloads tab (otherwise git svn will give errors). Then you will need to install XQuartz (http://xquartz.macosforge.org) to get X11 (no longer a default in OS X).<br />
--><br />
<br />
====Mac OSX 10.11 (El Capitan)====<br />
<br />
XCode up to version 7 is known to work for Slicer compilation. XCode 8 breaks things on several levels for now. <br />
Remember to install XCode command line tools (see above) if a reinstall for XCode is needed. <br />
<br />
The standard Qt4 installers fail on this version and the 4.8.6 source code won't build. But [http://slicer-devel.65872.n3.nabble.com/incompatible-qt-4-8-6-with-OS-X-El-Capitan-td4035551.html as described on the slicer-devel mailing list] it is possible to install the [https://github.com/Homebrew/formula-patches/blob/master/qt/el-capitan.patch homebrew version of qt4 which patches it to work on El Capitan] (see below).<br />
<br />
* Install the '''OS''', '''Xcode''', and '''XQuartz''' (see MacOSX 10.10 above)<br />
* Install '''Qt4''' by running the following two commands:<br />
<pre><br />
brew install qt4<br />
xcode-select --install<br />
</pre><br />
* TCL does not build correctly on El Capitan as of 2015-12-03, so when building Slicer turn `Slicer_USE_PYTHONQT_WITH_TCL` off.<br />
<br />
==== Mac OSX 10.12 (Sierra) ====<br />
<br />
Similar to 10.11 (El Capitan), there are new issues with Qt4 (caused by Phonon).<br />
The GitHub user Cartr [https://github.com/Homebrew/homebrew-core/pull/5216 offered a patch to the brew team], and even though it was not integrated (the homebrew team decided to stop patching their recipe to encourage people to use Qt5), he [https://github.com/cartr/homebrew-qt4 turned his formula into a tap] that can be installed (see below).<br />
<br />
Follow instructions for 10.11 ''(Installing Xcode, XQuartz, run without TCL)'' but install '''Qt4''' like shown below instead:<br />
<pre><br />
brew install cartr/qt4/qt<br />
xcode-select --install<br />
</pre><br />
<br />
Confirmed with Xcode: <br />
* Version 8.1 (8B62) and cmake version 3.4.20151021-g8fbc8e<br />
* Version 8.3.3 and cmake 3.5.2<br />
<!--<br />
DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9 -DSlicer_USE_PYTHONQT_WITH_TCL:BOOL=OFF ../Slicer<br />
make -j `sysctl -n hw.ncpu`<br />
--><br />
<br />
<br />
==== Mac OSX 10.14 (Mojave) ====<br />
<br />
<small>Associated discussion topic is https://discourse.slicer.org/t/building-on-mac-10-14-mojave/4554</small><br />
<br />
* Install Qt 5.11.2 using [http://download.qt.io/official_releases/online_installers/qt-unified-mac-x64-online.dmg Qt Online Installer for macOS]<br />
<br />
* Install XCode:<br />
<br />
<pre><br />
xcode-select --install<br />
</pre><br />
<br />
* Explicitly set the SDK when running make<br />
<pre><br />
SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk make -j20<br />
</pre><br />
<br />
=== Windows ===<br />
<br />
==== Common Prerequisites ====<br />
* [{{tool|download|cmake}} CMake] >= 3.13.4<br />
* [{{tool|download|git}} Git] >= 1.7.10<br />
<!--<br />
*[[{{tool|logo|cmake}}|x16px]] [{{tool|download|cmake}} CMake] >= 3.13.4<br />
*[[{{tool|logo|git}}|x16px]] [{{tool|download|git}} Git] >= 1.7.10<br />
* [https://code.google.com/p/msysgit/downloads/list?can=3 ''Git-X.X.X-preview2013XXXX.exe''] recommended.<br />
--><br />
<!--<br />
** Use of [http://code.google.com/p/tortoisegit/ TortoiseGit] is optional.<br />
--><br />
** {{note}}CMake must be able to find ''git.exe'' and ''patch.exe''. If git is installed in the default location then they may be found there, but if they are not found then either add the folder that contains them to PATH environment variable; or set GIT_EXECUTABLE and Patch_EXECUTABLE as environment variables or as CMake variables at configure time.<br />
<br />
* Subversion (SVN) client: any SVN client that has command line tools<br />
** [http://www.sliksvn.com/en/download SlikSvn] <!-- or [http://www.cygwin.com cygwin's svn client]-->, or<br />
** [https://tortoisesvn.net/downloads.html TortoiseSVN] - make sure you install ''Command line client tools'' component (disabled by default)<br />
<br />
* NSIS (optional): Needed if packaging Slicer - Click [http://nsis.sourceforge.net/Download here] to download. Make sure you install the language packs.<br />
<br />
* Qt5: Download [https://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe qt-unified-windows-x86-online.exe] and install Qt along with <tt>qtscript</tt> and <tt>qtwebengine</tt> components. For building with VS2015, Qt version 5.10.x or older is required (newer versions do not compile with VS2015 due to QWebEngine minimum requirement is VS2017). For more details, read [[Documentation/Nightly/Developers/Build Instructions/Prerequisites/Qt5|step-by-step]] guide.<br />
<br />
==== Tested and recommended build environment ====<br />
<br />
* VS2015 (Visual Studio 14 2015 Win64)<br />
** Make sure you enable installation of component ''Programming languages / Visual C++ / Common Tools for Visual C++ 2015'' (in some distributions, this option is not enabled by default)<br />
** Installation of [https://msdn.microsoft.com/en-us/library/mt752379.aspx Cumulative Servicing Release for Microsoft Visual Studio 2015 Update 3 (KB3165756)] is required on some older VS2015 distributions<br />
* Qt 5.10<br />
* CMake >= 3.13.4<br />
<br />
==== Experimental/deprecated build environments ====<br />
* [https://visualstudio.microsoft.com/vs/community/ Visual Studio 2017 (Community or any other edition)] with VS2015 build tools (tested, works well)<br />
** When configuring Visual Studio installer, enable installation of component ''VC++ 2015.3 v14.00 (v140) toolset for desktop''<br />
** Visual Studio 2015 toolset must be set in CMake: ''Optional toolset to use (argument to -T)'' need to be set to ''v140''<br />
* Cygwin (untested, probably does not work)<br />
** Cygwin suite (building with cygwin gcc not supported, but the cygwin shell environment can be used to run git, svn, etc).</div>Pieperhttps://www.slicer.org/w/index.php?title=Documentation/4.x/Acknowledgments&diff=61009Documentation/4.x/Acknowledgments2019-04-15T13:10:03Z<p>Pieper: /* Grants */</p>
<hr />
<div>__NOTOC__<br />
{{documentation/acknowledgments-versionlist}}<br />
<br />
{|<br />
| class="toc" style="padding: 15px" align="left"|<b> <span class="mw-headline">Citing Slicer</span></b><br />
{{:{{FULLPAGENAME}}/CitingSlicer}}<br />
|}<br />
<br />
Slicer is made possible through contributions from an international community of scientists from a multitude of fields, including engineering and biomedicine. The following sections give credit to some of the major contributors to the 3D Slicer core effort. Each 3D Slicer extension has a separate acknowledgements page with information specific to that extension.<br />
<br />
''Ongoing Slicer support depends on YOU''<br />
* Please give [https://github.com/Slicer/Slicer the Slicer repository] [https://help.github.com/articles/about-stars/ a star on github]. This is an easy way to show thanks and it can help us qualify for useful services that are only open to widely recognized open projects.<br />
* Don't forget to cite our publications because that helps us get new grant funding.<br />
* If you find Slicer is helpful like the community please get involved. You don't need to be a programmer to help!<br />
<br />
==Major Contributors==<br />
{{Clear|right}}{{TOC right}}<br />
*Ron Kikinis: Principal Investigator<br />
*Steve Pieper: Chief Architect<br />
*Jean-Christophe Fillion-Robin: Lead Developer<br />
*Nicole Aucoin<br />
*Stephen Aylward<br />
*Andrey Fedorov<br />
*Noby Hata<br />
*Hans Johnson<br />
*Tina Kapur<br />
*Gabor Fichtinger<br />
*Andras Lasso<br />
*Csaba Pinter<br />
*Jim Miller<br />
*Sonia Pujol: Director of Training<br />
*Junichi Tokuda<br />
*Lauren O'Donnell<br />
*Andinet Enquobahrie<br />
*Beatriz Paniagua<br />
<br />
<i>Contributors are not only developers, but also individual helping to secure funding and move the platform forward.</i><br />
<br />
==Groups Contributing to the Core Engineering of Slicer in a Major Way==<br />
*SPL: Ron Kikinis, Nicole Aucoin, Lauren O'Donnell, Andrey Fedorov, Isaiah Norton, Sonia Pujol, Noby Hata, Junichi Tokuda<br />
*Isomics: Steve Pieper, Alex Yarmarkovich<br />
*Kitware: Jean-Christophe Fillion-Robin, Julien Finet, Will Schroeder, Stephen Aylward, Andinet Enquobahrie, Beatriz Paniagua, Matt McCormick, Johan Andruejol, Max Smolens, Alexis Girault, Sam Horvath<br />
*University of Iowa: Hans Johnson<br />
*GE: Jim Miller<br />
*Perk Lab, Queen's University: Andras Lasso, Tamas Ungi, Csaba Pinter, Gabor Fichtinger<br />
*Kapteyn Astronomical Institute, University of Groningen: Davide Punzo<br />
<br />
== Funding Sources ==<br />
Many of the activities around the Slicer effort are made possible through funding from public and private sources. The National Institutes of Health of the USA is a major contributor through a variety of competitive grants and contracts.<br />
=== Grants ===<br />
{| class="wikitable sortable"<br />
|-<br />
! '''Project Name''' || '''Grant Number and NIH Link''' || '''Title (and Project Page''') || '''Grant PIs''' || '''Start Date''' || '''End Date'''<br />
|-<br />
|3D Slicer for Radiation Therapy || [https://www.canarie.ca/ CANARIE RS-319 / 3D Slicer] || SlicerRT || Gabor Fichtinger, PerkLab, Queen's University || 2018-07-15 || 2020-09-30<br />
|-<br />
|3D Slicer for Image Guided Therapy || [https://www.canarie.ca/ CANARIE RS-214 / 3D Slicer] || SlicerIGT || Gabor Fichtinger, PerkLab, Queen's University || 2017-07-15 || 2020-09-30<br />
|-<br />
| Pediatric Valve Modeling-Slicer Heart || NA || PediatricValveModeling || Matthew Jolley || 2015-08-15 || 2020-08-15<br />
|-<br />
| DiffusionMRI || [https://projectreporter.nih.gov/project_info_details.cfm?aid=8855115&icde=27026518 2P41EB015898] || Image Guided Therapy Center || Clare M. Tempany || 2004-04-01 || 2020-06-30<br />
|-<br />
| Shape || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9123966&icde=31459429 1R01EB021391] || Shape Analysis Toolbox for Medical Image Computing Projects || Beatriz Paniagua|| 2016-09-19 || 2020-06-30<br />
|-<br />
| National Center for Image Guided Therapy || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9125821&icde=31485478 5P41EB015898] || Use of Slicer in a Wide Array of Image-guided Therapy Research for Prostate Cancer, Neurosurgery, and Image Navigation || Clare M. Tempany || 2004-04-01 || 2020-06-30<br />
|-<br />
| Slicer-Radiomics-U24 || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8875289&icde=27050248 1U24CA194354] || Quantitative Radiomics System Decoding the Tumor Phenotype || Hugo Aerts || 2015-04-01 || 2020-03-31<br />
|-<br />
| Slicer-Radiomics-U01 || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8799943&icde=27026470 1U01CA190234] || Genotype and Imaging Phenotype Biomarkers in Lung Cancer || Hugo Aerts|| 2015-01-01 || 2019-12-01<br />
|-<br />
| Tools to Analyze Morphology and Spatially Mapped Molecular Data || [https://projectreporter.nih.gov/project_info_details.cfm?aid=9127923&icde=31460433 5U24CA180924] || Tools to Analyze Morphology and Spatially Mapped Molecular Data|| Joel Saltz || 2014-09-01 || 2019-08-31<br />
|-<br />
| NIRView (Dartmouth) || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8828624&icde=27036841 5R01CA184354] || MRI Fluorescence Tomography For Quantifying Tumor Receptor Concentration ''in vivo'' || Scott C. Davis || 2014-04-01 || 2019-02-28<br />
|-<br />
| VROrthognathic || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9465772&icde=36620728&ddparam=&ddvalue=&ddsub=&cr=2&csb=default&cs=ASC&pball= R43DE027595] || High-Fidelity Virtual Reality Trainer for Orthognathic Surgery || Beatriz Paniagua|| 2017-09-07 || 2018-09-06<br />
|-<br />
| CMF || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9111256&icde=31459429 R21DE025306] || Textural Biomarkers of Arthritis for the Subchondral Bone in the Temporomandibular Joint || Beatriz Paniagua|| 2016-09-01 || 2018-08-31<br />
|-<br />
| HD_SHAPEANALSS || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8462842&icde=27164806 1U01NS082086] || 4D Shape Analysis for Modeling Spatiotemporal Change Trajectories in Huntington’s Disease|| Guido Gerig || 2012-09-28 || 2018-08-31<br />
|-<br />
| QIICR || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8911287&icde=27026906 U24 CA180918] || [http://qiicr.org Quantitative Image Informatics for Cancer Research (QIICR)] || Ron Kikinis, Andrey Fedorov || 2013-09-04 || 2018-08-31<br />
|-<br />
| SlicerDMRI Diffusion MRI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8971083&icde=27026834 1U01CA199459] || Open Source Diffusion MRI Technology for Brain Cancer Research || Lauren Jean O'Donnell|| 2015-09-22 || 2018-07-31<br />
|-<br />
| HD_KIDS || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8900362&icde=27164764 5R01NS055903] || Growth and Development of the Striatum in Huntington's Disease || Peggy Nopoulos || 2009-03-01 || 2018-07-31<br />
|-<br />
| DiffusionMRI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8890837&icde=27036647 5P41EB015902] || Neuroimaging Analysis Center (NAC) || Ron Kikinis || 2013-08-01 || 2018-05-31<br />
|-<br />
| Neuroimage Analysis Center || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9115586&icde=31485398 4P41EB015902] || Application of Slicer to Image-guided Neurosurgery and other Applications through Steered Computation and Image Navigation Databases || Ron Kikinis || 2013-08-01 || 2018-05-31<br />
|-<br />
| Craniosynostosis || [https://projectreporter.nih.gov/project_info_description.cfm?aid=9141675&icde=31459353 2R42HD081712] || Image-guided Planning System for Skull Correction in Children with Craniosynostosis: Phase II || Marius George Linguraru || 2016-05-01 || 2018-04-30<br />
|-<br />
| DWI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8817256&icde=27036729 R01CA160902] || Advancement and Validation of Prostate Diffusion and Spectroscopic MRI || Stephan E. Maier|| 2012-04-01 || 2018-02-28<br />
|-<br />
| CMF || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8576556&icde=18353487 1R01DE024450] || Quantification of 3D Bony Changes In Temporomandibular Joint Osteoarthritis || Lucia Cevidanes|| 2013-09-10 || 2017-08-31<br />
|-<br />
| PET/CT Calibration Phantom || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8979242&icde=27036988 2R42CA167907] || Calibrated Methods for Quantitative PET/CT Imaging Phase II || Paul E. Kinahan || 2012-05-01 || 2017-07-31<br />
|-<br />
| HD_TRACKON || NA || TRACK-ON HD || Sarah Tabrizi|| 2012-01-01 || 2016-12-31<br />
|-<br />
| Slicer-RT || NA || Cancer Care Ontario Applied Cancer Research Unit, Canada || Gabor Fichtinger, PerkLab, Queen's University || 2011-01-01 || 2016-12-31<br />
|-<br />
| Slicer-RT || NA || [http://ocairo.technainstitute.com/ Ontario Consortium for Adaptive Interventions in Radiation Oncology, Canada] || David Jaffray, Princess Margaret Hospital, Toronto || 2011-01-01 || 2016-12-31<br />
|-<br />
| HD_TRAJECTORY || NA || Developing a Robust Segmentation Pipeline that Allows for Consistent Trajectory Estimation of HD Gene Positive Individuals across Multiple Longitudinal MRI Sites || Eun Young Kim|| 2014-11-01 || 2016-10-31<br />
|-<br />
| Craniosynostosis || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8778815&icde=27036063 1R41HD081712] || Image-Guided Planning System for Skull Correction in Children with Craniosynostos || Marius George Linguraru || 2014-09-26 || 2016-08-31<br />
|-<br />
| HD_PREDICT || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8338456&icde=27164778 5R01NS040068] || Neurobiological Predictors of Huntington's Disease (PREDICT-HD) || Jane Paulsen || 2000-08-01 || 2016-08-31<br />
|-<br />
| PET-CT guided needle biopsy || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8863934&icde=27037113 3R42CA153488] || Improving Liver Lesion Biopsy in the CT Suite Through Fusion with PET Images || Kevin R. Cleary || 2012-09-01 || 2016-08-01<br />
|-<br />
| OrthognathicTrac || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8710950&icde=27036891 1R43DE024334] || Real-Time Image Guidance for Improved Orthognathic Surgery || Andinet A. Enquobahrie|| 2014-08-05 || 2016-07-31<br />
|-<br />
| PediatricRadiologicDecisionSupport || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8272742&icde=13552329 1R01EB014947] || Mi2B2 Enabled Pediatric Radiological Decision Support || Shawn N. Murphy|| 2012-08-01 || 2016-07-31<br />
|-<br />
| ProstateBRP || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8906771&icde=27026518 5R01CA111288 ] || Enabling Technologies for MRI-guided Prostate Interventions || Clare M. Tempany || 2004-12-01 || 2016-07-01<br />
|-<br />
| ProstateQIN || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8707214&icde=27026645 5U01CA151261] || Quantitative MRI of Prostate Cancer as a Biomarker and Guide for Treatment || Fiona M. Fennessy || 2010-09-01 || 2016-07-01<br />
|-<br />
| HD_GENETICS || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8596213&icde=27164895 1U01NS082074] || Imaging and Genetics in Huntington's Disease || Turner Calhoun || 2013-07-01 || 2016-06-30<br />
|-<br />
| HD_PET || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8529927&icde=27164835 1U01NS083173] || Brain Network Imaging: A Novel Biomarker for Preclinical Huntington’s Disease || Andrew Feigin || 2013-07-01 || 2016-06-30<br />
|-<br />
| TubeTK || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8384153&icde=27037397 1R01CA170665] || [http://tubetk.org Micro-Tumor Detection by Quantifying Tumor-induced Vascular Abnormalities] || Paul A. Dayton|| 2012-09-01 || 2016-06-01<br />
|-<br />
| HD_WHITEMATTER || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8652000&icde=27164795 1U01NS083223] || Characterization of White Matter in Huntington’s Disease using Diffusion MRI || Carl-Fredrik Westin|| 2014-01-01 || 2015-12-31<br />
|-<br />
| Slicer-RT || NA || Cancer Care Ontario Research Chair, Canada || Gabor Fichtinger, PerkLab, Queen's University || 2010-01-01 || 2015-12-31<br />
|-<br />
| HD_FMRI_DWI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8462829&icde=27164863 1U01NS082083] || Functional Connectivity in Pre-manifest Huntington’s Disease || Stephen Mark Rao || 2012-09-26 || 2015-08-31<br />
|-<br />
| Duke Prostate Registration || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8905274&icde=27036277 1R41CA196565] || Prostate Cancer Assessment via Integrated 3D ARFI Elasticity Imaging and Multi-Parametric MRI || Mark L. Palmeri, Matthew M. McCormick|| 2015-04-01 || 2015-04-01<br />
|-<br />
| TubeTK || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8472102&icde=27037328 1R43EB016621] || [http://tubetk.org In-Field Fast Procedure Support and Automation] || Stephen R. Aylward || 2013-05-01 || 2015-04-01<br />
|-<br />
| TubeTK || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8453963&icde=27037364 1R41NS081792] || [http://tubetk.org Multimodality Image-based Assessment System for Traumatic Brain Injury] || Stephen R. Aylward || 2013-01-01 || 2014-12-01<br />
|-<br />
| PET-CT guided needle biopsy || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8390856&icde=27037039 2R42CA153488] || Improving Liver Lesion Biopsy in the CT Suite through Fusion with PET Images || Kevin R. Cleary || 2012-09-01 || 2014-08-01<br />
|-<br />
| TubeTK || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8252988&icde=27037450 1R43CA165621] || [http://tubetk.org Quantitative Ultrasound Analysis of Vascular Morphology for Cancer Assessment] || Stephen R. Aylward || 2012-12-01 || 2014-08-01<br />
|-<br />
| HD_SUBCORTICAL_SHAPE || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8462830&icde=27164813 5U01NS082085] || Basal Ganglia Shape Analysis and Circuitry in Huntington's Disease ||Michael Miller, Christopher Ross|| 2012-09-26 || 2014-07-31<br />
|-<br />
| HD_DWI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8501010&icde=27164945 5U54EB005149] || National Alliance for Medical Image Computing (NA-MIC)|| Ron Kikinis || 2010-09-30 || 2014-06-30<br />
|-<br />
| HD_FMRI || [https://projectreporter.nih.gov/project_info_description.cfm?aid=8077226&icde=27164732 5R01NS054893] || Cognitive and Functional Brain Changes in Preclinical Huntington's Disease (HD) || Jane Paulsen || 2007-05-15 || 2013-04-30<br />
|-<br />
| PET-CT guided needle biopsy || [https://projectreporter.nih.gov/project_info_description.cfm?aid=7999618&icde=27037084 1R41CA153488] || Improving Liver Lesion Biopsy in the CT Suite through Fusion with PET Images || Kevin R. Cleary || 2010-07-01 || 2012-06-01<br />
|-<br />
| Biological Morphometry || [https://www.nsf.gov/awardsearch/showAward?AWD_ID=1759883&HistoricalAwards=false NSF 1759883] || Collaborative Proposal: ABI Development: An Integrated Platform for Retrieval, Visualization and Analysis of 3D Morphology from Digital Biological Collections || Murat Maga || 2018-09-01 || 2021-08-31<br />
|}<br />
<br />
<small>For more information on how this table was created, see [[Documentation/Nightly/Acknowledgments/Grants|this page]].</small><br />
<br />
{{:{{FULLPAGENAME}}/CommercialPartners}} <br />
<br />
{{:{{FULLPAGENAME}}/CommercialProducts}}</div>Pieperhttps://www.slicer.org/w/index.php?title=User:Sunderlandkyl&diff=60979User:Sunderlandkyl2019-04-10T19:32:58Z<p>Pieper: Creating user page for new user.</p>
<hr />
<div>Software developer in the Laboratory for Percutaneous Surgery at Queen's University in Kingston Ontario, Canada.<br />
BComp - Biomedical Computing @ Queen's University (2015)<br />
MSc - School of Computing @ Queen's University (2017). Thesis: Fractional Labelmap Representation of Anatomical Structures (http://hdl.handle.net/1974/22677)<br />
<br />
PerkLab page: http://perk.cs.queensu.ca/users/sunderland<br />
GitHub: https://github.com/Sunderlandkyl</div>Pieper