44__contact__ = "Jerome.Kieffer@ESRF.eu"
55__license__ = "MIT"
66__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
7- __date__ = "29/01/2024 "
7+ __date__ = "20/02/2025 "
88__status__ = "production"
99__docformat__ = 'restructuredtext'
1010
1313import time
1414import logging
1515import h5py
16+ import atexit
1617logger = logging .getLogger (__name__ )
1718
1819
@@ -70,7 +71,7 @@ def is_hdf5(filename):
7071 return sig == signature
7172
7273
73- class Nexus ( object ) :
74+ class Nexus :
7475 """
7576 Writer class to handle Nexus/HDF5 data
7677
@@ -85,13 +86,17 @@ class Nexus(object):
8586 TODO: make it thread-safe !!!
8687 """
8788
88- def __init__ (self , filename , mode = None , creator = None , start_time = None ):
89+ def __init__ (self , filename , mode = None ,
90+ creator = None ,
91+ timeout = None ,
92+ start_time = None ):
8993 """
9094 Constructor
9195
9296 :param filename: name of the hdf5 file containing the nexus
9397 :param mode: can be 'r', 'a', 'w', '+' ....
9498 :param creator: set as attr of the NXroot
99+ :param timeout: retry for that amount of time (in seconds)
95100 :param start_time: set as attr of the NXroot
96101 """
97102 self .filename = os .path .abspath (filename )
@@ -107,15 +112,33 @@ def __init__(self, filename, mode=None, creator=None, start_time=None):
107112 else :
108113 self .mode = "a"
109114
110- if self .mode == "r" and h5py .version .version_tuple >= (2 , 9 ):
111- self .file_handle = open (self .filename , mode = self .mode + "b" )
112- self .h5 = h5py .File (self .file_handle , mode = self .mode )
115+ if timeout :
116+ end = time .perf_counter () + timeout
117+ while time .perf_counter () < end :
118+ try :
119+ if self .mode == "r" :
120+ self .file_handle = open (self .filename , mode = "rb" )
121+ self .h5 = h5py .File (self .file_handle , mode = "r" )
122+ else :
123+ self .file_handle = None
124+ self .h5 = h5py .File (self .filename , mode = self .mode )
125+ except OSError :
126+ os .stat (os .path .dirname (self .filename ))
127+ time .sleep (1 )
128+ else :
129+ break
130+ else :
131+ raise OSError (f"Unable to open HDF5 file { self .filename } " )
113132 else :
114- self .file_handle = None
115- self .h5 = h5py .File (self .filename , mode = self .mode )
133+ if self .mode == "r" :
134+ self .file_handle = open (self .filename , mode = self .mode + "b" )
135+ self .h5 = h5py .File (self .file_handle , mode = self .mode )
136+ else :
137+ self .file_handle = None
138+ self .h5 = h5py .File (self .filename , mode = self .mode )
116139 self .to_close = []
117-
118- if not pre_existing or "w" in mode :
140+ atexit . register ( self . close )
141+ if not pre_existing :
119142 self .h5 .attrs ["NX_class" ] = "NXroot"
120143 self .h5 .attrs ["file_time" ] = get_isotime (start_time )
121144 self .h5 .attrs ["file_name" ] = self .filename
@@ -143,6 +166,7 @@ def __exit__(self, *arg, **kwarg):
143166 self .close ()
144167
145168 def flush (self ):
169+ "write to disk"
146170 if self .h5 :
147171 self .h5 .flush ()
148172
@@ -199,7 +223,7 @@ def new_entry(self, entry="entry", program_name="pyFAI",
199223
200224 :param entry: name of the entry
201225 :param program_name: value of the field as string
202- :param title: value of the field as string
226+ :param title: description of experiment as str
203227 :param force_time: enforce the start_time (as string!)
204228 :param force_name: force the entry name as such, without numerical suffix.
205229 :return: the corresponding HDF5 group
@@ -211,7 +235,7 @@ def new_entry(self, entry="entry", program_name="pyFAI",
211235 entry_grp = self .h5 .require_group (entry )
212236 self .h5 .attrs ["default" ] = entry
213237 entry_grp .attrs ["NX_class" ] = "NXentry"
214- entry_grp ["title" ] = title
238+ entry_grp ["title" ] = str ( title )
215239 entry_grp ["program_name" ] = program_name
216240 if isinstance (force_time , str ):
217241 entry_grp ["start_time" ] = force_time
@@ -230,16 +254,18 @@ def new_instrument(self, entry="entry", instrument_name="id00",):
230254# howto external link
231255 # myfile['ext link'] = h5py.ExternalLink("otherfile.hdf5", "/path/to/resource")
232256
233- def new_class (self , grp , name , class_type = "NXcollection" ):
257+ @staticmethod
258+ def new_class (grp , name , class_type = "NXcollection" ):
234259 """
235260 create a new sub-group with type class_type
261+
236262 :param grp: parent group
237263 :param name: name of the sub-group
238264 :param class_type: NeXus class name
239265 :return: subgroup created
240266 """
241267 sub = grp .require_group (name )
242- sub .attrs ["NX_class" ] = class_type
268+ sub .attrs ["NX_class" ] = str ( class_type )
243269 return sub
244270
245271 def new_detector (self , name = "detector" , entry = "entry" , subentry = "pyFAI" ):
@@ -253,8 +279,8 @@ def new_detector(self, name="detector", entry="entry", subentry="pyFAI"):
253279 from . import __version__ as version
254280 entry_grp = self .new_entry (entry )
255281 pyFAI_grp = self .new_class (entry_grp , subentry , "NXsubentry" )
256- pyFAI_grp ["definition_local" ] = "pyFAI"
257- pyFAI_grp ["definition_local" ].attrs ["version" ] = version
282+ pyFAI_grp ["definition_local" ] = str ( "pyFAI" )
283+ pyFAI_grp ["definition_local" ].attrs ["version" ] = str ( version )
258284 det_grp = self .new_class (pyFAI_grp , name , "NXdetector" )
259285 return det_grp
260286
@@ -282,6 +308,21 @@ def get_data(self, grp, attr=None, value=None):
282308 self .get_attr (grp [name ], attr ) == value ]
283309 return coll
284310
311+ def get_default_NXdata (self ):
312+ """Return the default plot configured in the nexus structure.
313+
314+ :return: the group with the default plot or None if not found
315+ """
316+ entry_name = self .h5 .attrs .get ("default" )
317+ if entry_name :
318+ entry_grp = self .h5 .get (entry_name )
319+ nxdata_name = entry_grp .attrs .get ("default" )
320+ if nxdata_name :
321+ if nxdata_name .startswith ("/" ):
322+ return self .h5 .get (nxdata_name )
323+ return entry_grp .get (nxdata_name )
324+ return None
325+
285326 def deep_copy (self , name , obj , where = "/" , toplevel = None , excluded = None , overwrite = False ):
286327 """
287328 perform a deep copy:
@@ -300,7 +341,7 @@ def deep_copy(self, name, obj, where="/", toplevel=None, excluded=None, overwrit
300341 if name not in toplevel :
301342 grp = toplevel .require_group (name )
302343 for k , v in obj .attrs .items ():
303- grp .attrs [k ] = v
344+ grp .attrs [k ] = v
304345 elif isinstance (obj , h5py .Dataset ):
305346 if name in toplevel :
306347 if overwrite :
0 commit comments