Source code for schrodinger.analysis.visanalysis.volumedataio

"""
This file contains the implementation of several functions that can be used to
read/write VolumeData objects to various file-types.
"""

import os

import numpy as np

import schrodinger.infra.mminit as mminit
import schrodinger.infra.mmsurf as mmsurf

from . import vdexception
from . import volumedata as volumedata

_X = 0
_Y = 1
_Z = 2

_VX = np.array([1.0, 0.0, 0.0])
_VY = np.array([0.0, 1.0, 0.0])
_VZ = np.array([0.0, 0.0, 1.0])

_MUST_SPECIFY_FILE = "Either stream or filename must be specified."
_NON_ORTHOGONAL = "Non-orthogonal axes are currently not permitted."

_DEFAULT_NAME = "VolumeData"
_PYTHON = "PYTHON"

_mmInitialiser = mminit.Initializer([mmsurf.mmvisio_initialize],
                                    [mmsurf.mmvisio_terminate])


class _MMVol(object):
    """
    The _MMVol class acts as a wrapper around low-level MMVol objects. The
    class implements Python's context-manager interface to ensure that we
    don't 'leak' MMVol objects in the event of an error.
    """

    def __init__(self, mmvol=None):
        """
        This function creates a new _MMVol object. It either explicitly
        instantiates a MMVol object, or assumes control of the object passed
        via mmvol.

        :param mmvol: A valid MMVol handle, if specified
        :type mmvol: `MMVol handle`
        """
        if mmvol is None:
            self._mmvol = mmsurf.mmvol_new()
        else:
            self._mmvol = mmvol

    def __enter__(self):
        """
        Called by the context-manager as part of a 'with' statement.
        """
        return self._mmvol

    def __exit__(self, type, value, traceback):
        """
        Called by the context manager as part of a 'with' statement.
        """
        mmsurf.mmvol_delete(self._mmvol)
        return False


def _MMVolToVolumeData(mmvol, handleNonOrthogonalAxes=False):
    """
    This function converts the specified mmvol object to a VolumeData instance.

    :param mmvol: The MMVol object to be loaded into a VolumeData instance
    :param mmvol: `MMVol handle`
    :param handleNonOrthogonalAxes: True if the function should handle
        non-orthogonal axes. False otherwise. [Currently this parameter is
        ignored and the function will throw an exception if it encounters non-
        orthogonal axes.
    :type handleNonOrthogonalAxes: `bool`

    :return: The newly constructed VolumeData instance
    :rtype: ``VolumeData``
    """

    # Query the object for its various dimensions and other data.
    name = mmsurf.mmvol_get_name(mmvol)
    data = mmsurf.mmvol_get_grid_data(mmvol)
    N = np.array(mmsurf.mmvol_get_dimensions_array(mmvol))
    resolution = np.array(mmsurf.mmvol_get_axis_scales_array(mmvol))
    origin = np.array(mmsurf.mmvol_get_origin_array(mmvol))

    # The orientations of the axes is important, currently we only handle
    # orthogonal axes, but the hooks are in place for handling the non-
    # orthogonal case.
    axisOrientationX = mmsurf.mmvol_get_orientation_array(mmvol, _X)
    axisOrientationY = mmsurf.mmvol_get_orientation_array(mmvol, _Y)
    axisOrientationZ = mmsurf.mmvol_get_orientation_array(mmvol, _Z)

    if not np.all(np.equal(axisOrientationX, _VX)) or \
       not np.all(np.equal(axisOrientationY, _VY)) or \
       not np.all(np.equal(axisOrientationZ, _VZ)):
        if handleNonOrthogonalAxes:
            # DDR: Should it ever be required; code to handle non-orthogonal
            # axes can be included here. I'd suggest using scipy's
            # interpolation algorithms to map the non-orthogonal data to
            # an orthogonal system.
            pass
        else:
            raise vdexception.VDException(_NON_ORTHOGONAL)

        # Create the VolumeData object, set the data. We also set the name here,
        # although that isn't technically part of the VolumeData specification.
    ret = volumedata.VolumeData(N, resolution, origin)
    ret.Name = name
    ret.setData(data)

    return ret


[docs]def LoadVisFile(filename): """ This function retrieves a VolumeData object from a Schrodinger .vis file. :param filename: A Schrodinger .vis file containing a valid mmvol object :type filename: `string` :return: The VolumeData extracted from the contents of the .vis file :rtype: ``VolumeData`` """ # Load the mmvol object from the file. with _MMVol(mmvol=mmsurf.mmvisio_read_volume_from_file(filename)) as mmvol: return _MMVolToVolumeData(mmvol)
def _VolumeDataToMMVol(volumeData, mmvol): """ This function configures the specified mmvol object to be equivalent to the specified VolumeData. :param volumeData: The VolumeData object to be converted :type volumeData: ``VolumeData`` :param mmvol: A pre-allocated mmvol handle which awaits configuration :type mmvol: `MMVol handle` """ # BUGBUG: I'm not sure how many of these settings are required, or if they # are complete. These settings were arrived at through trial and error. # Name and trivial properties. if hasattr(volumeData, "Name"): mmsurf.mmvol_set_name(mmvol, volumeData.Name) else: mmsurf.mmvol_set_name(mmvol, _DEFAULT_NAME) mmsurf.mmvol_set_comment(mmvol, _DEFAULT_NAME) mmsurf.mmvol_set_property_name(mmvol, _DEFAULT_NAME) mmsurf.mmvol_set_author_name(mmvol, _PYTHON) # Data type and overall dimensionality. mmsurf.mmvol_set_grid_type(mmvol, mmsurf.MMVOL_REGULAR) mmsurf.mmvol_set_data_type(mmvol, mmsurf.MMVOL_FLOAT) mmsurf.mmvol_set_num_dimensions(mmvol, 3) mmsurf.mmvol_set_num_components(mmvol, 1) # Axes directions. mmsurf.mmvol_set_orientation(mmvol, _X, _VX) mmsurf.mmvol_set_orientation(mmvol, _Y, _VY) mmsurf.mmvol_set_orientation(mmvol, _Z, _VZ) # Number of data points and axis scaling. mmsurf.mmvol_set_dimensions(mmvol, volumeData.CoordinateFrame.N) mmsurf.mmvol_set_axis_scales(mmvol, volumeData.CoordinateFrame.resolution) mmsurf.mmvol_set_origin(mmvol, volumeData.CoordinateFrame.origin) # Actual data. mmsurf.mmvol_set_grid_data(mmvol, np.ascontiguousarray(volumeData.getData())) # Visibility. mmsurf.mmvol_set_default_surf_visibility(mmvol, True) mmsurf.mmvol_set_visible(mmvol, True) # Data range. Currently contoured at the mean of the data. # BUGBUG: Possibly better? Single contour for all +ve or all -ve data # at the mean. Two contours at min / 2, max / 2 for +/- data. min = np.min(volumeData.getData()) max = np.max(volumeData.getData()) sigma = np.std(volumeData.getData()) hints = np.array([(max + min) * 0.5]) colours = np.array([mmsurf.MMVOL_COLOR_CYAN]) # # Handle +/- data. # if np.sign( min ) == np.sign( max ): # hints = np.array( [ ( max + min ) * 0.5 ] ) # colours = np.array( [ mm.MMVOL_COLOR_CYAN ] ) # else: # hints = np.array( [ min * 0.5, max * 0.5 ] ) # colours = np.array( [ mm.MMVOL_COLOR_RED, \ # mm.MMVOL_COLOR_CYAN ] ) mmsurf.mmvol_set_min_sample_value(mmvol, min) mmsurf.mmvol_set_max_sample_value(mmvol, max) mmsurf.mmvol_set_has_sample_range(mmvol, True) mmsurf.mmvol_set_sigma(mmvol, sigma) mmsurf.mmvol_set_contour_hints(mmvol, len(hints), hints, colours)
[docs]def SaveVisFile(volumeData, filename): """ This function saves a VolumeData object to a Schrodinger .vis file. :param volumeData: The VolumeData to be serialised :type volumeData: ``volumeData`` :param filename: Where to store the .vis file :type filename: `string` """ with _MMVol() as mmvol: _VolumeDataToMMVol(volumeData, mmvol) # Attempt the save, before we can actually save we have to remove # any file that has the specified name. This appears to be due to # a complexity of the mmvisio library. if os.path.isfile(filename): os.remove(filename) mmsurf.mmvisio_write_volume_to_file(filename, mmvol)
[docs]def LoadCNSFile(filename): """ This function loads a VolumeData object from a CNS file. :param filename: The .cns formatted file :type filename: `string` :return: The VolumeData object extracted from the .cns file. :rtype: ``VolumeData`` """ # Attempt to load and parse the .cns file. mmvol, centre, type, comments = mmsurf.mmvisio_read_cns(filename) with _MMVol(mmvol=mmvol) as mmvol: return _MMVolToVolumeData(mmvol)