Skip to content

Commit

Permalink
first attempt of a soleil reader which can read a stack of images.
Browse files Browse the repository at this point in the history
  • Loading branch information
System User authored and picca committed Jul 26, 2019
1 parent 52d41f0 commit bc3aae3
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 51 deletions.
1 change: 1 addition & 0 deletions fabio/fabioformats.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def importer(module_name):
("raxisimage", "RaxisImage"),
("numpyimage", "NumpyImage"),
("eigerimage", "EigerImage"),
("soleilimage", "SoleilImage"),
("hdf5image", "Hdf5Image"),
("fit2dimage", "Fit2dImage"),
("speimage", "SpeImage"),
Expand Down
114 changes: 63 additions & 51 deletions fabio/openimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
import logging
logger = logging.getLogger(__name__)
from . import fabioutils
from .fabioutils import FilenameObject, BytesIO
from .fabioutils import FilenameObject, BytesIO, NotGoodReader
from .fabioimage import FabioImage

# Make sure to load all formats
Expand Down Expand Up @@ -105,18 +105,40 @@ def do_magic(byts, filename):
if "/" in format_type:
if format_type == "eiger/hdf5":
if "::" in filename:
return "hdf5"
return ["hdf5"]
else:
return "eiger"
return ["eiger", "soleil"]
elif format_type == "marccd/tif":
if "mccd" in filename.split("."):
return "marccd"
return ["marccd"]
else:
return "tif"
return format_type
raise Exception("Could not interpret magic string")
return ["tif"]
return [format_type]
return None


def openimage_str(filename, frame):
logger.debug("Attempting to open %s" % (filename))
objs = _openimage(filename)
for obj in objs:
try:
return obj.read(filename, frame)
except NotGoodReader as ex:
pass
raise NotGoodReader("Did not found a good reader for {}".format(filename))

def openimage_PathTypes(filename, frame):
if not isinstance(filename, fabioutils.StringTypes):
filename = str(filename)
openimage_str(filename, frame)

def openimage_FilenameObject(filename, frame):
try:
return openimage_str(filename.tostring(), frame)
except Exception as ex:
logger.debug("Exception %s, trying name %s" % (ex, filename.stem))
return openimage_str(filename.stem, filename.num)

def openimage(filename, frame=None):
"""Open an image.
Expand All @@ -134,42 +156,28 @@ def openimage(filename, frame=None):
:param Union[int,None] frame: A specific frame inside this file.
:rtype: FabioImage
"""
if isinstance(filename, fabioutils.PathTypes):
if not isinstance(filename, fabioutils.StringTypes):
filename = str(filename)

if isinstance(filename, FilenameObject):
try:
logger.debug("Attempting to open %s" % (filename.tostring()))
obj = _openimage(filename.tostring())
logger.debug("Attempting to read frame %s from %s with reader %s" % (frame, filename.tostring(), obj.classname))
obj = obj.read(filename.tostring(), frame)
except Exception as ex:
# multiframe file
# logger.debug( "DEBUG: multiframe file, start # %d"%(
# filename.num)
logger.debug("Exception %s, trying name %s" % (ex, filename.stem))
obj = _openimage(filename.stem)
logger.debug("Reading frame %s from %s" % (filename.num, filename.stem))
obj.read(filename.stem, frame=filename.num)
else:
logger.debug("Attempting to open %s" % (filename))
obj = _openimage(filename)
logger.debug("Attempting to read frame %s from %s with reader %s" % (frame, filename, obj.classname))
obj = obj.read(obj.filename, frame)
return obj

if isinstance(filename, str):
return openimage_str(filename, frame)
elif isinstance(filename, unicode):
return openimage_str(str(filename), frame)
elif isinstance(filename, fabioutils.PathTypes):
return openimage_PathTypes(filename, frame)
elif isinstance(filename, FilenameObject):
return openimage_FilenameObject(filename, frame)

def openheader(filename):
""" return only the header"""
if isinstance(filename, fabioutils.PathTypes):
if not isinstance(filename, fabioutils.StringTypes):
filename = str(filename)

obj = _openimage(filename)
obj.readheader(obj.filename)
return obj

objs = _openimage(filename)
for obj in objs:
try:
obj.readheader(obj.filename)
return obj
except NotGoodReader:
pass

def _openimage(filename):
"""
Expand Down Expand Up @@ -205,10 +213,8 @@ def _openimage(filename):
else:
imo = None

filetype = None
try:
filetype = do_magic(magic_bytes, filename)
except Exception:
filetypes = do_magic(magic_bytes, filename)
if filetypes is None:
logger.debug("Backtrace", exc_info=True)
try:
file_obj = FilenameObject(filename=filename)
Expand All @@ -219,27 +225,33 @@ def _openimage(filename):
isinstance(file_obj.format, list):
# one of OXD/ADSC - should have got in previous
raise Exception("openimage failed on magic bytes & name guess")
filetype = file_obj.format

if file_obj.format is not None:
filetypes = [file_obj.format]
except Exception:
logger.debug("Backtrace", exc_info=True)
raise IOError("Fabio could not identify " + filename)

if filetype is None:
if filetypes is None:
raise IOError("Fabio could not identify " + filename)

klass_name = "".join(filetype) + 'image'
objs = []
for filetype in filetypes:
klass_name = "".join(filetype) + 'image'
try:
obj = fabioformats.factory(klass_name)
objs.append(obj)
except (RuntimeError, Exception):
pass

try:
obj = fabioformats.factory(klass_name)
except (RuntimeError, Exception):
if len(objs) == 0:
logger.debug("Backtrace", exc_info=True)
raise IOError("Filename %s can't be read as format %s" % (filename, klass_name))
raise IOError("Filename %s can't be read as format %s" % (filename, filetypes))

obj.filename = filename
# skip the read for read header
return obj
for obj in objs:
obj.filename = filename

# skip the read for read header
return objs

def open_series(filenames=None, first_filename=None,
single_frame=None, fixed_frames=None, fixed_frame_number=None):
Expand Down
193 changes: 193 additions & 0 deletions fabio/soleilimage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# coding: utf-8
#
# Project: FabIO X-ray image reader
#
# Copyright (C) 2010-2016 European Synchrotron Radiation Facility
# Grenoble, France
#
# Copyright (C) 2019 Synchrotron-SOLEIL
# Gif-sur-Yvette, France
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#


"""Soleil HDF5 image for FabIO
Authors: Jerome Kieffer, Picca Frédéric-Emmanuel
email: [email protected], [email protected]
Specifications:
input should being the form:
filename
Only supports ndim=2 or 3 (exposed as a stack of images)
"""

from __future__ import with_statement, print_function, division

__authors__ = ["Jérôme Kieffer", "Picca Frédéric-Emmanuel"]
__contact__ = "[email protected]"
__license__ = "MIT"
__copyright__ = "Jérôme Kieffer"
__date__ = "01/03/2019"

import logging

logger = logging.getLogger(__name__)

from collections import namedtuple
from functools import partial

try:
import h5py
from h5py import Dataset, File
except ImportError:
h5py = None

from fabio.fabioimage import FabioFrame, FabioImage
from .fabioutils import previous_filename, next_filename

# Generic hdf5 access types.

DatasetPathContains = namedtuple("DatasetPathContains", "path")
DatasetPathWithAttribute = namedtuple("DatasetPathWithAttribute", "attribute value")

def _v_attrs(attribute, value, _name, obj):
"""extract all the images and accumulate them in the acc variable"""
if isinstance(obj, Dataset):
if attribute in obj.attrs and obj.attrs[attribute] == value:
return obj


def _v_item(key, name, obj):
if key in name:
return obj


def get_dataset(h5file, path):
res = None
if isinstance(path, DatasetPathContains):
res = h5file.visititems(partial(_v_item, path.path))
elif isinstance(path, DatasetPathWithAttribute):
res = h5file.visititems(partial(_v_attrs,
path.attribute, path.value))
return res


class SoleilFrame(FabioFrame):
"""Identify a slice of dataset from an HDF5 file"""

def __init__(self, soleil_image, frame_num):
if not isinstance(soleil_image, SoleilImage):
raise TypeError("Expected class {SoleilImage}".format(SoleilImage))
data = soleil_image.dataset[frame_num, :, :]
super(SoleilFrame, self).__init__(data=data, header=soleil_image.header)
self.hdf5 = soleil_image.hdf5
self.dataset = soleil_image.dataset
self.filename = soleil_image.filename
self._nframes = soleil_image.nframes
self.header = soleil_image.header
self.currentframe = frame_num


class SoleilImage(FabioImage):
"""
FabIO image class for Images from an Soleil HDF file
filename::dataset
"""

DESCRIPTION = "Soleil Hierarchical Data Format HDF5 flat reader"

DEFAULT_EXTENSIONS = ["nxs", "h5"]

def __init__(self, *arg, **kwargs):
if not h5py:
raise RuntimeError("fabio.SoleilImage cannot be used without h5py. Please install h5py and restart")
super(SoleilImage, self).__init__(*arg, **kwargs)
self.hdf5 = None
self.dataset = None

def read(self, filename, frame=None):
"""
try to read image
:param fname: filename
"""
self.resetvals()

path = DatasetPathWithAttribute("interpretation", b"image")
self.filename = filename
self.hdf5 = File(self.filename, "r")
self.dataset = get_dataset(self.hdf5, path)

# ndim does not exist for external links ?
ndim = len(self.dataset.shape)
if ndim == 3:
self._nframes = self.dataset.shape[0]
if frame is not None:
self.currentframe = int(frame)
else:
self.currentframe = 0
self.data = self.dataset[self.currentframe, :, :]
elif ndim == 2:
self.data = self.dataset[:, :]
else:
err = "Only 2D and 3D datasets are supported by FabIO, here %sD" % self.dataset.ndim
logger.error(err)
raise RuntimeError(err)
return self

def getframe(self, num):
"""
Returns a frame as a new FabioImage object
:param num: frame number
"""
if num < 0 or num > self.nframes:
raise RuntimeError("Requested frame number %i is out of range [0, %i[ " % (num, self.nframes))
# Do a deep copy of the header to make a new one
return SoleilFrame(self, num)

def next(self):
"""
Get the next image in a series as a fabio image
"""
if self.currentframe < (self.nframes - 1):
return self.getframe(self.currentframe + 1)
else:
newobj = SoleilImage()
newobj.read(next_filename(self.filename))
return newobj

def previous(self):
"""
Get the previous image in a series as a fabio image
"""
if self.currentframe > 0:
return self.getframe(self.currentframe - 1)
else:
newobj = SoleilImage()
newobj.read(previous_filename(self.filename))
return newobj

def close(self):
if self.hdf5 is not None:
self.hdf5.close()
self.dataset = None

0 comments on commit bc3aae3

Please sign in to comment.