Skip to content

Commit

Permalink
improved import/export
Browse files Browse the repository at this point in the history
- better code (used _get_loaders/_get_exporters and is more tabkle driven)
- extension are handled better: they are overriden by format args and
  and the writer makes sure that the corrct extension is used
  • Loading branch information
orbeckst committed Sep 7, 2010
1 parent fc4545c commit ca14c6c
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 73 deletions.
1 change: 0 additions & 1 deletion gridData/OpenDX.py
@@ -1,4 +1,3 @@
# $Id$
# gridDataFormats --- python modules to read and write gridded data
# Copyright (c) 2009-2010 Oliver Beckstein <orbeckst@gmail.com>
# Released under the GNU Lesser Public License, version 3 or later.
Expand Down
157 changes: 115 additions & 42 deletions gridData/core.py
@@ -1,4 +1,3 @@
# $Id$
# gridDataFormats --- python modules to read and write gridded data
# Copyright (c) 2009-2010 Oliver Beckstein <orbeckst@gmail.com>
# Released under the GNU Lesser Public License, version 3 or later.
Expand Down Expand Up @@ -29,6 +28,13 @@

from gridDataFormats import gridDataWarning

def _grid(x):
"""Access the underlying ndarray of a Grid object or return the object itself"""
try:
return x.grid
except AttributeError:
return x

class Grid(object):
"""Class to manage a multidimensional grid object.
Expand All @@ -41,6 +47,7 @@ class Grid(object):
The attribute Grid.metadata holds a user-defined dictionary that
can be used to annotate the data. It is saved with save().
"""
default_format = 'DX'

def __init__(self,grid=None,edges=None,origin=None,delta=None, metadata=None):
"""
Expand Down Expand Up @@ -75,6 +82,15 @@ def __init__(self,grid=None,edges=None,origin=None,delta=None, metadata=None):
associated with the density; the class does not touch
metadata[] but stores it with save()
"""
self._exporters = {'DX': self._export_dx,
'PICKLE': self._export_python,
'PYTHON': self._export_python, # compatibility
}
self._loaders = {'DX': self._load_dx,
'PICKLE': self._load_python,
'PYTHON': self._load_python, # compatibility
}


if metadata is None:
metadata = {}
Expand Down Expand Up @@ -127,7 +143,28 @@ def _update(self):
self.midpoints = map(lambda e: 0.5 * (e[:-1] + e[1:]), self.edges)
self.origin = map(lambda m: m[0], self.midpoints)

def load(self,filename=None):
def _guess_format(self, filename, format=None, export=True):
if export:
available = self._exporters
else:
available = self._loaders
if format is None:
format = os.path.splitext(filename)[1][1:]
format = format.upper()
if not format:
format = self.default_format
if not format in available:
raise ValueError("File format %r not available, choose one of %r"\
% (format, available.keys()))
return format

def _get_exporter(self, filename, format=None):
return self._exporters[self._guess_format(filename, format=format, export=True)]

def _get_loader(self, filename, format=None):
return self._loaders[self._guess_format(filename, format=format, export=False)]

def load(self,filename, format=None):
"""Load saved (pickled or dx) grid and edges from <filename>.pickle
Grid.load(<filename>.pickle)
Expand All @@ -136,16 +173,8 @@ def load(self,filename=None):
The load() method calls the class's constructor method and
completely resets all values, based on the loaded data.
"""
import os.path
root,ext = os.path.splitext(filename)
if ext == '.dx':
self._load_dx(filename)
elif ext == '.pickle':
self._load_python(filename)
else:
warnings.warn("File to load from has no identifying extension assuming pickle",
category=gridDataWarning)
self._load_python(filename)
loader = self._get_loader(filename, format=format)
loader(filename)

def _load_python(self,filename):
f = open(filename,'rb')
Expand All @@ -164,41 +193,35 @@ def _load_dx(self,dxfile):
grid,edges = dx.histogramdd()
self.__init__(grid=grid,edges=edges,metadata=self.metadata)

def export(self,filename,format="dx"):
def export(self,filename,format=None):
"""export density to file using the given format; use 'dx' for visualization.
export(filename=<filename>,format=<format>)
The <filename> can be omitted if a default file name already
exists for the object (e.g. if it was loaded from a file or it
was saved before.) Do not supply the filename extension. The
correct one will be added by the method.
The format can also be deduced from the suffix of the filename
though the *format* keyword takes precedence.
The default format for export() is 'dx'.
Only implemented formats:
dx OpenDX (WRITE ONLY)
python pickle (use Grid.load(filename) to restore); Grid.save()
dx OpenDX
pickle pickle (use Grid.load(filename) to restore); Grid.save()
is simpler than export(format='python').
"""
if format == "dx":
self._export_dx(filename)
elif format == "python":
self._export_python(filename)
else:
raise NotImplementedError("Exporting to format "+str(format)+\
" is not implemented.")
exporter = self._get_exporter(filename, format=format)
exporter(filename)

def _export_python(self,filename):
"""Pickle the Grid object
The object is dumped as a dictionary with grid and edges: This
is sufficient to recreate the grid object with __init__().
"""
root, ext = os.path.splitext(filename)
filename = root + ".pickle"

data = dict(grid=self.grid,edges=self.edges,metadata=self.metadata)
filename = filename + ".pickle"
f = open(filename,'wb')
try:
cPickle.dump(data,f,cPickle.HIGHEST_PROTOCOL)
Expand All @@ -209,17 +232,16 @@ def _export_python(self,filename):
def _export_dx(self,filename):
"""Export the density grid to an OpenDX file. The file format
is the simplest regular grid array and it is also understood
by VMD's DX reader.
by VMD's and PyMOL's DX reader.
For the file format see
http://opendx.sdsc.edu/docs/html/pages/usrgu068.htm#HDREDF
"""

filename = filename + '.dx'
root, ext = os.path.splitext(filename)
filename = root + '.dx'

comments = [
'OpenDX density file written by',
'$Id$',
'OpenDX density file written by gridDataFormats.Grid.export()',
'File format: http://opendx.sdsc.edu/docs/html/pages/usrgu068.htm#HDREDF',
'Data are embedded in the header and tied to the grid positions.',
'Data is written in C array order: In grid[x,y,z] the axis z is fastest',
Expand Down Expand Up @@ -250,9 +272,8 @@ def save(self,filename):
g = Grid(filename=<filename>)
Always omit the filename's suffix as it is set by the Grid class.
"""
self.export(filename,format="python")
self.export(filename,format="pickle")

def centers(self):
"""Returns the coordinates of the centers of all grid cells as an iterator."""
Expand All @@ -263,7 +284,7 @@ def centers(self):
yield numpy.sum(self.delta * numpy.asarray(idx), axis=0) + self.origin

def check_compatible(self, other):
"""Check if *other* can be used in an algebraic operation.
"""Check if *other* can be used in an arithmetic operation.
1) *other* is a scalar
2) *other* is a grid defined on the same edges
Expand All @@ -272,10 +293,13 @@ def check_compatible(self, other):
"""
if not (numpy.isscalar(other) or
numpy.all(numpy.concatenate(self.edges) == numpy.concatenate(other.edges))):
raise TypeError("The argument can not be algebraically combined with the grid. "
raise TypeError("The argument can not be arithmetically combined with the grid. "
"It must be a scalar or a grid with identical edges.")
return True

# basic arithmetic (left and right associative so that Grid1 + Grid2 but also
# 3 * Grid and Grid/0.5 work)

def __add__(self, other):
"""Return a new :class:`Grid` with the point-wise sum of the data.
Expand All @@ -284,8 +308,8 @@ def __add__(self, other):
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(self.grid + other.grid, edges=self.edges)

return Grid(self.grid + _grid(other), edges=self.edges)
def __sub__(self, other):
"""Return a new :class:`Grid` with the point-wise difference of the data.
Expand All @@ -294,7 +318,7 @@ def __sub__(self, other):
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(self.grid - other.grid, edges=self.edges)
return Grid(self.grid - _grid(other), edges=self.edges)

def __mul__(self, other):
"""Return a new :class:`Grid` with the point-wise product of the data.
Expand All @@ -304,7 +328,7 @@ def __mul__(self, other):
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(self.grid * other.grid, edges=self.edges)
return Grid(self.grid * _grid(other), edges=self.edges)

def __div__(self, other):
"""Return a new :class:`Grid` with the point-wise quotient of the data.
Expand All @@ -314,7 +338,7 @@ def __div__(self, other):
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(self.grid / other.grid, edges=self.edges)
return Grid(self.grid / _grid(other), edges=self.edges)

def __pow__(self, other):
"""Return a new :class:`Grid` with the point-wise power of the data.
Expand All @@ -324,8 +348,57 @@ def __pow__(self, other):
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(numpy.power(self.grid, other.grid), edges=self.edges)
return Grid(numpy.power(self.grid, _grid(other)), edges=self.edges)

def __radd__(self, other):
"""Return a new :class:`Grid` with the point-wise sum of the data.
g.__add__(h) <==> h + g
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(_grid(other) + self.grid, edges=self.edges)

def __rsub__(self, other):
"""Return a new :class:`Grid` with the point-wise difference of the data.
g.__sub__(h) <==> h - g
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(_grid(other) - self.grid, edges=self.edges)

def __rmul__(self, other):
"""Return a new :class:`Grid` with the point-wise product of the data.
g.__mul__(h) <==> h * g
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(_grid(other) * self.grid, edges=self.edges)

def __rdiv__(self, other):
"""Return a new :class:`Grid` with the point-wise quotient of the data.
g.__div__(h) <==> h/g
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(_grid(other) / self.grid, edges=self.edges)

def __rpow__(self, other):
"""Return a new :class:`Grid` with the point-wise power of the data.
g.__pow__(h) <==> numpy.power(h, g)
:Returns: :class:`Grid`
"""
self.check_compatible(other)
return Grid(numpy.power(_grid(other), self.grid), edges=self.edges)

def __repr__(self):
return '<Grid with '+str(self.grid.shape)+' bins>'
2 changes: 1 addition & 1 deletion gridData/gOpenMol.py
Expand Up @@ -123,5 +123,5 @@
Last modified: September 23, 2003 09:18:50
"""

class plt:
class GOpenMol(object):
raise NotImplementedError
40 changes: 11 additions & 29 deletions setup.py
Expand Up @@ -7,38 +7,20 @@
use_setuptools()
from setuptools import setup, find_packages

setup(name="Hop",
version="0.3.1",
description="Hop analyses solvent dynamics in molecular dynamics trajectories",
long_description="""\
Hop performs a 'hopping analysis' of molecules in molecular dynamics
(MD) trajectories. Typically, these molecules are water molecules. The
movement of all waters is tracked as they move between hydration
sites. Hydration sites are interpreted as vertices in a graph, while
movement (or 'hops') between them are taken as edges. In this way
hydration is characterized at a coarse grained level as a network of
hops with rate constants and fluxes derived from the MD simulations.\
setup(name="GridDataFormats",
version="0.1",
description="A python library for reading and writing gridded data",
long_description="""The gridDataFormats package provides classes to
unify reading and writing n-dimensional datasets. At the moment this simply
means reading and writing OpenDX files.
""",
author="Oliver Beckstein",
author_email="orbeckst@gmail.com",
license="GPLv3",
url="http://sbcb.bioch.ox.ac.uk/oliver/software/#Hop",
keywords="science 'molecular dynamics' analysis hydration water",
packages=find_packages(exclude=['tests','extras','doc/examples']),
package_data = {'vmd': ['*.tcl']},
install_requires=['numpy>=1.0.3',
'scipy',
'networkx>1.0',
'MDAnalysis>=0.6.3', # or get 0.6.4-dev from svn
],
dependency_links = [
"http://code.google.com/p/mdanalysis/downloads/list",
],
extras_require={
'plotting': ['matplotlib>=0.91.3', # probably already installed
'pygraphviz', # only needed when plotting, not needed for graph building
]
'heatmap': ['rpy'], # optional,used for heatmaps
},
zip_safe=True, # vmdcontrol uses pkg_resources to find vmd tcl script
keywords="science",
packages=find_packages(exclude=[]),
package_data = {},
install_requires=['numpy>=1.0.3'],
zip_safe=True,
)

0 comments on commit ca14c6c

Please sign in to comment.