Skip to content

Commit 4e2f938

Browse files
System Userpicca
authored andcommitted
first attempt of a soleil reader which can read a stack of images.
1 parent 52d41f0 commit 4e2f938

3 files changed

Lines changed: 258 additions & 51 deletions

File tree

fabio/fabioformats.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def importer(module_name):
8282
("raxisimage", "RaxisImage"),
8383
("numpyimage", "NumpyImage"),
8484
("eigerimage", "EigerImage"),
85+
("soleilimage", "SoleilImage"),
8586
("hdf5image", "Hdf5Image"),
8687
("fit2dimage", "Fit2dImage"),
8788
("speimage", "SpeImage"),

fabio/openimage.py

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import logging
4747
logger = logging.getLogger(__name__)
4848
from . import fabioutils
49-
from .fabioutils import FilenameObject, BytesIO
49+
from .fabioutils import FilenameObject, BytesIO, NotGoodReader
5050
from .fabioimage import FabioImage
5151

5252
# Make sure to load all formats
@@ -105,18 +105,40 @@ def do_magic(byts, filename):
105105
if "/" in format_type:
106106
if format_type == "eiger/hdf5":
107107
if "::" in filename:
108-
return "hdf5"
108+
return ["hdf5"]
109109
else:
110-
return "eiger"
110+
return ["eiger", "soleil"]
111111
elif format_type == "marccd/tif":
112112
if "mccd" in filename.split("."):
113-
return "marccd"
113+
return ["marccd"]
114114
else:
115-
return "tif"
116-
return format_type
117-
raise Exception("Could not interpret magic string")
115+
return ["tif"]
116+
return [format_type]
117+
return None
118118

119119

120+
def openimage_str(filename, frame):
121+
logger.debug("Attempting to open %s" % (filename))
122+
objs = _openimage(filename)
123+
for obj in objs:
124+
try:
125+
return obj.read(filename, frame)
126+
except NotGoodReader as ex:
127+
pass
128+
raise NotGoodReader("Did not found a good reader for {}".format(filename))
129+
130+
def openimage_PathTypes(filename, frame):
131+
if not isinstance(filename, fabioutils.StringTypes):
132+
filename = str(filename)
133+
openimage_str(filename, frame)
134+
135+
def openimage_FilenameObject(filename, frame):
136+
try:
137+
return openimage_str(filename.tostring(), frame)
138+
except Exception as ex:
139+
logger.debug("Exception %s, trying name %s" % (ex, filename.stem))
140+
return openimage_str(filename.stem, filename.num)
141+
120142
def openimage(filename, frame=None):
121143
"""Open an image.
122144
@@ -134,42 +156,29 @@ def openimage(filename, frame=None):
134156
:param Union[int,None] frame: A specific frame inside this file.
135157
:rtype: FabioImage
136158
"""
137-
if isinstance(filename, fabioutils.PathTypes):
138-
if not isinstance(filename, fabioutils.StringTypes):
139-
filename = str(filename)
140-
141-
if isinstance(filename, FilenameObject):
142-
try:
143-
logger.debug("Attempting to open %s" % (filename.tostring()))
144-
obj = _openimage(filename.tostring())
145-
logger.debug("Attempting to read frame %s from %s with reader %s" % (frame, filename.tostring(), obj.classname))
146-
obj = obj.read(filename.tostring(), frame)
147-
except Exception as ex:
148-
# multiframe file
149-
# logger.debug( "DEBUG: multiframe file, start # %d"%(
150-
# filename.num)
151-
logger.debug("Exception %s, trying name %s" % (ex, filename.stem))
152-
obj = _openimage(filename.stem)
153-
logger.debug("Reading frame %s from %s" % (filename.num, filename.stem))
154-
obj.read(filename.stem, frame=filename.num)
155-
else:
156-
logger.debug("Attempting to open %s" % (filename))
157-
obj = _openimage(filename)
158-
logger.debug("Attempting to read frame %s from %s with reader %s" % (frame, filename, obj.classname))
159-
obj = obj.read(obj.filename, frame)
160-
return obj
161-
159+
print(type(filename))
160+
if isinstance(filename, str):
161+
return openimage_str(filename, frame)
162+
elif isinstance(filename, unicode):
163+
return openimage_str(str(filename), frame)
164+
elif isinstance(filename, fabioutils.PathTypes):
165+
return openimage_PathTypes(filename, frame)
166+
elif isinstance(filename, FilenameObject):
167+
return openimage_FilenameObject(filename, frame)
162168

163169
def openheader(filename):
164170
""" return only the header"""
165171
if isinstance(filename, fabioutils.PathTypes):
166172
if not isinstance(filename, fabioutils.StringTypes):
167173
filename = str(filename)
168174

169-
obj = _openimage(filename)
170-
obj.readheader(obj.filename)
171-
return obj
172-
175+
objs = _openimage(filename)
176+
for obj in objs:
177+
try:
178+
obj.readheader(obj.filename)
179+
return obj
180+
except NotGoodReader:
181+
pass
173182

174183
def _openimage(filename):
175184
"""
@@ -205,10 +214,8 @@ def _openimage(filename):
205214
else:
206215
imo = None
207216

208-
filetype = None
209-
try:
210-
filetype = do_magic(magic_bytes, filename)
211-
except Exception:
217+
filetypes = do_magic(magic_bytes, filename)
218+
if filetypes is None:
212219
logger.debug("Backtrace", exc_info=True)
213220
try:
214221
file_obj = FilenameObject(filename=filename)
@@ -219,27 +226,33 @@ def _openimage(filename):
219226
isinstance(file_obj.format, list):
220227
# one of OXD/ADSC - should have got in previous
221228
raise Exception("openimage failed on magic bytes & name guess")
222-
filetype = file_obj.format
223-
229+
if file_obj.format is not None:
230+
filetypes = [file_obj.format]
224231
except Exception:
225232
logger.debug("Backtrace", exc_info=True)
226233
raise IOError("Fabio could not identify " + filename)
227234

228-
if filetype is None:
235+
if filetypes is None:
229236
raise IOError("Fabio could not identify " + filename)
230237

231-
klass_name = "".join(filetype) + 'image'
238+
objs = []
239+
for filetype in filetypes:
240+
klass_name = "".join(filetype) + 'image'
241+
try:
242+
obj = fabioformats.factory(klass_name)
243+
objs.append(obj)
244+
except (RuntimeError, Exception):
245+
pass
232246

233-
try:
234-
obj = fabioformats.factory(klass_name)
235-
except (RuntimeError, Exception):
247+
if len(objs) == 0:
236248
logger.debug("Backtrace", exc_info=True)
237-
raise IOError("Filename %s can't be read as format %s" % (filename, klass_name))
249+
raise IOError("Filename %s can't be read as format %s" % (filename, filetypes))
238250

239-
obj.filename = filename
240-
# skip the read for read header
241-
return obj
251+
for obj in objs:
252+
obj.filename = filename
242253

254+
# skip the read for read header
255+
return objs
243256

244257
def open_series(filenames=None, first_filename=None,
245258
single_frame=None, fixed_frames=None, fixed_frame_number=None):

fabio/soleilimage.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# coding: utf-8
2+
#
3+
# Project: FabIO X-ray image reader
4+
#
5+
# Copyright (C) 2010-2016 European Synchrotron Radiation Facility
6+
# Grenoble, France
7+
#
8+
# Copyright (C) 2019 Synchrotron-SOLEIL
9+
# Gif-sur-Yvette, France
10+
#
11+
# Permission is hereby granted, free of charge, to any person obtaining a copy
12+
# of this software and associated documentation files (the "Software"), to deal
13+
# in the Software without restriction, including without limitation the rights
14+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
# copies of the Software, and to permit persons to whom the Software is
16+
# furnished to do so, subject to the following conditions:
17+
#
18+
# The above copyright notice and this permission notice shall be included in
19+
# all copies or substantial portions of the Software.
20+
#
21+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
# THE SOFTWARE.
28+
#
29+
30+
31+
"""Soleil HDF5 image for FabIO
32+
33+
Authors: Jerome Kieffer, Picca Frédéric-Emmanuel
34+
email: Jerome.Kieffer@terre-adelie.org, picca@synchrotron-soleil.fr
35+
36+
Specifications:
37+
input should being the form:
38+
39+
filename
40+
41+
Only supports ndim=2 or 3 (exposed as a stack of images)
42+
"""
43+
44+
from __future__ import with_statement, print_function, division
45+
46+
__authors__ = ["Jérôme Kieffer", "Picca Frédéric-Emmanuel"]
47+
__contact__ = "Jerome.Kieffer@terre-adelie.org"
48+
__license__ = "MIT"
49+
__copyright__ = "Jérôme Kieffer"
50+
__date__ = "01/03/2019"
51+
52+
import logging
53+
54+
logger = logging.getLogger(__name__)
55+
56+
from collections import namedtuple
57+
from functools import partial
58+
59+
try:
60+
import h5py
61+
from h5py import Dataset, File
62+
except ImportError:
63+
h5py = None
64+
65+
from fabio.fabioimage import FabioFrame, FabioImage
66+
from .fabioutils import previous_filename, next_filename
67+
68+
# Generic hdf5 access types.
69+
70+
DatasetPathContains = namedtuple("DatasetPathContains", "path")
71+
DatasetPathWithAttribute = namedtuple("DatasetPathWithAttribute", "attribute value")
72+
73+
def _v_attrs(attribute, value, _name, obj):
74+
"""extract all the images and accumulate them in the acc variable"""
75+
if isinstance(obj, Dataset):
76+
if attribute in obj.attrs and obj.attrs[attribute] == value:
77+
return obj
78+
79+
80+
def _v_item(key, name, obj):
81+
if key in name:
82+
return obj
83+
84+
85+
def get_dataset(h5file, path):
86+
res = None
87+
if isinstance(path, DatasetPathContains):
88+
res = h5file.visititems(partial(_v_item, path.path))
89+
elif isinstance(path, DatasetPathWithAttribute):
90+
res = h5file.visititems(partial(_v_attrs,
91+
path.attribute, path.value))
92+
return res
93+
94+
95+
class SoleilFrame(FabioFrame):
96+
"""Identify a slice of dataset from an HDF5 file"""
97+
98+
def __init__(self, soleil_image, frame_num):
99+
if not isinstance(soleil_image, SoleilImage):
100+
raise TypeError("Expected class {SoleilImage}".format(SoleilImage))
101+
data = soleil_image.dataset[frame_num, :, :]
102+
super(SoleilFrame, self).__init__(data=data, header=soleil_image.header)
103+
self.hdf5 = soleil_image.hdf5
104+
self.dataset = soleil_image.dataset
105+
self.filename = soleil_image.filename
106+
self._nframes = soleil_image.nframes
107+
self.header = soleil_image.header
108+
self.currentframe = frame_num
109+
110+
111+
class SoleilImage(FabioImage):
112+
"""
113+
FabIO image class for Images from an Soleil HDF file
114+
115+
filename::dataset
116+
"""
117+
118+
DESCRIPTION = "Soleil Hierarchical Data Format HDF5 flat reader"
119+
120+
DEFAULT_EXTENSIONS = ["nxs", "h5"]
121+
122+
def __init__(self, *arg, **kwargs):
123+
if not h5py:
124+
raise RuntimeError("fabio.SoleilImage cannot be used without h5py. Please install h5py and restart")
125+
super(SoleilImage, self).__init__(*arg, **kwargs)
126+
self.hdf5 = None
127+
self.dataset = None
128+
129+
def read(self, filename, frame=None):
130+
"""
131+
try to read image
132+
:param fname: filename
133+
"""
134+
self.resetvals()
135+
136+
path = DatasetPathWithAttribute("interpretation", b"image")
137+
self.filename = filename
138+
self.hdf5 = File(self.filename, "r")
139+
self.dataset = get_dataset(self.hdf5, path)
140+
141+
# ndim does not exist for external links ?
142+
ndim = len(self.dataset.shape)
143+
if ndim == 3:
144+
self._nframes = self.dataset.shape[0]
145+
if frame is not None:
146+
self.currentframe = int(frame)
147+
else:
148+
self.currentframe = 0
149+
self.data = self.dataset[self.currentframe, :, :]
150+
elif ndim == 2:
151+
self.data = self.dataset[:, :]
152+
else:
153+
err = "Only 2D and 3D datasets are supported by FabIO, here %sD" % self.dataset.ndim
154+
logger.error(err)
155+
raise RuntimeError(err)
156+
return self
157+
158+
def getframe(self, num):
159+
"""
160+
Returns a frame as a new FabioImage object
161+
:param num: frame number
162+
"""
163+
if num < 0 or num > self.nframes:
164+
raise RuntimeError("Requested frame number %i is out of range [0, %i[ " % (num, self.nframes))
165+
# Do a deep copy of the header to make a new one
166+
return SoleilFrame(self, num)
167+
168+
def next(self):
169+
"""
170+
Get the next image in a series as a fabio image
171+
"""
172+
if self.currentframe < (self.nframes - 1):
173+
return self.getframe(self.currentframe + 1)
174+
else:
175+
newobj = SoleilImage()
176+
newobj.read(next_filename(self.filename))
177+
return newobj
178+
179+
def previous(self):
180+
"""
181+
Get the previous image in a series as a fabio image
182+
"""
183+
if self.currentframe > 0:
184+
return self.getframe(self.currentframe - 1)
185+
else:
186+
newobj = SoleilImage()
187+
newobj.read(previous_filename(self.filename))
188+
return newobj
189+
190+
def close(self):
191+
if self.hdf5 is not None:
192+
self.hdf5.close()
193+
self.dataset = None

0 commit comments

Comments
 (0)