Source code for schrodinger.analysis.visanalysis.volumedatautils

# ----------------------------------------------------------------------------
# Name:
#
#   volumedatautils.py
#
# Purpose:
#
#   This file contains the implementation of the VolumeDataUtils module. The
#   VolumeDataUtils module contains a number of utility functions for creating,
#   handling and using VolumeData objects.
#
# Copyright of:
#
#   Copyright Schrodinger, LLC. All rights reserved.
#
# Version:
#
#   Version         Author          Notes
#       1.0            DDR          Original Implementation
#
# Notes:
#
# ----------------------------------------------------------------------------

# ----------------------------------------------------------------------------
# Module imports.

from past.utils import old_div

import numpy as np
import scipy.spatial as spatial

from . import vdexception
from . import volumedata

# End of module imports.
# ----------------------------------------------------------------------------

# ----------------------------------------------------------------------------
# Global constants.

_X = 0
_Y = 1
_Z = 2

_DOWN_COLUMNS = 0
_ACROSS_ROWS = 1

_INVALID_VOLUME_SPEC = "min/maxWorldCoordinates and resolutions must be same size."
_BAD_RESOLUTION = "resolution must be a 3-element array"
_BAD_COORD = "Coordinate must be a 3-element array"
_INVALID_RESOLUTION_MODE = "resolutionMode must be highest/lowest/fixed."

# End of global constants.
# ----------------------------------------------------------------------------


# ----------------------------------------------------------------------------
# Function definition:
#
#   CreateLike
#
# ---------------------------------------------------------------------------
[docs]def CreateLike(volumeData, initialValue=0.0): """ This function creates a new VolumeData instance that is identical in size and resolution to volumeData. All data-points within the newly constructed VolumeData instance are set to initialValue. :param volumeData: The template VolumeData instance :type volumeData: ``volumeData`` :param initialValue: The initial value to assign to every data-point of the new VolumeData instance :type initialValue: `float` :return: The newly constructed VolumeData instance :rtype: ``VolumeData`` """ N = volumeData.CoordinateFrame.N resolution = volumeData.CoordinateFrame.resolution origin = volumeData.CoordinateFrame.origin ret = volumedata.VolumeData(N=N, resolution=resolution, origin=origin) ret.setData(np.ones_like(ret.getData()) * initialValue) return ret
# End of function: CreateLike. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Function definition: # # Resize # # ----------------------------------------------------------------------------
[docs]def Resize(volumeData, N=None, resolution=None, origin=None, interpolationOrder=0, oobMethod="constant", oobConstant=0.0): """ This function is used to resize the specified volumeData object, returning a new VolumeData instance with the specified N, resolution and origin. :param volumeData: The VolumeData instance to be resized. :type volumeData: VolumeData :param N: The number of array-coordinates along the X, Y and Z axes respectively. :type N: `iterable< int, 3 >` :param resolution: The resolution of the X, Y and Z axes respectively. Specified in world-coordinate units :type resolution: `iterable< float, 3 >` :param origin: The origin of the X, Y and Z axes respectively. Specified in world-coordinates :param interpolationOrder: The degree of interpolation to use when retrieving the values. 0-5 :type interpolationOrder: `int` :param oobMethod: What to do with requests that lie outside of the bounds of this VolumeData object. The options are "constant", which returns the value of oobConstant. "nearest" which returns the value of the nearest valid point or "wrap", which effectively tiles the data into an infinite repeating lattice. :type oobMethod: `string` :param oobConstant: Of the class float. The value to return if the request is OOB and the oobMethod is "constant" :type oobConstant: `float` :return: A new VolumeData instance covering the specified region of space, filled with data from the argument volumeData instance. :rtype: VolumeData """ # ------------------------------------------------------------------------ # Create the new VolumeData that will hold the resized data. ret = volumedata.VolumeData(N=N, resolution=resolution, origin=origin) # ------------------------------------------------------------------------ # Acquire the array and world-coordinates of this new VolumeData instance. newArrayCoordinates = ret.getAllArrayCoordinates() newWorldCoordinates = ret.getAllWorldCoordinates() # ------------------------------------------------------------------------ # Get the values from volumeData at these world-coordinates. values = volumeData.getAtWorldCoordinateL( newWorldCoordinates, interpolationOrder=interpolationOrder, oobMethod=oobMethod, oobConstant=oobConstant) # ------------------------------------------------------------------------ # Place these values into the new VolumeData. for i, value in enumerate(values): x = newArrayCoordinates[i][_X] y = newArrayCoordinates[i][_Y] z = newArrayCoordinates[i][_Z] ret.getData()[x][y][z] = value return ret
# End of function: Resize. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Function definition: # # CalculateContainingVolume # # ----------------------------------------------------------------------------
[docs]def CalculateContainingVolume(minWorldCoordinates, maxWorldCoordinates, resolutions, resolutionMode="highest", resolutionValue=None): """ This function calculates values for N, origin and resolution that span the specified world-coordinate range. ``len(resolutions) == len(minWorldCoordinates) == len(maxWorldCoordinates)`` :param minWorldCoordinates: The minimum world-coordinates to consider :type minWorldCoordinates: `iterable<iterable<float, 3>>` :param maxWorldCoordinates: The maximum world-coordinates to consider :type maxWorldCoordinates: `iterable<iterable<float, 3>>` :param resolutions: The resolutions to consider :type resolutions: `iterable<float, 3>` :param resolutionMode: Either "highest", in which case the highest resolution is used for the resized VolumeDatas, "lowest", in which case the lowest resolution is used, or "fixed", in which case the resolution specified by resolutionValue will be used :type resolutionMode: `string` :param resolutionValue: The resolution value to use when the resolutionMode is "fixed". Ignored otherwise :type resolutionValue: `iterable<float, 3>` :return: These are the values of N, resolution and origin that span the input world-coordinate range :rtype: `tuple<iterable<int, 3>, iterable<float, 3>, iterable<float, 3>>` """ if ((len(minWorldCoordinates) != len(maxWorldCoordinates)) or (len(minWorldCoordinates) != len(resolutions))): raise vdexception.VDException(_INVALID_VOLUME_SPEC) if ((len(minWorldCoordinates[0]) != 3) or (len(maxWorldCoordinates[0]) != 3)): raise vdexception.VDException(_BAD_COORD) if len(resolutions[0]) != 3: raise vdexception.VDException(_BAD_RESOLUTION) # ------------------------------------------------------------------------ # Ensure the coordinates and resolutions are passed in a manner that is # compatible with np.min/np.max minWorldCoordinates = np.array(minWorldCoordinates) maxWorldCoordinates = np.array(maxWorldCoordinates) resolutions = np.array(resolutions) origin = np.min(minWorldCoordinates, axis=_DOWN_COLUMNS) maxWorldCoordinate = np.max(maxWorldCoordinates, axis=_DOWN_COLUMNS) extent = maxWorldCoordinate - origin if resolutionMode == "highest": resolution = np.min(resolutions, axis=_DOWN_COLUMNS) elif resolutionMode == "lowest": resolution = np.max(resolutions, axis=_DOWN_COLUMNS) elif resolutionMode == "fixed": if resolutionValue is None: raise vdexception.VDException(_BAD_RESOLUTION) resolution = resolutionValue else: raise vdexception.VDException(_INVALID_RESOLUTION_MODE) N = np.ceil(old_div(extent, resolution)).astype(np.int) return N, resolution, origin
# End of function: CalculateContainingVolume. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Function definition: # # MakeConsistent # # ----------------------------------------------------------------------------
[docs]def MakeConsistent(volumeDatas, resolutionMode="highest", resolutionValue=None, interpolationOrder=0, oobMethod="constant", oobConstant=0.0): """ This function can be used to make all of the VolumeData instances in volumeDatas consistent with each other. This is done by resizing all of the VolumeData instances so that they span the union volume of the individual VolumeData instances. :param volumeDatas: The VolumeData instances to make consistent with each other. :type volumeDatas: `iterable<VolumeData>` :param resolutionMode: Either "highest", in which case the highest resolution is used for the resized VolumeDatas, "lowest", in which case the lowest resolution is used, or "fixed", in which case the resolution specified by resolutionValue will be used :type resolutionMode: `string` :param resolutionValue: The resolution value to use when the resolutionMode is "fixed". Ignored otherwise :type resolutionValue: `iterable<float, 3>` :param interpolationOrder: The degree of interpolation to use when retrieving the values. 0-5 :type interpolationOrder: `int` :param oobMethod: What to do with requests that lie outside of the bounds of this VolumeData object. The options are "constant", which returns the value of oobConstant. "nearest" which returns the value of the nearest valid point or "wrap", which effectively tiles the data into an infinite repeating lattice. :type oobMethod: `string` :param oobConstant: Of the class float. The value to return if the request is OOB and the oobMethod is "constant" :type oobConstant: `float` :return: These are the set of consistent VolumeData instances formed from volumeDatas :rtype: `iterable< VolumeData >` """ # ------------------------------------------------------------------------ # Retrieve the extents of the various VolumeData instances. Convert these # to a min, max and resolution for the consistent grids. minWorldCoordinates = [vd.CoordinateFrame.origin for vd in volumeDatas] maxWorldCoordinates = [vd.CoordinateFrame.max for vd in volumeDatas] resolutions = [vd.CoordinateFrame.resolution for vd in volumeDatas] N, resolution, origin = CalculateContainingVolume( minWorldCoordinates, maxWorldCoordinates, resolutions, resolutionMode=resolutionMode, resolutionValue=resolutionValue) # ------------------------------------------------------------------------ # Resize all of the grids. ret = [ Resize(vd, N=N, resolution=resolution, origin=origin, interpolationOrder=interpolationOrder, oobMethod=oobMethod, oobConstant=oobConstant) for vd in volumeDatas ] return ret
# End of function: MakeConsistent. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Class definition: # # DataPointLocator # # ----------------------------------------------------------------------------
[docs]class DataPointLocator(object): """ The DataPointLocator class is a simple utility class that allows data-points that are 'close' to a given position in space to be located easily. This class uses spatial partitioning (k-d tree) to ensure that the search for data-points is carried out in a computationally efficient manner. The class is designed to be used in the following manner: 1) Instantiate a new DataPointLocator object. This sets up the instance ready to carry out the search. At this point the results list is empty. 2) Execute the various SearchForDataPointsWithinX methods to fill the internal results list with array-coordinates that are within the specified world-distance of the various search origins. 3) Optionally 'uniquify' the results list. 4) Examine the results list to find those points that were within the specified distances of the search origins. """ # ------------------------------------------------------------------------
[docs] def __init__(self, volumeData): """ This function creates a new DataPointLocator instance. :param volumeData: The VolumeData instance to run the queries against :type volumeData: ``VolumeData`` """ # -------------------------------------------------------------------- # Instantiate the kd-tree and fill it with the world-coordinates of # the volumeData. self._kdtree = spatial.KDTree(volumeData.getAllWorldCoordinates()) self._volumeData = volumeData # -------------------------------------------------------------------- # Set up the empty results list. This will store indices into the # array-coordinates of self._volumeData. self._results = list()
# ------------------------------------------------------------------------
[docs] def SearchForDataPointsWithin(self, world, distance): """ This function searches for valid array-coordinates whose corresponding world-coordinate lies within distance of the specified world-coordinate. The array-coordinates are appended to the internal results list. :param world: The search origin, specified in world-coordinates :type world: `iterable<float, 3>` :param distance: Locate all valid array-coordinates whose corresponding world-coordinate lies with distance of world :type distance: `float` """ indices = self._kdtree.query_ball_point(world, distance) self._results.extend(indices)
# ------------------------------------------------------------------------
[docs] def UniquifyResults(self): """ This function uniquifies the result list so that each array-coordinate appears only once. """ self._results = list(set(self._results))
# ------------------------------------------------------------------------
[docs] def ClearResults(self): """ This function clears the current result list. """ self._results = list()
# ------------------------------------------------------------------------ def _getResults(self): """ This function and the synonymous property Results allows access to the result list. :return: The array-coordinates located by the various calls to SearchForDataPointsWithinX :rtype: `iterable< iterable< int, 3 > >` """ # -------------------------------------------------------------------- # Create an array containing the array-coordinates that correspond # to the results list. arrayCoordinates = self._volumeData.getAllArrayCoordinates() ret = [arrayCoordinates[index] for index in self._results] return ret Results = property(_getResults) """ This property allows access to the result list. """
# End of class definition: DataPointLocator. # ---------------------------------------------------------------------------- # End of file: volumedatautils.py # ----------------------------------------------------------------------------