Source code for xmipp3.protocols.protocol_shift_volume

# -*- coding: utf-8 -*-
# **************************************************************************
# *
# * Authors:     Estrella Fernandez Gimenez (me.fernandez@cnb.csic.es)
# *
# *  BCU, 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 pyworkflow.protocol.params import PointerParam, FloatParam, BooleanParam, IntParam
import pyworkflow.object as pwobj
from pwem.protocols import EMProtocol
from pwem.objects import Volume
from pwem.emlib.image import ImageHandler as ih

import numpy as np


[docs]class XmippProtShiftVolume(EMProtocol): """ Shifts a 3D volume spatially according to user-provided parameters. AI Generated ## Overview The Shift Volume protocol translates a 3D volume by a user-defined or automatically determined shift. This protocol is useful when a map needs to be recentered, moved consistently with a shifted particle set, or translated so that its center of mass is placed at the volume center. It does not perform rotational alignment or refinement. It only applies a spatial shift to a single input volume. The shift can be obtained in three ways: - from a previous **Shift particles** protocol; - from user-provided X, Y, and Z shift values; - from the center of mass of the input volume. The main output is a shifted volume. The protocol also outputs the X, Y, and Z shift values used. ## Inputs and General Workflow The input is a single 3D volume. The protocol first determines the shift values to apply. If requested, it reads the shift values from a previous Shift particles protocol. Otherwise, it uses manual X, Y, and Z values or computes a shift from the volume center of mass. Optionally, the protocol first windows the input volume to a new box size. It then applies the selected shift using the Xmipp geometry transformation program. Finally, it creates a new output volume and records the applied shift values. ## Input Volume The **Volume** parameter defines the map to be shifted. The input volume should be the volume that the user wants to translate. The original volume is not modified; the protocol writes a new shifted volume. The output volume keeps the same sampling rate as the input volume. ## Use the Same Shifts as for the Particles The **Use the same shifts as for the particles?** option tells the protocol to reuse the shift values produced by a previous **Shift particles** protocol. This is useful when particles and volumes must be moved consistently. For example, if particles were recentered on a specific domain or region, the corresponding volume can be shifted by the same X, Y, and Z values so that both remain in the same coordinate convention. When this option is enabled, the user must select the previous Shift particles protocol. ## Shift Particles Protocol The **Shift particles protocol** parameter is used when the shift values are taken from a previous particle-shifting run. The protocol reads the scalar outputs: - shiftX; - shiftY; - shiftZ. These values are then applied to the input volume. This option helps maintain consistency between a shifted particle set and a shifted reference or reconstruction volume. ## User-Defined Shift If **Use the same shifts as for the particles?** is disabled and **Use center of mass?** is also disabled, the user provides the shift values manually. The parameters are: - **x**; - **y**; - **z**. These values define the translation applied to the volume. Manual shifts are useful when the user already knows the desired displacement, for example from visual inspection, previous calculations, or external coordinate conventions. ## Use Center of Mass The **Use center of mass?** option computes the shift automatically from the input volume. In this mode, the protocol reads the volume, sets negative density values to zero, computes the center of mass of the remaining positive density, and chooses the shift that moves this center of mass toward the center of the box. This is useful when the volume is off-center and the user wants to recenter it based on its density distribution. This option assumes that the positive density corresponds to the structure of interest. If the map contains strong positive artifacts, disconnected density, or large background features, the center-of-mass shift may not represent the desired biological center. ## Box Size The **Use original box size for the shifted volume?** option controls whether the output volume keeps the same box size as the input. If enabled, the original box size is used. If disabled, the protocol first applies a windowing operation to create a volume with the selected **Final box size**. The shift is then applied to that windowed volume. Changing the box size can be useful when recentering a smaller region or when preparing a volume for a workflow that requires a specific box size. ## Final Box Size The **Final box size** parameter is used when the original box size is not kept. It defines the cubic box size of the intermediate windowed volume and therefore the size of the final shifted volume. The value should be large enough to contain the shifted density. If the box is too small, relevant density may be cropped. ## Shift Application The protocol applies the shift using `xmipp_transform_geometry` with the `--shift` option. The transformation is performed with the `--dont_wrap` option. This means that density shifted outside the box is not wrapped around to the opposite side. This behavior is usually appropriate for recentering, because wrapped density would create artificial features at the box boundaries. ## Output Volume The main output is **outputVolume**. This output is the shifted map written as `shift_volume.mrc` in the protocol working directory. It is registered as a Scipion volume and assigned the same sampling rate as the input volume. The output can be used for visualization, comparison, refinement setup, or other workflows that require the map to be centered or shifted consistently. ## Shift Outputs The protocol also produces three scalar outputs: - **shiftX**; - **shiftY**; - **shiftZ**. These values record the translation applied to the volume. They are useful for documentation, reproducibility, and for applying the same shift to related objects in later workflows. ## Interpretation of the Result The output volume should be interpreted as the same density map translated in space. No new structural information is created. The protocol does not refine, filter, mask, align, or validate the map. It only shifts the position of the density within the box. If the selected shift is correct, the volume will be better centered or better matched to the coordinate convention required by downstream protocols. If the shift is incorrect, relevant density may become off-center or cropped. ## Practical Recommendations Use the same shift as the particles when you need consistency between shifted particles and a corresponding volume. Use manual X, Y, and Z values when the desired displacement is already known. Use center-of-mass shifting for simple recentering, but inspect the result afterwards. Be careful with maps containing large artifacts, disconnected components, or negative/positive density imbalance when using center of mass. Keep the original box size unless there is a clear reason to change it. If changing the box size, make sure the new box is large enough to contain the shifted structure. Compare the shifted output with the original volume to confirm that the density has moved as expected. ## Final Perspective Shift Volume is a simple volume-translation utility. For biological users, its main value is practical coordinate control. It helps recenter maps, apply the same shifts used for particles, or prepare volumes for downstream protocols that expect a particular origin or density position. The protocol should be understood as a geometrical transformation step. Its correctness depends on choosing shift values that match the intended coordinate frame and biological region. """ _label = 'shift volume' # --------------------------- DEFINE param functions -------------------------------------------- def _defineParams(self, form): form.addSection(label='Input') form.addParam('inputVol', PointerParam, pointerClass='Volume', label="Volume", help='Volume to shift') form.addParam('shiftBool', BooleanParam, label='Use the same shifts as for the particles?', default='True', help='Use output shifts of protocol "shift particles" which should be executed previously') form.addParam('inputProtocol', PointerParam, pointerClass='XmippProtShiftParticles', allowsNull=True, label="Shift particles protocol", condition='shiftBool') form.addParam('useCM', BooleanParam, label='Use center of mass?', default='False', help='Select the position where the particles will be shifted in a volume displayed in a wizard.') COND = 'not shiftBool and not useCM' form.addParam('x', FloatParam, label="x", condition=COND, allowsNull=True) form.addParam('y', FloatParam, label="y", condition=COND, allowsNull=True) form.addParam('z', FloatParam, label="z", condition=COND, allowsNull=True) form.addParam('boxSizeBool', BooleanParam, label='Use original box size for the shifted volume?', default='True', help='Use input volume box size for the shifted volume.') form.addParam('boxSize', IntParam, label='Final box size', condition='not boxSizeBool', help='Box size of the shifted volume.') # --------------------------- INSERT steps functions -------------------------------------------- def _insertAllSteps(self): self._insertFunctionStep('shiftStep') self._insertFunctionStep('createOutputStep') # --------------------------- STEPS functions --------------------------------------------
[docs] def shiftStep(self): fnVol = self.inputVol.get().getFileName() if not self.boxSizeBool: box = self.boxSize.get() self.runJob('xmipp_transform_window', '-i "%s" -o "%s" --size %d %d %d' % (fnVol, self._getExtraPath("resized_volume.mrc"), box, box, box)) fnVol = self._getExtraPath("resized_volume.mrc") if self.shiftBool: shiftprot = self.inputProtocol.get() self.shiftx = shiftprot.shiftX.get() self.shifty = shiftprot.shiftY.get() self.shiftz = shiftprot.shiftZ.get() else: if not self.useCM: self.shiftx = self.x.get() self.shifty = self.y.get() self.shiftz = self.z.get() else: if fnVol.endswith('.mrc'): fnVol += ':mrc' vol = ih().read(fnVol).getData() vol[vol < 0] = 0 xs = np.linspace(-vol.shape[2] / 2, vol.shape[2] / 2, vol.shape[2]) ys = np.linspace(-vol.shape[1] / 2, vol.shape[1] / 2, vol.shape[1]) zs = np.linspace(-vol.shape[0] / 2, vol.shape[0] / 2, vol.shape[0]) xs, ys, zs = np.meshgrid(xs, ys, zs, indexing='ij') totalMass = vol.sum() self.shiftx = -(xs * vol).sum() / totalMass self.shifty = -(ys * vol).sum() / totalMass self.shiftz = -(zs * vol).sum() / totalMass program = "xmipp_transform_geometry" args = '-i %s -o %s --shift %f %f %f --dont_wrap' % \ (fnVol, self._getExtraPath("shift_volume.mrc"), self.shiftx, self.shifty, self.shiftz) self.runJob(program, args)
[docs] def createOutputStep(self): out_vol = Volume() in_vol = self.inputVol.get() out_vol.setSamplingRate(in_vol.getSamplingRate()) out_vol.setFileName(self._getExtraPath("shift_volume.mrc")) self._defineOutputs(outputVolume=out_vol) self._defineOutputs(shiftX=pwobj.Float(self.shiftx), shiftY=pwobj.Float(self.shifty), shiftZ=pwobj.Float(self.shiftz)) self._defineSourceRelation(in_vol, out_vol)
# --------------------------- INFO functions -------------------------------------------- def _summary(self): summary = [] if not hasattr(self, 'outputVolume'): summary.append("Output volume not ready yet.") else: if self.shiftBool: summary.append("Volume shift as particles in %s" % self.inputProtocol.get()) else: summary.append("User defined shift") return summary