Source code for xmipp3.protocols.protocol_preprocess.protocol_create_mask3d

# -*- coding: utf-8 -*-
# **************************************************************************
# *
# * Authors:     Carlos Oscar S. Sorzano (coss@cnb.csic.es)
#                J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# *
# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 2 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************

from pwem import emlib
from pwem.emlib.image import ImageHandler

from pyworkflow.protocol.params import PointerParam, StringParam, PathParam

from pwem.objects import VolumeMask
from pwem.protocols import ProtCreateMask3D


from xmipp3.convert import getImageLocation
from .geometrical_mask import *


SOURCE_VOLUME=0
SOURCE_GEOMETRY=1
SOURCE_FEATURE_FILE=2

OPERATION_THRESHOLD=0
OPERATION_SEGMENT=1
OPERATION_POSTPROCESS=2

SEGMENTATION_VOXELS=0
SEGMENTATION_AMINOACIDS=1
SEGMENTATION_DALTON=2
SEGMENTATION_AUTOMATIC=3

MORPHOLOGY_DILATION=0
MORPHOLOGY_EROSION=1
MORPHOLOGY_CLOSING=2
MORPHOLOGY_OPENING=3


[docs]class XmippProtCreateMask3D(ProtCreateMask3D, XmippGeometricalMask3D): """ Create a 3D mask. The mask can be created with a given geometrical shape (Sphere, Box, Cylinder...) or it can be obtained from operating on a 3d volume or a previous mask. AI Generated ## Overview The Create 3D Mask protocol generates a three-dimensional mask. 3D masks are used in cryo-EM workflows to define the molecular region, remove background, restrict refinement or validation to a region of interest, create soft boundaries, or prepare maps for local processing. This protocol can create a mask from several sources: - an input volume; - a geometrical shape; - a feature file. After the initial mask is created, the protocol can optionally post-process it by removing small components, keeping only the largest component, applying symmetry, applying morphological operations, inverting the mask, or smoothing its borders. The main output is a Scipion 3D volume mask. ## Inputs and General Workflow The protocol first creates an initial mask according to the selected source. If the source is **Volume**, the mask is created from an input map by thresholding, segmentation, or direct post-processing. If the source is **Geometry**, the mask is created from a user-defined geometrical shape such as a sphere, box, crown, cylinder, Gaussian, raised cosine, or raised crown. If the source is **Feature File**, the mask is generated from an Xmipp phantom feature file. After creation, optional post-processing operations are applied. Finally, the mask is registered in Scipion with the appropriate sampling rate. ## Mask Source The **Mask source** parameter defines how the initial mask is created. There are three options: **Volume** creates the mask from an existing 3D volume. **Geometry** creates the mask from a user-defined geometrical shape. **Feature File** creates the mask from a phantom feature file describing one or more shapes. The best option depends on the use case. Volume-derived masks are useful when the mask should follow density. Geometrical masks are useful for simple, reproducible shapes. Feature files are useful for more complex synthetic masks. ## Creating a Mask from a Volume When **Volume** is selected as the source, the **Input volume** parameter defines the map used to create the mask. The protocol supports three operations: - **Threshold**; - **Segment**; - **Only postprocess**. The sampling rate of the output mask is copied from the input volume. This mode is useful when the user wants the mask to follow the density present in an experimental or processed map. ## Threshold Operation The **Threshold** operation creates a binary mask from the input volume. The **Threshold** parameter defines the gray-value cutoff. Voxels with values below the threshold are set to 0, and the remaining voxels become part of the mask. This is a simple and common way to create a mask from density. The threshold should be chosen carefully. If it is too high, weak but real density may be excluded. If it is too low, background noise may be included. ## Segmentation Operation The **Segment** operation creates a mask by selecting a region corresponding to a desired size or mass. The available segmentation types are: **Number of voxels**, where the user specifies the target number of voxels. **Number of aminoacids**, where the user specifies the approximate number of amino acids and the protocol uses the sampling rate to estimate the mask. **Dalton mass**, where the user specifies the molecular mass in daltons. **Automatic**, where the protocol uses Otsu thresholding. Segmentation is useful when the expected molecular size is known and the user wants a mask that reflects that approximate amount of density. ## Only Postprocess Operation The **Only postprocess** operation copies the input volume as the starting mask. No thresholding or segmentation is applied at the creation step. The selected post-processing operations are then applied to the copied volume. This option is useful when the input is already a mask, or when the user wants to apply additional post-processing to an existing mask-like volume. ## Creating a Mask from Geometry When **Geometry** is selected as the source, the protocol creates a mask from a geometrical shape. The user defines the mask size, sampling rate, shape, and shape-specific parameters. The output mask is cubic, with dimensions: \[ \text{size} \times \text{size} \times \text{size} \] This mode does not require an input volume. ## Sampling Rate for Geometry or Feature File The **Sampling Rate** parameter is used when the mask source is **Geometry** or **Feature File**. It defines the physical voxel size of the generated mask, in angstroms per pixel. This value should match the volume or particles that will later use the mask. ## Mask Size The **Mask size** parameter is used for geometrical masks. It defines the cubic dimensions of the generated mask in voxels. For example, a size of 128 creates a 128 × 128 × 128 voxel mask. The mask size should match the box size of the maps where the mask will be used. ## Geometrical Mask Types The available 3D geometrical mask types are: - Sphere; - Box; - Crown; - Cylinder; - Gaussian; - Raised cosine; - Raised crown. Each type exposes its own parameters. These parameters are expressed in pixels or voxels. ## Sphere Mask A **Sphere** mask creates a spherical region. The **Radius** parameter defines the sphere radius in pixels. If the radius is set to **-1**, the protocol uses half the mask size. Sphere masks are useful for approximately globular particles or for simple central support regions. ## Box Mask A **Box** mask creates a cubic or rectangular support region. The **Box size** parameter defines the box size. If it is set to **-1**, the protocol uses half the mask size. This mask can be useful when the region of interest is approximately rectangular or when the user wants to keep a central cubic region. ## Crown Mask A **Crown** mask creates a shell-like region between an inner and an outer radius. The relevant parameters are: - **Inner radius**; - **Outer radius**. If the outer radius is set to **-1**, the protocol uses half the mask size. Crown masks are useful for selecting a radial shell while excluding the central region. ## Cylinder Mask A **Cylinder** mask creates a cylindrical region. The relevant parameters are: - **Radius**; - **Height**. If the radius is set to **-1**, the protocol uses half the mask size. If the height is set to **-1**, the protocol uses the full mask size. Cylinder masks are useful for elongated particles, filaments, or approximate helical support regions. ## Gaussian Mask A **Gaussian** mask creates a smooth three-dimensional Gaussian weighting function. The **Sigma** parameter defines the Gaussian width in pixels. If sigma is set to **-1**, the protocol uses one sixth of the mask size. Gaussian masks are useful when the user wants smooth weighting rather than a hard binary support. ## Raised Cosine Mask A **Raised cosine** mask creates a smooth radial transition between an inner and an outer radius. This is useful when the user wants to avoid sharp mask edges, which can introduce Fourier artifacts. The relevant parameters are: - **Inner radius**; - **Outer radius**. ## Raised Crown Mask A **Raised crown** mask creates a crown-like mask with smooth transitions at both borders. The relevant parameters are: - **Inner radius**; - **Outer radius**; - **Border decay**. The border decay controls the falloff at the crown boundaries. ## Shift Center The **Shift center of the mask?** option allows the user to move the center of a geometrical mask away from the center of the box. When enabled, the user provides X, Y, and Z center offsets in pixels. This is useful when the region of interest is not centered in the volume. ## Creating a Mask from a Feature File When **Feature File** is selected, the protocol creates the mask using an Xmipp phantom feature file. The feature file describes one or more geometrical features, their operations, densities, centers, and shape-specific parameters. Examples of supported feature types include spheres, blobs, Gaussians, cylinders, double cylinders, cubes, ellipsoids, and cones. This option is useful for complex synthetic masks that are easier to describe in a feature file than through the graphical form. ## Remove Small Objects The **Remove small objects** option removes connected components smaller than a selected size. The **Minimum size** parameter defines the minimum component size in voxels. This is useful for cleaning masks created from noisy thresholding or segmentation, where small isolated islands may appear outside the main structure. The input mask should be binary when using this option. ## Keep Largest Component The **Keep largest component** option keeps only the largest connected component of the mask. This is useful when a thresholded or segmented mask contains several separated regions, and the user wants to keep only the main molecular component. The input mask should be binary. ## Symmetrize Mask The **Symmetrize mask** option applies a selected symmetry group to the mask. The **Symmetry group** parameter defines the Xmipp symmetry, with **c1** meaning no symmetry. If the mask was created by thresholding or segmentation, the protocol binarizes it again after symmetrization. Symmetry should be used only when the biological object and the intended mask are expected to be symmetric. ## Morphological Operation The **Apply morphological operation** option applies a binary morphological operation to the mask. The available operations are: **Dilation**, which expands the white region. **Erosion**, which shrinks the white region. **Closing**, which performs dilation followed by erosion and can remove black holes or gaps. **Opening**, which performs erosion followed by dilation and can remove small white objects. The **Structural element size** controls the strength of the operation. Larger values produce stronger effects. ## Invert the Mask The **Invert the mask** option swaps the inside and outside of the mask. The protocol multiplies the mask by -1 and then adds 1, converting 1-valued regions to 0 and 0-valued regions to 1. This is useful when the user wants to select the complement of the current mask. ## Smooth Borders The **Smooth borders** option smooths the mask by convolving it with a Gaussian. The **Gaussian sigma** parameter defines the width of the smoothing kernel in pixels. After smoothing, negative values are clipped to zero. Smoothing is often useful for reducing sharp mask boundaries, which can create artifacts in Fourier-space operations. ## Output Mask The main output is **outputMask**. This output is a Scipion **VolumeMask** object pointing to the generated `mask.mrc` file. If the source is **Volume**, the output mask uses the sampling rate of the input volume. If the source is **Geometry** or **Feature File**, it uses the sampling rate provided by the user. The protocol also writes the sampling rate into the image header. ## Interpreting the Output The output should be interpreted as a 3D mask created by the selected source and post-processing operations. A mask created from a volume reflects density and thresholding or segmentation choices. A geometrical mask reflects user-defined shape parameters. A feature file mask reflects the external feature description. The mask may be binary or smoothly weighted, depending on the selected creation and post-processing operations. ## Practical Recommendations Use volume thresholding for simple density-derived masks. Use segmentation when the approximate molecular size or mass is known. Use geometrical masks when a simple reproducible shape is sufficient. Use feature files for complex synthetic masks. Remove small objects and keep the largest component when masks contain unwanted islands. Use smoothing before Fourier-space operations or refinement steps where sharp mask edges may cause artifacts. Set the sampling rate and box size to match the volume where the mask will be used. Inspect the mask visually before using it in downstream protocols. Avoid masks that are too tight, because they may remove real density. Avoid masks that are too loose, because they may include solvent and noise. ## Final Perspective Create 3D Mask is a flexible mask-generation and mask-processing protocol. For biological users, its value is that it can create masks from experimental density, from known geometrical shapes, or from feature-file descriptions, and then refine those masks with common post-processing operations. The resulting mask can be used in reconstruction, refinement, validation, local-resolution estimation, subtraction, sharpening, or visualization workflows where defining the molecular region is essential. """ _label = 'create 3d mask' #--------------------------- DEFINE param functions -------------------------------------------- def _defineParams(self, form): form.addSection(label='Mask generation') form.addParam('source', EnumParam, default=SOURCE_VOLUME, choices=['Volume', 'Geometry', 'Feature File' ], label='Mask source') # For volume sources isVolume = 'source==%d' % SOURCE_VOLUME form.addParam('inputVolume', PointerParam, pointerClass="Volume", label="Input volume", allowsNull=True, condition=isVolume, help="Select the volume that will be used to create the mask") form.addParam('volumeOperation', EnumParam, default=OPERATION_THRESHOLD, choices=['Threshold', 'Segment', 'Only postprocess'], label='Operation', condition=isVolume) #TODO: add wizard form.addParam('threshold', FloatParam, default=0.0, condition='volumeOperation==%d and %s' % (OPERATION_THRESHOLD, isVolume), label='Threshold', help="Select the threshold. Gray values lesser than the threshold" \ "will be set to zero, otherwise will be one (mask area).") isSegmentation = 'volumeOperation==%d and %s' % (OPERATION_SEGMENT, isVolume) form.addParam('segmentationType', EnumParam, default=SEGMENTATION_DALTON, condition=isSegmentation, label='Segmentation type', choices=['Number of voxels', 'Number of aminoacids', 'Dalton mass', 'Automatic']) form.addParam('nvoxels', IntParam, condition='%s and segmentationType==%d' % (isSegmentation, SEGMENTATION_VOXELS), label='Number of voxels') form.addParam('naminoacids', IntParam, condition='%s and segmentationType==%d' % (isSegmentation, SEGMENTATION_AMINOACIDS), label='Number of aminoacids') form.addParam('dalton', FloatParam, condition='%s and segmentationType==%d' % (isSegmentation, SEGMENTATION_DALTON), label='Mass (Da)') # For geometrical sources form.addParam('samplingRate', FloatParam, default=1, condition='source==%d or source==%d' % (SOURCE_GEOMETRY, SOURCE_FEATURE_FILE), label="Sampling Rate (Å/px)") XmippGeometricalMask3D.defineParams(self, form, isGeometry='source==%d' % SOURCE_GEOMETRY, addSize=True) # Feature File isFeatureFile = 'source==%d' % SOURCE_FEATURE_FILE form.addParam('featureFilePath', PathParam, condition=isFeatureFile, label="Feature File", help="""Create a mask using a feature file. Follows an example of feature file # XMIPP_STAR_1 * # Type of feature (sph, blo, gau, Cyl, dcy, cub, ell, con)(Required) # The operation after adding the feature to the phantom (+/=) (Required) # The feature density (Required) # The feature center (Required) # The vector for special parameters of each vector (Required) # Sphere: [radius] # Blob : [radius alpha m] Gaussian : [sigma] # Cylinder : [xradius yradius height rot tilt psi] # DCylinder : [radius height separation rot tilt psi] # Cube : [xdim ydim zdim rot tilt psi] # Ellipsoid : [xradius yradius zradius rot tilt psi] # Cone : [radius height rot tilt psi] data_block1 _dimensions3D '34 34 34' _phantomBGDensity 0. _scale 1. data_block2 loop_ _featureType _featureOperation _featureDensity _featureCenter _featureSpecificVector sph + 1 '3.03623188 0.02318841 -5.04130435' '7' """ ) # Postprocessing form.addSection(label='Postprocessing') form.addParam('doSmall', BooleanParam, default=False, label='Remove small objects', help="To remove small clusters of points. " "The input mask has to be binary.") form.addParam('smallSize', IntParam, default=50, label='Minimum size',condition="doSmall", help='Connected components whose size is smaller than ' 'this number in voxels will be removed') form.addParam('doBig', BooleanParam, default=False, label='Keep largest component', help="To keep cluster greater than a given size. The input mask has to be binary") form.addParam('doSymmetrize', BooleanParam, default=False, label='Symmetrize mask') form.addParam('symmetry', StringParam, default='c1', label='Symmetry group',condition="doSymmetrize", help="To obtain a symmetric mask. See https://i2pc.github.io/docs/Utils/Conventions/index.html#symmetry \n" "for a description of the symmetry groups format. \n" "If no symmetry is present, give c1") form.addParam('doMorphological', BooleanParam, default=False, label='Apply morphological operation', help="Dilation (dilate white region). \n" "Erosion (erode white region). \n" "Closing (Dilation+Erosion, removes black spots). \n" "Opening (Erosion+Dilation, removes white spots). \n") form.addParam('morphologicalOperation', EnumParam, default=MORPHOLOGY_DILATION, condition="doMorphological", choices=['dilation', 'erosion', 'closing', 'opening'], label='Operation') form.addParam('elementSize', IntParam, default=1, condition="doMorphological", label='Structural element size', help="The larger this value, the more the effect will be noticed") form.addParam('doInvert', BooleanParam, default=False, label='Invert the mask') form.addParam('doSmooth', BooleanParam, default=False, label='Smooth borders', help="Smoothing is performed by convolving the mask with a Gaussian.") form.addParam('sigmaConvolution', FloatParam, default=2, condition="doSmooth", label='Gaussian sigma (px)', help="The larger this value, the more the effect will be noticed") #--------------------------- INSERT steps functions ------------------------ def _insertAllSteps(self): self.maskFile = self._getPath('mask.mrc') if self.source == SOURCE_VOLUME: self._insertFunctionStep(self.createMaskFromVolumeStep) elif self.source == SOURCE_GEOMETRY: self.inputVolume.set(None) self._insertFunctionStep(self.createMaskFromGeometryStep) elif self.source == SOURCE_FEATURE_FILE: self._insertFunctionStep(self.createMaskFromFeatureFile) self._insertFunctionStep(self.postProcessMaskStep) self._insertFunctionStep(self.createOutputStep) #--------------------------- STEPS functions -------------------------------
[docs] def createMaskFromVolumeStep(self): volume = self.inputVolume.get() fnVol = getImageLocation(volume) if fnVol.endswith(".mrc"): fnVol += ":mrc" Ts = volume.getSamplingRate() if self.volumeOperation == OPERATION_THRESHOLD: self.runJob("xmipp_transform_threshold", "-i %s -o %s --select below %f --substitute binarize" % (fnVol, self.maskFile, self.threshold.get())) elif self.volumeOperation == OPERATION_SEGMENT: args="-i %s -o %s --method " % (fnVol, self.maskFile) if self.segmentationType == SEGMENTATION_VOXELS: args += "voxel_mass %d" % (self.nvoxels.get()) elif self.segmentationType == SEGMENTATION_AMINOACIDS: args += "aa_mass %d %f" % (self.naminoacids.get(), Ts) elif self.segmentationType == SEGMENTATION_DALTON: args += "dalton_mass %d %f" % (self.dalton.get(), Ts) else: args += "otsu" self.runJob("xmipp_volume_segment", args) elif self.volumeOperation == OPERATION_POSTPROCESS: ImageHandler().convert(fnVol,self.maskFile) return [self.maskFile]
[docs] def createMaskFromGeometryStep(self): # Create empty volume file with desired dimensions size = self.size.get() emlib.createEmptyFile(self.maskFile, size, size, size) # Create the mask args = '-i %s ' % self.maskFile args += XmippGeometricalMask3D.argsForTransformMask(self,size) args += ' --create_mask %s' % self.maskFile self.runJob("xmipp_transform_mask", args) return [self.maskFile]
[docs] def createMaskFromFeatureFile(self): featFileName = self.featureFilePath.get() self.runJob("xmipp_phantom_create", "-i %s -o %s" % (featFileName, self.maskFile) ) return [self.maskFile]
[docs] def postProcessMaskStep(self): if self.doSmall: self.runJob("xmipp_transform_morphology","-i %s --binaryOperation removeSmall %d" % (self.maskFile,self.smallSize.get())) if self.doBig: self.runJob("xmipp_transform_morphology","-i %s --binaryOperation keepBiggest" % self.maskFile) if self.doSymmetrize: if self.symmetry!='c1': self.runJob("xmipp_transform_symmetrize","-i %s --sym %s --dont_wrap"%(self.maskFile,self.symmetry.get())) if self.volumeOperation==OPERATION_THRESHOLD or self.volumeOperation==OPERATION_SEGMENT: self.runJob("xmipp_transform_threshold", "-i %s --select below 0.5 --substitute binarize" % self.maskFile) if self.doMorphological: self.runJob("xmipp_transform_morphology","-i %s --binaryOperation %s --size %d" % (self.maskFile, self.getEnumText('morphologicalOperation'), self.elementSize.get())) if self.doInvert: self.runJob("xmipp_image_operate","-i %s --mult -1"%self.maskFile) self.runJob("xmipp_image_operate","-i %s --plus 1"%self.maskFile) if self.doSmooth: self.runJob("xmipp_transform_filter","-i %s --fourier real_gaussian %f"%(self.maskFile,self.sigmaConvolution.get())) self.runJob("xmipp_transform_threshold", "-i %s --select below 0 --substitute value 0" % self.maskFile)
[docs] def createOutputStep(self): volMask = VolumeMask() volMask.setFileName(self.maskFile) if self.source==SOURCE_VOLUME: Ts = self.inputVolume.get().getSamplingRate() else: Ts = self.samplingRate.get() volMask.setSamplingRate(Ts) self.runJob("xmipp_image_header","-i %s --sampling_rate %f"%(self.maskFile,Ts)) self._defineOutputs(outputMask=volMask) if self.source==SOURCE_VOLUME: self._defineSourceRelation(self.inputVolume, self.outputMask)
#--------------------------- INFO functions -------------------------------------------- def _summary(self): messages = [] messages.append("*Mask creation*") if self.source==SOURCE_VOLUME: if self.volumeOperation==OPERATION_THRESHOLD: messages.append(" Thresholding %f"%self.threshold.get()) elif self.volumeOperation==OPERATION_SEGMENT: if self.segmentationType==SEGMENTATION_AUTOMATIC: messages.append(" Automatically segmented") else: m=" Segmented to a mass of " if self.segmentationType==SEGMENTATION_VOXELS: m+="%d voxels"%(int(self.nvoxels.get())) elif self.segmentationType==SEGMENTATION_AMINOACIDS: m+="%d aminoacids"%(int(self.naminoacids.get())) elif self.segmentationType==SEGMENTATION_DALTON: m+="%d daltons"%(int(self.dalton.get())) messages.append(m) elif self.source==SOURCE_GEOMETRY: size = self.size.get() messages.append(" Mask of size: %d x %d x %d"%(size,size,size)) messages += XmippGeometricalMask3D.summary(self) messages.append("*Mask processing*") if self.doSmall: messages.append(" Removing components smaller than %d" % self.smallSize.get()) if self.doBig: messages.append(" Keeping largest component") if self.doSymmetrize: messages.append(" Symmetrized %s" % self.symmetry.get()) if self.doMorphological: messages.append(" Morphological operation: %s" % self.getEnumText('morphologicalOperation')) if self.doInvert: messages.append(" Inverted") if self.doSmooth: messages.append(" Smoothed (sigma=%f)"%self.sigmaConvolution.get()) return messages def _citations(self): if (self.source == SOURCE_VOLUME and self.volumeOperation == OPERATION_SEGMENT and self.segmentationType==SEGMENTATION_AUTOMATIC): return ['Otsu1979'] def _methods(self): messages = [] if self.inputVolume.get() is None: return messages messages.append("*Mask creation*") if self.source == SOURCE_VOLUME: messages.append('We processed the volume %s.'%self.inputVolume.get().getNameId()) if self.volumeOperation == OPERATION_THRESHOLD: messages.append("We thresholded it to a gray value of %f. "%self.threshold.get()) elif self.volumeOperation == OPERATION_SEGMENT: if self.segmentationType == SEGMENTATION_AUTOMATIC: messages.append("We automatically segmented it using Otsu's method [Otsu1979]") else: m="We segmented it to a mass of " if self.segmentationType == SEGMENTATION_VOXELS: m+="%d voxels"%(int(self.nvoxels.get())) elif self.segmentationType == SEGMENTATION_AMINOACIDS: m+="%d aminoacids"%(int(self.naminoacids.get())) elif self.segmentationType == SEGMENTATION_DALTON: m+="%d daltons"%(int(self.dalton.get())) messages.append(m) elif self.source == SOURCE_GEOMETRY: size=self.size.get() messages.append("We created a mask of size: %d x %d x %d voxels. " % (size, size, size)) messages += XmippGeometricalMask3D.methods(self) if self.doSmall: messages.append("We removed components smaller than %d voxels." % (self.smallSize.get())) if self.doBig: messages.append("We kept the largest component. ") if self.doSymmetrize: messages.append("We symmetrized it as %s. "%self.symmetry.get()) if self.doMorphological: messages.append("Then, we applied a morphological operation, concisely, a %s. " % self.getEnumText('morphologicalOperation')) if self.doInvert: messages.append("We inverted the mask. ") if self.doSmooth: messages.append("And, we smoothed it (sigma=%f voxels)." % self.sigmaConvolution.get()) if self.hasAttribute('outputMask'): messages.append('We refer to the output mask as %s.' % self.outputMask.getNameId()) return messages def _validate(self): errors = [] if self.source == SOURCE_VOLUME and not self.inputVolume.get(): errors.append("You need to select an input volume") return errors