import pathlib
import h5py
import numpy as np
import qpimage
from .image_data import Fluorescence
from .meta import FLMetaDict, DATA_KEYS_FL, META_KEYS_FL
from ._version import version as __version__
[docs]class FLImage(object):
# required to create in-memory hdf5 files with unique fd
_instances = 0
def __init__(self, data=None, meta_data={}, h5file=None, h5mode="a",
h5dtype="float32",
):
"""Fluorescence image manipulation
This class makes available fluorescence microscopy data in a
manner similar to :class:`qpimage.QPImage`.
Parameters
----------
data: 2d ndarray (float or complex) or list
The experimental fluorescence image.
meta_data: dict
Meta data associated with the input data.
see :data:`flimage.meta.META_KEYS`
h5file: str, pathlib.Path, h5py.Group, h5py.File, or None
A path to an hdf5 data file where all data is cached. If
set to `None` (default), all data will be handled in
memory using the "core" driver of the :mod:`h5py`'s
:class:`h5py:File` class. If the file does not exist,
it is created. If the file already exists, it is opened
with the file mode defined by `hdf5_mode`. If this is
an instance of h5py.Group or h5py.File, then this will
be used to internally store all data.
h5mode: str
Valid file modes are (only applies if `h5file` is a path)
- "r": Readonly, file must exist
- "r+": Read/write, file must exist
- "w": Create file, truncate if exists
- "w-" or "x": Create file, fail if exists
- "a": Read/write if exists, create otherwise (default)
h5dtype: str
The datatype in which to store the image data. The default
is "float32" which is sufficient for 2D image analysis and
consumes only half the disk space of the numpy default
"float64".
"""
if (data is not None and
not isinstance(data, (np.ndarray, list, tuple))):
msg = "`data` must be numpy.ndarray!"
if isinstance(data, (str, pathlib.Path)):
msg += " Did you mean `h5file={}`?".format(data)
raise ValueError(msg)
if isinstance(h5file, h5py.Group):
self.h5 = h5file
self._do_h5_cleanup = False
else:
if h5file is None:
h5kwargs = {"name": "flimage{}.h5".format(FLImage._instances),
"driver": "core",
"backing_store": False,
"mode": "w"}
else:
h5kwargs = {"name": h5file,
"mode": h5mode}
self.h5 = h5py.File(**h5kwargs)
self._do_h5_cleanup = True
FLImage._instances += 1
# set meta data
meta = FLMetaDict(meta_data)
for key in meta:
self.h5.attrs[key] = meta[key]
if "qpimage version" not in self.h5.attrs:
self.h5.attrs["qpimage version"] = qpimage.__version__
if "flimage version" not in self.h5.attrs:
self.h5.attrs["flimage version"] = __version__
# set data
self._fl = Fluorescence(self.h5.require_group("fluorescence"),
h5dtype=h5dtype)
if data is not None:
self._fl["raw"] = data
self.h5dtype = h5dtype
def __enter__(self):
return self
def __eq__(self, other):
datame = [self.meta[k] for k in self.meta if k in DATA_KEYS_FL]
dataot = [other.meta[k] for k in other.meta if k in DATA_KEYS_FL]
if (isinstance(other, FLImage) and
self.shape == other.shape and
np.allclose(self.fl, other.fl) and
datame == dataot):
return True
else:
return False
def __exit__(self, exc_type, exc_val, exc_tb):
if self._do_h5_cleanup:
self.h5.flush()
self.h5.close()
def __contains__(self, key):
return key in self.h5.attrs
def __getitem__(self, given):
"""Slice FLImage `fl` and return a new FLImage
The background data of the returned QPImage is merged into
the "data" background array, i.e. there will be no other
background array.
"""
if isinstance(given, (slice, tuple)):
# return new QPImage
fli = FLImage(data=self.raw_fl[given],
meta_data=self.meta)
return fli
elif isinstance(given, str):
# return meta data
return self.meta[given]
else:
msg = "Only slicing and meta data keys allowed for `__getitem__`"
raise ValueError(msg)
def __repr__(self):
if "identifier" in self:
ident = self["identifier"]
else:
ident = hex(id(self))
rep = "FLImage <{}>, {x}x{y}px".format(ident,
x=self._fl.raw.shape[0],
y=self._fl.raw.shape[1],
)
if "wavelength" in self:
wl = self["wavelength"]
if wl < 2000e-9 and wl > 10e-9:
# convenience for light microscopy
rep += ", λ={:.1f}nm".format(wl * 1e9)
else:
rep += ", λ={:.2e}m".format(wl)
return rep
def __setitem__(self, key, value):
if key not in META_KEYS_FL:
raise KeyError("Unknown meta data key: {}".format(key))
else:
self.h5.attrs[key] = value
@property
def bg_fl(self):
"""background fluorescence image"""
return self._fl.bg
@property
def fl(self):
"""background-corrected fluorescence image"""
return self._fl.image
@property
def dtype(self):
"""dtype of the fluorescence data array"""
return self._fl.raw.dtype
@property
def info(self):
"""list of tuples with FLImage meta data"""
info = []
# meta data
meta = self.meta
for key in meta:
info.append((key, self.meta[key]))
# background correction
info += self._fl.info
return info
@property
def meta(self):
"""dictionary with imaging meta data"""
return FLMetaDict(self.h5.attrs)
@property
def raw_fl(self):
"""raw fluorescence image"""
return self._fl.raw
@property
def shape(self):
"""size of image dimensions"""
return self._fl.h5["raw"].shape
[docs] def copy(self, h5file=None):
"""Create a copy of the current instance
This is done by recursively copying the underlying hdf5 data.
Parameters
----------
h5file: str, h5py.File, h5py.Group, or None
see `FLImage.__init__`
"""
h5 = qpimage.core.copyh5(self.h5, h5file)
return FLImage(h5file=h5, h5dtype=self.h5dtype)
[docs] def set_bg_data(self, bg_data):
"""Set background fluorescence data
Parameters
----------
bg_data: 2d ndarray, FLImage, or `None`
The background data (must be same type as `data`).
If set to `None`, the background data is reset.
"""
if isinstance(bg_data, FLImage):
fl = bg_data.fl
elif bg_data is None:
# Reset phase and amplitude
fl = None
else:
# Compute phase and amplitude from bg_data
fl = bg_data
# Set background data
self._fl.set_bg(fl, key="data")