From 5cf1b921e55ffd30973923a1e1ce7587207a7e25 Mon Sep 17 00:00:00 2001 From: Ben Williams Date: Thu, 10 Feb 2022 13:35:16 +0000 Subject: [PATCH] Add format class for I19-2 Eiger with DAC shadow (#481) Also, Remove redundant file mode "r". This has been the default since h5py 3.0 and DIALS/DXTBX ship with >=3.1. --- newsfragments/481.bugfix | 1 + src/dxtbx/format/FormatNXmx.py | 54 +++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 newsfragments/481.bugfix diff --git a/newsfragments/481.bugfix b/newsfragments/481.bugfix new file mode 100644 index 000000000..f8624a9b2 --- /dev/null +++ b/newsfragments/481.bugfix @@ -0,0 +1 @@ +``FormatNXmxI19_2``: Allow data from beamline I19-2 at Diamond Light Source to be processed with optional masking of the beamline's standard diamond anvil pressure cell with a 76° aperture. diff --git a/src/dxtbx/format/FormatNXmx.py b/src/dxtbx/format/FormatNXmx.py index 690cc19a9..34e02b129 100644 --- a/src/dxtbx/format/FormatNXmx.py +++ b/src/dxtbx/format/FormatNXmx.py @@ -1,20 +1,26 @@ from __future__ import annotations +import math + import h5py +from libtbx import Auto + import dxtbx.nexus from dxtbx.format.FormatNexus import FormatNexus +from dxtbx.masking import GoniometerMaskerFactory class FormatNXmx(FormatNexus): + """Read NXmx-flavour NeXus-format HDF5 data.""" _cached_file_handle = None @staticmethod def understand(image_file): - with h5py.File(image_file, "r", swmr=True) as handle: + """This format class currently only applies to beamline I19-2 at DLS.""" + with h5py.File(image_file, swmr=True) as handle: name = dxtbx.nexus.nxmx.h5str(FormatNXmx.get_instrument_name(handle)) - # Temporarily restrict this to I19-2 while developing if name and "I19-2" in name: return True return False @@ -26,7 +32,7 @@ def __init__(self, image_file, **kwargs): def _start(self): self._static_mask = None - with h5py.File(self._image_file, "r", swmr=True) as fh: + with h5py.File(self._image_file, swmr=True) as fh: nxmx = dxtbx.nexus.nxmx.NXmx(fh) nxsample = nxmx.entries[0].samples[0] nxinstrument = nxmx.entries[0].instruments[0] @@ -61,7 +67,7 @@ def get_static_mask(self, index=None, goniometer=None): def get_raw_data(self, index): if self._cached_file_handle is None: - self._cached_file_handle = h5py.File(self._image_file, "r", swmr=True) + self._cached_file_handle = h5py.File(self._image_file, swmr=True) nxmx = dxtbx.nexus.nxmx.NXmx(self._cached_file_handle) nxdata = nxmx.entries[0].data[0] @@ -79,3 +85,43 @@ def get_raw_data(self, index): d1d.set_selected(d1d == top - 1, -1) d1d.set_selected(d1d == top - 2, -2) return raw_data + + +class FormatNXmxI19_2(FormatNXmx): + """ + Read NXmx-flavour data from beamline I19-2 at Diamond Light Source. + + Include the option of dynamic shadowing of the standard I19-2 diamond anvil + pressure cell with a 76° conical aperture. + """ + + @staticmethod + def understand(image_file): + """This format class applies if the instrument name contains 'I19-2'.""" + with h5py.File(image_file, swmr=True) as handle: + name = dxtbx.nexus.nxmx.h5str(FormatNXmx.get_instrument_name(handle)) + if name and "I19-2" in name: + return True + return False + + @staticmethod + def has_dynamic_shadowing(**kwargs): + """Check if dynamic shadowing should be applied for a diamond anvil cell.""" + dynamic_shadowing = kwargs.get("dynamic_shadowing", False) + if dynamic_shadowing in (Auto, "Auto"): + return False + return dynamic_shadowing + + def __init__(self, image_file, **kwargs): + """Initialise the image structure from the given file.""" + self._dynamic_shadowing = self.has_dynamic_shadowing(**kwargs) + super().__init__(image_file, **kwargs) + + def get_goniometer_shadow_masker(self, goniometer=None): + """Apply the dynamic mask for a diamond anvil cell with a 76° aperture.""" + if goniometer is None: + goniometer = self.get_goniometer() + + return GoniometerMaskerFactory.diamond_anvil_cell( + goniometer, cone_opening_angle=math.radians(76) + )