# **************************************************************************
# *
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# * Josue Gomez Blanco (josue.gomez-blanco@mcgill.ca)
# *
# * 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.emlib.image import ImageHandler
from pwem.protocols import ProtMaskParticles, ProtMaskVolumes
from pyworkflow.protocol.params import EnumParam, PointerParam, IntParam
from xmipp3.convert import getImageLocation
from xmipp3.constants import *
from .geometrical_mask import XmippGeometricalMask3D, XmippGeometricalMask2D
from .protocol_process import XmippProcessParticles, XmippProcessVolumes
from .protocol_create_mask3d import XmippProtCreateMask3D
[docs]class XmippProtMask:
""" This class implement the common features for applying a mask with
Xmipp either SetOfParticles, Volume or SetOfVolumes objects.
"""
def __init__(self, **args):
self._program = "xmipp_transform_mask"
#--------------------------- DEFINE param functions --------------------------------------------
def _defineProcessParams(self, form):
""" Add common mask parameters that can be used
in several protocols definitions.
Params:
form: the Definition instance.
"""
form.addParam('source', EnumParam,
label='Mask source',
default=SOURCE_GEOMETRY, choices=['Geometry','Created mask'],
help='Select which type of mask do you want to apply. \n ')
form.addParam('inputMask', PointerParam, pointerClass=self.MASK_CLASSNAME,
condition='source==%d' % SOURCE_MASK,
label="Input mask")
args={}
args['isGeometry'] = 'source==%d' % SOURCE_GEOMETRY
args['addSize'] = False
if isinstance (self, XmippProtCreateMask3D):
args['isFeatureFile'] = 'source==%d' % SOURCE_FEATURE_FILE
self.GEOMETRY_BASECLASS.defineParams(self, form, **(args))
form.addParam('fillType', EnumParam,
choices=['value', 'min', 'max', 'avg'],
condition='source==%d' % SOURCE_GEOMETRY,
default=MASK_FILL_VALUE,
label="Fill with ", display=EnumParam.DISPLAY_COMBO,
help='Select how are you going to fill the pixel values outside the mask. ')
form.addParam('fillValue', IntParam, default=0,
condition='fillType == %d and source==%d' % (MASK_FILL_VALUE,SOURCE_GEOMETRY),
label='Fill value',
help='Value to fill the pixel values outside the mask. ')
#--------------------------- INSERT steps functions --------------------------------------------
def _insertProcessStep(self):
inputFn = self.inputFn
if self.source == SOURCE_MASK:
inputMaskFile = getImageLocation(self.inputMask.get())
outputMaskFile = self._getTmpPath(self.MASK_FILE)
self._insertFunctionStep('copyMaskFileStep', inputMaskFile, outputMaskFile)
if self.fillType == MASK_FILL_VALUE:
fillValue = self.fillValue.get()
else:
fillValue = self.getEnumText('fillType')
if self.source == SOURCE_GEOMETRY:
self._program = "xmipp_transform_mask"
self._args += self._getGeometryCommand()
self._args += " --substitute %(fillValue)s "
elif self.source == SOURCE_MASK:
self._program = "xmipp_image_operate"
self._args += (" --mult %s" % outputMaskFile)
else:
raise Exception("Unrecognized mask type: %d" % self.source)
self._insertFunctionStep("maskStep", self._args % locals())
#--------------------------- STEPS functions ---------------------------------------------------
[docs] def copyMaskFileStep(self, inputMaskFile, outputMaskFile):
""" Create a local copy of the mask.
We use ImageHandler.convert instead of copyFile
because the mask could be inside an stack.
"""
ImageHandler().convert(self.inputMask.get(), outputMaskFile)
[docs] def maskStep(self, args):
args += self._getExtraMaskArgs()
self.runJob(self._program, args)
#--------------------------- UTILS functions ---------------------------------------------------
def _getExtraMaskArgs(self):
""" Return some extra arguments for the mask program.
This function will varies when masking particles or volumes.
"""
args = " -o %s --save_metadata_stack %s --keep_input_columns" % (self.outputStk, self.outputMd)
return args
#--------------------------- INFO functions --------------------------------------------
def _summary(self, geoClass):
messages = []
messages.append("*Mask application*")
if self.source.get() == SOURCE_GEOMETRY:
messages.append(' Used geometrical mask:')
messages += geoClass.summary(self)
else:
messages.append(' Used created mask: %s' % self.inputMask.get().getNameId())
if self.fillType.get() == MASK_FILL_VALUE:
messages.append(' Filled with %s value' % self.fillValue.get())
else:
messages.append(' Filled with %s value' % self.getEnumText('fillType'))
return messages
def _methods(self, geoClass):
messages = []
if self.inputMask.get() is None:
return messages
messages.append("*Mask application*")
if self.source.get() == SOURCE_GEOMETRY:
messages.append("We applied a geometrical mask:")
messages+=geoClass.methods(self)
else:
messages.append('We applied a created mask: %s' % self.inputMask.get().getNameId())
if self.fillType.get() == MASK_FILL_VALUE:
messages.append('filled with %s value' % self.fillValue.get())
else:
messages.append('filled with %s value' % self.getEnumText('fillType'))
return messages
def _validateDimensions(self, inputSetName, inputMaskName, errorMsg):
""" Validate that input set (either particles or volumens) have the same
dimension than the input mask.
"""
errors = []
if self.source == SOURCE_MASK:
px, py, _ = self.getAttributeValue(inputSetName).getDim()
mx, my, _ = self.getAttributeValue(inputMaskName).getDim()
if px != mx or py != my:
errors.append(errorMsg)
return errors
[docs]class XmippProtMaskParticles(ProtMaskParticles, XmippProcessParticles,
XmippProtMask, XmippGeometricalMask2D):
""" Applies masks to a set of particle images to isolate specific regions
or exclude unwanted background. The mask can be done with a geometry or
import an external one. The center can be shift and the pixels outside the
mask can be filled with a min avg, max or an specific value. Masking is
critical for focusing on structural features and improving alignment and
classification.
AI Generated
## Overview
The Apply 2D Mask protocol applies a mask to a set of particle images.
Masking is commonly used in cryo-EM to isolate the particle region, suppress
background, remove unwanted image areas, or focus processing on a region of
interest. This protocol can apply either a geometrical mask created directly
from user-defined parameters, or an already existing 2D mask.
Pixels outside the mask can be replaced by a fixed value or by a statistic of
the image, such as the minimum, maximum, or average value.
The main output is a new particle set containing the masked particle images.
## Inputs and General Workflow
The input is a set of particles.
The user selects the mask source:
- a geometrical mask;
- a previously created mask.
If a geometrical mask is selected, the protocol creates the mask internally
using the particle box size and the selected geometrical parameters. If a
created mask is selected, the protocol copies the mask to the protocol working
area and applies it to the particles.
The protocol then writes a new particle stack and metadata file, preserving the
input metadata columns when possible.
## Input Particles
The **Input particles** parameter defines the particle set to be masked.
The mask is applied independently to each particle image. The protocol does not
change particle coordinates, orientations, CTF information, or alignment
metadata. It only changes the image values according to the selected mask.
The output particles can be used in downstream processing steps such as
classification, alignment, reconstruction, or visualization.
## Mask Source
The **Mask source** parameter controls how the mask is provided.
There are two options:
**Geometry** creates a mask from user-defined geometrical parameters.
**Created mask** applies a mask that already exists as a Scipion mask object.
Use a geometrical mask when a simple shape is sufficient. Use a created mask
when the mask was generated previously, for example from another protocol or by
manual editing.
## Geometrical Mask
When **Geometry** is selected, the protocol creates the mask from the particle
box size.
The available 2D geometrical masks are:
- Circular;
- Box;
- Crown;
- Gaussian;
- Raised cosine;
- Raised crown.
The mask is applied using `xmipp_transform_mask`.
The geometrical mask is not saved as a separate output mask by this protocol;
it is used internally to produce the masked particles.
## Circular Mask
A **Circular** mask keeps a disk-shaped region.
The **Radius** parameter defines the radius in pixels. If the radius is set to
**-1**, the protocol uses half of the particle box size.
Circular masks are useful for centered particles whose density lies mainly
inside a round support.
## Box Mask
A **Box** mask keeps a rectangular region.
The **Box size** parameter defines the size of the box in pixels. If the value
is **-1**, the protocol uses half of the particle box size.
This option is useful for keeping a central square or rectangular region.
## Crown Mask
A **Crown** mask keeps an annular region between an inner and an outer radius.
The relevant parameters are:
- **Inner radius**;
- **Outer radius**.
If the outer radius is **-1**, the protocol uses half of the particle box size.
Crown masks are useful when the user wants to keep ring-like information while
excluding the center.
## Gaussian Mask
A **Gaussian** mask applies a smooth Gaussian weighting.
The **Sigma** parameter defines the Gaussian width in pixels. If sigma is set
to **-1**, the protocol uses one sixth of the particle box size.
Gaussian masks are useful when the user wants smooth attenuation rather than a
hard binary boundary.
## 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 reduce sharp-edge artifacts caused by
hard masking.
## Raised Crown Mask
A **Raised crown** mask creates a crown-like mask with smooth transitions at
the 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** option moves the center of the geometrical mask away from
the center of the particle box.
When enabled, the user provides:
- **X center offset**;
- **Y center offset**.
This is useful when the region of interest is not centered in the particle
image.
## Created Mask
When **Created mask** is selected, the **Input mask** parameter defines the 2D
mask to apply.
The mask is copied locally and multiplied by the input particle images.
The mask must have the same X and Y dimensions as the input particles. The
protocol validates this requirement and reports an error if the dimensions do
not match.
## Fill Type
The **Fill with** parameter controls how pixels outside a geometrical mask are
replaced.
The available options are:
- **value**;
- **min**;
- **max**;
- **avg**.
If **value** is selected, the user provides an explicit fill value. If one of
the statistical options is selected, the protocol fills the outside region with
the corresponding image statistic.
This option is available for geometrical masks.
## Fill Value
The **Fill value** parameter is used when **Fill with = value**.
It defines the numerical value assigned to pixels outside the mask.
A common choice is 0, which suppresses the background outside the selected
region. Other values may be useful when the image background has a nonzero
mean.
## Output Particles
The main output is the masked particle set.
The output images are written to a new stack, and the output metadata preserves
the input columns when possible.
The output particle set can be used directly in later Scipion protocols.
## Validation Rules
When a created mask is used, the input particles and mask must have the same
image dimensions.
If the mask dimensions do not match the particle dimensions, the protocol
reports a validation error.
For geometrical masks, the mask size is derived from the particle dimensions.
## Interpreting the Result
The output particles should be interpreted as the original particles after
masking.
Masking can improve focus on the particle signal and reduce background
contribution, but it can also remove useful signal if the mask is too tight or
miscentered.
Sharp mask edges can introduce Fourier artifacts. Smooth masks such as
Gaussian, raised cosine, or raised crown masks may be preferable for
frequency-sensitive downstream steps.
## Practical Recommendations
Use a circular or raised-cosine mask for centered particles.
Use a shifted geometrical mask only when the region of interest is known to be
off-center.
Use an existing created mask when the mask was carefully generated from the
data or manually curated.
Make sure the mask is not too tight around the particle density.
Use smooth masks when the output will be used for alignment, classification, or
Fourier-space processing.
Inspect representative masked particles before continuing downstream.
## Final Perspective
Apply 2D Mask is a particle-image masking protocol.
For biological users, its value is that it suppresses irrelevant image regions
and focuses downstream analysis on the selected particle or region of interest.
The protocol should be used carefully: masking improves many workflows, but an
incorrect mask can remove real signal or introduce artifacts.
"""
_label = 'apply 2d mask'
MASK_FILE = 'mask.spi'
MASK_CLASSNAME = 'Mask'
GEOMETRY_BASECLASS = XmippGeometricalMask2D
def __init__(self, **kwargs):
ProtMaskParticles.__init__(self, **kwargs)
XmippProcessParticles.__init__(self, **kwargs)
XmippProtMask.__init__(self, **kwargs)
self.allowThreads = False
self.allowMpi = False
#--------------------------- DEFINE param functions --------------------------------------------
def _defineProcessParams(self, form):
XmippProtMask._defineProcessParams(self, form)
#--------------------------- UTILS functions ---------------------------------------------------
def _getGeometryCommand(self):
Xdim = self.inputParticles.get().getDimensions()[0]
self.ndim = self.inputParticles.get().getSize()
args = XmippGeometricalMask2D.argsForTransformMask(self, Xdim)
return args
#--------------------------- INFO functions --------------------------------------------
def _summary(self):
messages = []
messages += XmippProtMask._summary(self, XmippGeometricalMask2D)
return messages
def _methods(self):
messages = []
messages += XmippProtMask._methods(self, XmippGeometricalMask2D)
return messages
def _validate(self):
return XmippProtMask._validateDimensions(self,
'inputParticles', 'inputMask',
'Input particles and mask should '
'have same dimensions.')
[docs]class XmippProtMaskVolumes(ProtMaskVolumes, XmippProcessVolumes, XmippProtMask, XmippGeometricalMask3D):
""" Apply mask to a volume.
AI Generated
## Overview
The Apply 3D Mask protocol applies a mask to one volume or to a set of volumes.
3D masking is used in cryo-EM to isolate molecular density, suppress solvent,
focus on a region of interest, or prepare maps for alignment, refinement,
subtraction, validation, filtering, or visualization. This protocol can apply
either a geometrical 3D mask or an already created volume mask.
For geometrical masks, voxels outside the mask can be filled with a fixed
value or with a statistic of the volume, such as the minimum, maximum, or
average value. For created masks, the protocol multiplies the input volume by
the mask.
The main output is a masked volume or a masked set of volumes.
## Inputs and General Workflow
The input can be a single volume or a set of volumes.
The user selects the mask source:
- a geometrical mask;
- a created 3D mask.
If a geometrical mask is selected, the protocol creates the shape internally
using the input volume dimensions. If a created mask is selected, the mask is
copied locally and multiplied by the input volume or volumes.
For a single input volume, the protocol writes one masked output map. For a set
of volumes, it writes a new output volume set and preserves the input metadata
when possible.
## Input Volumes
The **Input volumes** parameter defines the map or maps to be masked.
The protocol applies the mask to voxel values. It does not align, filter,
resize, sharpen, or validate the maps.
The output should be interpreted as the same input volume data restricted or
modified according to the selected mask.
## Mask Source
The **Mask source** parameter controls how the mask is provided.
There are two options:
**Geometry** creates a geometrical mask from user-defined parameters.
**Created mask** applies an existing Scipion **VolumeMask** object.
Use geometry for simple masks such as spheres, cylinders, boxes, or smooth
radial masks. Use a created mask when the mask has been derived from the map,
segmented, manually edited, or generated by another protocol.
## Geometrical Mask
When **Geometry** is selected, the protocol creates the mask from the input
volume size.
The available 3D geometrical masks are:
- Sphere;
- Box;
- Crown;
- Cylinder;
- Gaussian;
- Raised cosine;
- Raised crown.
The mask is applied using `xmipp_transform_mask`.
The geometrical mask is used internally and is not registered as a separate
output mask by this protocol.
## Sphere Mask
A **Sphere** mask keeps a spherical region.
The **Radius** parameter defines the radius in pixels. If the value is **-1**,
the protocol uses half of the input volume box size.
Sphere masks are useful for approximately globular particles or simple central
support regions.
## Box Mask
A **Box** mask keeps a rectangular or cubic region.
The **Box size** parameter defines the box size in pixels. If the value is
**-1**, the protocol uses half of the input volume box size.
This mask can be useful when the density region is approximately rectangular
or when a central cubic region should be retained.
## Crown Mask
A **Crown** mask keeps a shell-like region between an inner and an outer
radius.
The relevant parameters are:
- **Inner radius**;
- **Outer radius**.
If the outer radius is **-1**, the protocol uses half of the input volume box
size.
Crown masks are useful for selecting radial shells while excluding the center.
## Cylinder Mask
A **Cylinder** mask keeps a cylindrical region.
The relevant parameters are:
- **Radius**;
- **Height**.
If the radius is **-1**, the protocol uses half of the input volume box size.
If the height is **-1**, the protocol uses the full box size.
Cylinder masks are useful for elongated structures, filaments, or simple
helical support regions.
## Gaussian Mask
A **Gaussian** mask applies a smooth 3D Gaussian weighting.
The **Sigma** parameter defines the Gaussian width in pixels. If sigma is
**-1**, the protocol uses one sixth of the input volume box size.
Gaussian masks are useful when the user wants smooth attenuation rather than a
binary boundary.
## Raised Cosine Mask
A **Raised cosine** mask creates a smooth radial transition between an inner
and an outer radius.
This is useful for reducing artifacts caused by sharp mask boundaries.
## Raised Crown Mask
A **Raised crown** mask creates a crown-like mask with smooth transitions at
the inner and outer 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 moves the center of a geometrical mask
away from the center of the input volume.
When enabled, the user provides:
- **X**;
- **Y**;
- **Z** center offsets.
This is useful when the molecular region or region of interest is not centered
in the volume.
## Created Mask
When **Created mask** is selected, the **Input mask** parameter defines the
VolumeMask to apply.
The mask is copied locally and multiplied by the input volume or volume set.
The mask must have the same X and Y dimensions as the input volumes. The
protocol validates these dimensions. In practice, users should ensure that the
full 3D dimensions, sampling rate, origin, and coordinate frame are compatible.
## Fill Type
The **Fill with** parameter controls how voxels outside a geometrical mask are
replaced.
The available options are:
- **value**;
- **min**;
- **max**;
- **avg**.
If **value** is selected, the user provides an explicit fill value. Otherwise,
outside voxels are filled using the selected statistic.
This option applies to geometrical masks.
## Fill Value
The **Fill value** parameter is used when **Fill with = value**.
It defines the numerical value assigned outside the geometrical mask.
A common value is 0, which removes density outside the mask. Other values may
be useful when the surrounding background should match a specific level.
## Output Volume
If the input is a single volume, the output is a single masked volume.
The output contains the result of applying the selected mask to the input map.
This output can be used for visualization, alignment, focused processing,
subtraction, validation, or other downstream workflows.
## Output Volume Set
If the input is a set of volumes, the output is a masked volume set.
Each output item corresponds to the matching input volume after mask
application. Input metadata are preserved when possible.
This is useful when the same mask should be applied consistently to several
maps or class volumes.
## Validation Rules
When a created mask is used, the input volumes and mask must have matching
dimensions.
If the dimensions do not match, the protocol reports a validation error.
For geometrical masks, the mask size is derived from the input volume or volume
set dimensions.
## Interpreting the Result
The masked output should be interpreted as the original volume restricted or
modified by the selected mask.
Masking can improve visualization and reduce background influence. However, an
incorrect mask can remove real density, create artificial boundaries, or bias
downstream processing.
Sharp masks can introduce Fourier artifacts. Smooth geometrical masks or
carefully softened created masks are preferable when the output will be used in
Fourier-space operations.
## Practical Recommendations
Use an existing created mask when a molecule-specific mask has already been
generated.
Use a geometrical mask for simple support regions or quick preprocessing.
Use smooth masks when the masked volume will be used for refinement,
Fourier-space filtering, FSC analysis, or subtraction.
Make sure the mask is not too tight around the density.
Check that the mask and volume share the same size, sampling, origin, and
coordinate frame.
Inspect the masked map visually before using it in downstream workflows.
## Final Perspective
Apply 3D Mask is a general volume-masking protocol.
For biological users, its value is that it restricts 3D density to a selected
region, reducing background and enabling focused analysis. It can be applied
to one map or consistently to a set of maps.
The protocol should be used with care because masks influence many downstream
cryo-EM analyses. A good mask clarifies the region of interest; a poor mask can
bias interpretation or processing.
"""
_label = 'apply 3d mask'
MASK_FILE = 'mask.vol'
MASK_CLASSNAME = 'VolumeMask'
GEOMETRY_BASECLASS = XmippGeometricalMask3D
def __init__(self, **kwargs):
ProtMaskVolumes.__init__(self, **kwargs)
XmippProcessVolumes.__init__(self, **kwargs)
XmippProtMask.__init__(self, **kwargs)
self.allowMpi = False
self.allowThreads = False
#--------------------------- DEFINE param functions --------------------------------------------
def _defineProcessParams(self, form):
XmippProtMask._defineProcessParams(self, form)
#--------------------------- UTILS functions ---------------------------------------------------
def _getGeometryCommand(self):
if self._isSingleInput():
Xdim = self.inputVolumes.get().getDim()[0]
else:
Xdim = self.inputVolumes.get().getDimensions()[0]
args = XmippGeometricalMask3D.argsForTransformMask(self, Xdim)
return args
def _getExtraMaskArgs(self):
if self._isSingleInput():
return " -o %s" % self.outputStk
else:
return XmippProtMask._getExtraMaskArgs(self)
#--------------------------- INFO functions --------------------------------------------
def _summary(self):
messages = []
messages += XmippProtMask._summary(self, XmippGeometricalMask3D)
return messages
def _methods(self):
messages = []
messages += XmippProtMask._methods(self, XmippGeometricalMask3D)
return messages
def _validate(self):
return XmippProtMask._validateDimensions(self,
'inputVolumes', 'inputMask',
'Input volumes and mask should '
'have same dimensions.')