Skip to content

Commit

Permalink
initial base classes and qpimage support
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmueller committed Oct 13, 2017
1 parent 46ba0c7 commit 2d1f677
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 62 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ include LICENSE
include README.rst
recursive-include examples *.py *.jpg
recursive-include docs *.py *.md *.txt *.rst
recursive-include tests *.py *.md *.zip
recursive-include tests *.py *.md *.zip *.h5
prune docs/_build
1 change: 1 addition & 0 deletions qpformat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .core import load_data # noqa: F401
29 changes: 20 additions & 9 deletions qpformat/core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
from . import file_formats
from .file_formats import formats, formats_dict, UnknownFileFormatError


def guess_format(path):
"""Determine the file format of a folder or a file"""
for fmt in formats:
if fmt.verify(path):
return fmt.__name__
else:
raise UnknownFileFormatError(path)


def load_data(path, fmt=None, bg_path=None, bg_fmt=None,
h5out=None):
"""Load experimental data
Parameters
----------
path: str
Expand All @@ -25,21 +34,23 @@ def load_data(path, fmt=None, bg_path=None, bg_fmt=None,
is written to disk.
meta_data: dict
Meta data (see `qpimage.meta.VALID_META_KEYS`)
Returns
-------
dataobj: `file_formats.Group` or `file_formats.Single`
A qpformat data object with unified access to the
experimental data.
"""
if fmt is None:
fmt = file_formats.guess_format(path)
if bg_fmt is None and bg_path is not None:
bg_fmt = file_formats.guess_format(bg_path)
fmt = guess_format(path)

dataobj = formats_dict[fmt](path)

dataobj = file_formats.formats_dict[fmt](path)
bgobj = file_formats.formats_dict[bg_fmt](bg_path)
dataobj.set_bg(bgobj)
if bg_path is not None:
if bg_fmt is None:
bg_fmt = guess_format(bg_path)
bgobj = formats_dict[bg_fmt](bg_path)
dataobj.set_bg(bgobj)

if h5out is not None:
# TODO:
Expand Down
36 changes: 16 additions & 20 deletions qpformat/file_formats/__init__.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
from .group_folder import GroupFolder
from .group_hdf5_qpimage import GroupHdf5Qimage
from .group_zip_tif_phasics import GroupZipTifPhasics
from .single_hdf5_qimage import SingleHdf5Qimage
from .single_tif_phasics import SingleTifPhasics
# from .group_folder import GroupFolder
# from .group_hdf5_qpimage import GroupHdf5Qpimage
# from .group_zip_tif_phasics import GroupZipTifPhasics
from .single_hdf5_qimage import SingleHdf5Qpimage
# from .single_tif_phasics import SingleTifPhasics


def guess_format(path):
"""Determine the file format of a folder or a file"""
for fmt in formats:
if fmt.verify(path):
return path
class UnknownFileFormatError(BaseException):
pass


# the order is important for
formats = [GroupFolder,
GroupZipTifPhasics,
GroupHdf5Qimage,
SingleHdf5Qimage,
SingleTifPhasics,
]

# the order is important for
formats = [ # GroupFolder,
# GroupZipTifPhasics,
# GroupHdf5Qpimage,
SingleHdf5Qpimage,
# SingleTifPhasics,
]

# convenience dictionary
formats_dict = {}
for _fmt in formats:
formats_dict[_fmt.name] = _fmt
for fmt in formats:
formats_dict[fmt.__name__] = fmt
120 changes: 96 additions & 24 deletions qpformat/file_formats/dataset.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,118 @@
import abc
import copy

import qpimage


class DataSet(object):
__meta__ = abs.ABCMeta
__meta__ = abc.ABCMeta

def __init__(self, path, meta_data):
def __init__(self, path, meta_data={}):
self.path = path
self.meta_data = copy.copy(meta_data)
self._bgdata = []

def __repr__(self):
# class name
# path
# length
# if present:
# - resolution
# - wavelength
pass
rep = "QPFormat '{}'".format(self.__class__.__name__) \
+ ", {} event(s)".format(len(self)) \
+ "\nfile: {}".format(self.path)

meta = []
if "wavelength" in self.meta_data:
wl = self.meta_data["wavelength"]
if wl < 2000e-9 and wl > 10e-9:
# convenience for light microscopy
meta.append("λ={:.1f}nm".format(wl * 1e9))
else:
meta.append("λ={:.2e}m".format(wl))
if "pixel size" in self.meta_data:
pxm = self.meta_data["pixel size"]
if pxm < 1e-3 and pxm > 1e-8:
# convenience for light microscopy
meta.append("1px={}µm".format(pxm * 1e6))
else:
meta.append("1px={}m".format(pxm))
rep += ", ".join(meta)
return rep

@abc.abstractmethod
def __len__(self):
return len(self.data)
"""Return number of samples of a data set"""

@abc.abstractmethod
def verify(path):
"""Verify that `path` has this file format
def get_name(self, idx):
"""Return name of data at index `idx`"""
return "{} [{}]".format(self.path, idx)

def get_qpimage(self, idx):
"""Return background-corrected QPImage of data at index `idx`"""
# raw data
qpi = self.get_qpimage_raw(idx)
# bg data
if len(self._bgdata) == 1:
# One background for all
bgidx = 0
else:
bgidx = idx

if isinstance(self._bgdata, DataSet):
bg = self._bgdata.get_qpimage_raw(bgidx)
else:
bg = self._bgdata[bgidx]
qpi.set_bg(bg_data=bg)
return qpi

@abc.abstractmethod
def get_qpimage_raw(self, idx):
"""Return QPImage without background correction"""

Returns `True` if the file format matches.
"""

def get_data(self, idx):
pass

def get_time(self, idx):
"""Return time of data at in dex `idx`
By default, this returns zero and must be
overridden if the file format supports timing.
"""
return 0


def saveh5(self, h5file):
"""Save the data set as an hdf5 file (QPImage format)"""

def set_bg(self, dataset):
"""Set background data
Parameters
----------
dataset: `DataSet` or `qpimage.QPImage`
If the ``len(dataset)`` matches ``len(self)``,
then background correction is performed
element-wise. Otherwise, ``len(dataset)``
must be one and is used for all data of ``self``.
See Also
--------
get_qpimage: obtain the background corrected QPImage
"""

if isinstance(dataset, qpimage.QPImage):
# Single QPImage
self._bgdata = [dataset]
elif (isinstance(dataset, list) and
len(dataset) == len(self) and
isinstance(dataset[0], qpimage.QPImage)):
# List of QPImage
self.bgdata = dataset
elif (isinstance(dataset, DataSet) and
(len(dataset) == 1 or
len(dataset) == len(self))):
# DataSet
self.bgdata = dataset
else:
raise ValueError("Bad length or type for bg: {}".format(dataset))
self._bgdata = dataset

@staticmethod
@abc.abstractmethod
def verify(path):
"""Verify that `path` has this file format
Returns `True` if the file format matches.
"""
pass


5 changes: 5 additions & 0 deletions qpformat/file_formats/group_hdf5_qimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from dataset import DataSet


class GroupHdf5Qpimage(DataSet):
pass
56 changes: 56 additions & 0 deletions qpformat/file_formats/single_hdf5_qimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import h5py
import qpimage

from .dataset import DataSet


class SingleHdf5Qpimage(DataSet):
def __len__(self):
return 1

def get_qpimage(self, idx):
"""Return background-corrected QPImage of data at index `idx`"""
if self._bgdata:
# The user has explicitly chosen different background data
# using `get_qpimage_raw`.
return super(SingleHdf5Qpimage, self).get_qpimage(idx)
else:
# We can use the background data stored in the qpimage hdf5 file
return qpimage.QPImage(h5file=self.path, h5mode="r").copy()

def get_qpimage_raw(self, idx=0):
"""Return QPImage without background correction"""
if idx != 0:
raise ValueError("Single file format, only one entry (`idx!=0`)!")
qpi = qpimage.QPImage(h5file=self.path, h5mode="r").copy()
# Remove previously performed background correction
qpi.set_bg_data(None)
return qpi

def get_time(self, idx=0):
"""Return the time of the QPImage data"""
qpi = qpimage.QPImage(h5file=self.path, h5mode="r")
if "time" in qpi.meta:
return qpi.meta["time"]
else:
return 0

@staticmethod
def verify(path):
"""Verify that `path` has the qpimage file format
Returns `True` if the file format matches.
"""
valid = False
try:
h5 = h5py.File(path, mode="r")
except:
pass
else:
if ("qpimage version" in h5.attrs and
"phase" in h5 and
"amplitude" in h5 and
"bg_data" in h5["phase"] and
"bg_data" in h5["amplitude"]):
valid = True
return valid
Binary file added tests/data/bg_ramp.h5
Binary file not shown.
16 changes: 8 additions & 8 deletions tests/test_minimal.py → tests/test_single_hdf5_qpimage.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import os
from os.path import abspath, dirname
from os.path import abspath, dirname, join
import sys
import tempfile

import numpy as np

# Add parent directory to beginning of path variable
sys.path.insert(0, dirname(dirname(abspath(__file__))))
import qpformat # noqa: E402


def test_nothing():
pass
def test_load_data():
path = join(dirname(abspath(__file__)), "data/bg_ramp.h5")
ds = qpformat.load_data(path)
assert ds.path == path
assert ds.get_time() == 0
assert "SingleHdf5Qpimage" in ds.__repr__()


if __name__ == "__main__":
# Run all tests
loc = locals()
for key in list(loc.keys()):
if key.startswith("test_") and hasattr(loc[key], "__call__"):
loc[key]()

0 comments on commit 2d1f677

Please sign in to comment.