diff --git a/cfel_crystfel.py b/cfel_crystfel.py index 911b63cfe4cdf4f7b9ea7f27030e985e7f2e063f..553984fc5f6b5acb95e4aea0de0e52f525b4dcb1 100644 --- a/cfel_crystfel.py +++ b/cfel_crystfel.py @@ -329,6 +329,23 @@ def _find_min_max_d(detector): def load_crystfel_geometry(filename): + """Loads a CrystFEL geometry file into a dictionary. + + Reimplements the get_detector_geometry_2 function from CrystFEL amost verbatim. Returns a dictionary with the + geometry information. Entries in the geometry file appears as keys in the returned dictionary. For a full + documentation on the CrystFEL geometry format, see: + + tfel/manual-crystfel_geometry.html + + Args: + + filename (str): filename of the geometry file + + Returns: + + detector (dict): dictionary with the geometry loaded from the file + + """ fh = open(filename, 'r') diff --git a/cfel_cxi.py b/cfel_cxi.py index 557ef805e64cafdf12caba152c7b1cc1e0314ce2..e338672910c1cca4256e2b352fdd345fc5a933e8 100644 --- a/cfel_cxi.py +++ b/cfel_cxi.py @@ -13,9 +13,11 @@ # You should have received a copy of the GNU General Public License # along with cfelpyutils. If not, see <http://www.gnu.org/licenses/>. """ -Utilities for interoperability with the CrystFEL software package. +Utilities for writing multi-event files in the CXIDB format. -This module contains reimplementation of Crystfel functions and utilities. +This module contains utilities to write files that adhere to the CXIDB file format: + +http://www.cxidb.org/cxi.html . """ from __future__ import absolute_import @@ -94,7 +96,80 @@ def _validate_data(data): class CXIWriter: + """Writing of multi-event CXIDB files. + + Implements a simple low-level CXIDB file format writer for multi event files. it allows the user to write data + "stacks" in the CXIDB files, making sure that the entries in all stacks are synchronized. + + A CXI Writer instance manages one file. A user can add a stack to a CXI Writer instance with the + add_stack_to_writer function, which also writes the first entry in the stack. The user can then add to the writer + all the stacks that he wants in the file. Once all stacks are added, the user initializes them with the + initialize_stacks function. After initialization, no more stacks can be added. Instead, entries can be appended to + the existing stacks, using the append_data_to_stack function. + + A "slice" (a set of synced entries in all the stacks in the file) can be written to the a file only after an entry + has been appended to all stacks in the file. Conversely, after an entry has been appended to a stack, the user + cannot append another entry before a slice is written. This ensures synchronization of the data in all the stacks. + + A file can be closed at any time. In any case, the writer will not allow a file to contain more than the + number_of_entries specified during instantiation. + + Simple non-stack entries can be written to the file at any time, before or after stack initialization (provided of + course that the file is open). Entries and stacks will general never be overwritten unless the overwrite parameter + is set to True. + + Example of usage of the stack API: + + c1 = 0 + c2 = 0 + + f1 = CXIWriter('/data/test1.h5', ) + f2 = CXIWriter('/data/test2.h5', ) + + f1.add_stack_to_writer('detector1', '/entry_1/detector_1/data', numpy.random.rand(2, 2)) + f2.add_stack_to_writer('detector2', '/entry_1/detector_1/data', numpy.random.rand(3, 2)) + + f1.add_stack_to_writer('counter1', '/entry_1/detector_1/count', c1) + f2.add_stack_to_writer('counter2', '/entry_1/detector_1/count', c2) + + f1.write_simple_entry('/entry_1/detector_1/name', 'FrontCSPAD') + f2.write_simple_entry('/entry_1/detector_1/name', 'BackCSPAD') + + f1.initialize_stacks() + f2.initialize_stacks() + + for i in range(1, 60): + print('Writing slice:', i) + a = numpy.random.rand(2, 2) + b = numpy.random.rand(2, 2) + + c1 += 1 + c2 += 2 + + f1.append_data_to_stack('detector1', a) + f2.append_data_to_stack('detector2', b) + + f1.append_data_to_stack('counter1', c1) + f2.append_data_to_stack('counter2', c2) + + f1.write_stack_slice_and_increment() + f2.write_stack_slice_and_increment() + + f1.close_file() + f2.close_file() + """ + def __init__(self, filename, max_num_slices=5000): + """Instantiates a CXI Writer, managing one file. + + Instantiates a CXI Writer, responsible for writing data into one file. + + Args: + + filename (str): name of the file managed by the CXI Writer + + max_num_slices (int): maximum number of slices for the stacks in the file (default 5000) + """ self._cxi_stacks = {} self._pending_simple_entries = [] @@ -122,6 +197,26 @@ class CXIWriter: self._fh.create_dataset(entry.path, data=entry.data) def add_stack_to_writer(self, name, path, initial_data, overwrite=True): + """Adds a new stack to the file. + + Adds a new stack to the CXI Writer instance. The user must provide a name for the stack, that will identify + the stack in all subsequents operations. The user must also provide the data that will be written as the + initial entry in the stack (initial_data). This initial entry is used to set the size and type of data that the + stack will hold and these parameters are in turn be used to validate all data that is subsequently appended to + the stack. + + Args: + + name (str): stack name. + + path (str): path in the hdf5 file where the stack will be written. + + initial_data (Union: numpy.ndarray, str, int, float): initial entry in the stack. It gets written to the + stack as slice 0. Its characteristics are used to validate all data subsequently appended to the stack. + + overwrite (bool): if set to True, a stack already existing at the same location will be overwritten. If set + to False, an attempt to overwrite a stack will raise an error. + """ _validate_data(initial_data) @@ -137,7 +232,21 @@ class CXIWriter: new_stack = _Stack(path, initial_data) self._cxi_stacks[name] = new_stack - def write_entry(self, path, data, overwrite=False): + def write_simple_entry(self, path, data, overwrite=False): + """Writes a simple, non-stack entry in the file. + + Writes a simple, non-stack entry in the file, at the specified path. A simple entry can be written at all times, + before or after the stack initialization. + + Args: + + path (str): path in the hdf5 file where the entry will be written. + + data (Union: numpy.ndarray, str, int, float): data to write + + overwrite (bool): if set to True, an entry already existing at the same location will be overwritten. If set + to False, an attempt to overwrite an entry will raise an error. + """ _validate_data(data) @@ -150,6 +259,11 @@ class CXIWriter: self._write_simple_entry(new_entry) def initialize_stacks(self): + """Initializes the stacks. + + Initializes the stacks in the CXI Writer instance. This fixes the number and type of stacks in the file. No + stacks can be added to the CXI Writer after initialization. + """ if self._file_is_open is not True: raise RuntimeError('The file is closed. Cannot initialize the file.') @@ -168,7 +282,20 @@ class CXIWriter: self._initialized = True - def append_to_stack(self, name, data): + def append_data_to_stack(self, name, data): + """Appends data to a stack. + + Appends data to a stack, validating the data to make sure that the data type and size match the previous entries + in the stack. Only one entry can be appended to each stack before writing a slice across all stacks with + the write_slice_and_increment. + + Args: + + name (str): stack name, defining the stack to which the data will be appended. + + data (Union: numpy.ndarray, str, int, float): data to write. The data will be validated against the type + and size of previous entries in the stack. + """ _validate_data(data) @@ -184,6 +311,13 @@ class CXIWriter: raise RuntimeError('Error appending to stack {}: {}'.format(name, e)) def write_stack_slice_and_increment(self): + """Writes a slice across all stacks and resets the writer for the next slice. + + Writes a slice across all stacks in the file. It checks that an entry has been appended to each stack, and + writes all the entries on top of the relevant stacks in one go. If an entry is missing in a stack, the function + will raise an error. After writing the slice, the function resets the writer to allow again appending data to + the stacks. + """ if self._file_is_open is not True: raise RuntimeError('The file is closed. The slice cannot be written.') @@ -196,7 +330,7 @@ class CXIWriter: for entry in self._cxi_stacks.values(): if entry._data_to_write is None: - raise RuntimeError('The slice is incomplete and will not be written. The following stac is not ' + raise RuntimeError('The slice is incomplete and will not be written. The following stack is not ' 'present in the current slice:', entry.path) for entry in self._cxi_stacks.values(): @@ -205,6 +339,15 @@ class CXIWriter: self._curr_slice += 1 def get_file_handle(self): + """Access to the naked h5py file handle. + + This function allows access to the a naked h5py handle for the file managed by the CXI Writer. This allowa + operations on the file that are not covered by CXI Writer API. Use it at your own risk. + + Returns: + + fh (h5py.File): an h5py file handle to the file managed by the writer. + """ if self._file_is_open is not True: raise RuntimeError('The file is closed. Cannot get the file handle.') @@ -212,6 +355,10 @@ class CXIWriter: return self._fh def close_file(self): + """Closes the file. + + Closes the file for writing, ending all writing operations. + """ if self._file_is_open is not True: raise RuntimeError('The file is already closed. Cannot close the file.') @@ -221,45 +368,4 @@ class CXIWriter: self._fh.close() - self._file_is_open = False - - -if __name__ == "__main__": - - c1 = 0 - c2 = 0 - - f1 = CXIWriter('/data/test1.h5', ) - f2 = CXIWriter('/data/test2.h5', ) - - f1.add_stack_to_writer('detector1', '/entry_1/detector_1/data', numpy.random.rand(2, 2)) - f2.add_stack_to_writer('detector2', '/entry_1/detector_1/data', numpy.random.rand(3, 2)) - - f1.add_stack_to_writer('counter1', '/entry_1/detector_1/count', c1) - f2.add_stack_to_writer('counter2', '/entry_1/detector_1/count', c2) - - f1.write_entry('/entry_1/detector_1/name', 'FrontCSPAD') - f2.write_entry('/entry_1/detector_1/name', 'BackCSPAD') - - f1.initialize_stacks() - f2.initialize_stacks() - - for i in range(1, 60): - print('Writing slice:', i) - a = numpy.random.rand(2, 2) - b = numpy.random.rand(2, 2) - - c1 += 1 - c2 += 2 - - f1.append_to_stack('detector1', a) - f2.append_to_stack('detector2', b) - - f1.append_to_stack('counter1', c1) - f2.append_to_stack('counter2', c2) - - f1.write_stack_slice_and_increment() - f2.write_stack_slice_and_increment() - - f1.close_file() - f2.close_file() + self._file_is_open = False \ No newline at end of file