def test_illumination_range(): beam_size = 100.0 * sc.units.m sample_size = 10.0 * sc.units.m theta = sc.array(values=[15.0, 30.0], unit=sc.units.deg, dims=['']) expected_result = sc.array(values=[10., 10.], unit=sc.units.m, dims=['']) actual_result = corrections.illumination_of_sample(beam_size, sample_size, theta) assert sc.allclose(actual_result, expected_result)
def make_detector_info(ws, spectrum_dim): det_info = ws.detectorInfo() # det -> spec mapping nDet = det_info.size() spectrum = np.empty(shape=(nDet, ), dtype=np.int32) has_spectrum = np.full((nDet, ), False) spec_info = ws.spectrumInfo() for i, spec in enumerate(spec_info): spec_def = spec.spectrumDefinition for j in range(len(spec_def)): det, time = spec_def[j] if time != 0: raise RuntimeError( "Conversion of Mantid Workspace with scanning instrument " "not supported yet.") spectrum[det] = i has_spectrum[det] = True # Store only information about detectors with data (a spectrum). The rest # mostly just gets in the way and including it in the default converter # is probably not required. spectrum = sc.array(dims=['detector'], values=spectrum[has_spectrum]) detector = sc.array(dims=['detector'], values=det_info.detectorIDs()[has_spectrum]) # May want to include more information here, such as detector positions, # but for now this is not necessary. return sc.scalar( sc.Dataset(coords={ 'detector': detector, spectrum_dim: spectrum }))
def _make_simple_dataset(u, v, w): data = sc.array(dims=['u', 'v', 'w'], values=np.ones((u, v, w))) u = sc.array(dims=['u'], values=np.arange(u)) v = sc.array(dims=['v'], values=np.arange(v)) w = sc.array(dims=['w'], values=np.arange(w)) return sc.Dataset(data={'a': data}, coords={ 'w': w, 'v': v, 'u': u, })
def test_loads_data_with_coords(nexus_group: Tuple[Callable, LoadFromNexus]): resource, loader = nexus_group builder = NexusBuilder() da = sc.DataArray( sc.array(dims=['xx', 'yy'], unit='K', values=[[1.1, 2.2], [3.3, 4.4]])) da.coords['xx'] = sc.array(dims=['xx'], unit='m', values=[0.1, 0.2]) builder.add_detector( Detector(detector_numbers=np.array([1, 2, 3, 4]), data=da)) with resource(builder)() as f: detector = nexus.NXroot(f, loader)['entry/detector_0'] loaded = detector[...] assert sc.identical(loaded, da.rename_dims({'yy': 'dim_1'}))
def test_nxobject_monitor(nexus_group: Tuple[Callable, LoadFromNexus]): resource, loader = nexus_group with resource(builder_with_events_monitor_and_log())() as f: monitor = nexus.NXroot(f, loader)['monitor'] assert monitor.nx_class == nexus.NX_class.NXmonitor assert sc.identical( monitor[...], sc.DataArray(sc.array(dims=['time_of_flight'], values=[1.0]), coords={ 'time_of_flight': sc.array(dims=['time_of_flight'], values=[1.0]) }))
def test_nxobject_log(nexus_group: Tuple[Callable, LoadFromNexus]): resource, loader = nexus_group with resource(builder_with_events_monitor_and_log())() as f: log = nexus.NXroot(f, loader)['entry']['log'] assert log.nx_class == nexus.NX_class.NXlog assert sc.identical( log[...], sc.DataArray( sc.array(dims=['time'], values=[1.1, 2.2, 3.3]), coords={ 'time': sc.epoch(unit='ns') + sc.array( dims=['time'], unit='s', values=[4.4, 5.5, 6.6]).to( unit='ns', dtype='int64') }))
def test_make_chopper_bad_close_angles(params): dim = 'frame' with pytest.raises(ValueError) as e_info: _ = ch.make_chopper( frequency=params['frequency'], phase=params['phase'], position=params['position'], cutout_angles_begin=sc.array(dims=[dim], values=[0.0, 1.0, 2.0], unit='rad'), cutout_angles_end=sc.array(dims=[dim], values=[4.0, 3.0, 5.0], unit='rad'), kind=params['kind']) assert str(e_info.value) == "Chopper end cutout angles are not monotonic."
def test_load_component_info_to_2d_geometry(geom_file): geometry = mantid.load_component_info_to_2d(geom_file, sizes={ 'x': 10, 'y': 10 }) assert geometry["position"].sizes == {'x': 10, 'y': 10} assert sc.identical( geometry["x"], sc.DataArray(data=sc.array( dims=["x"], values=np.arange(0.0, 0.1, 0.01), unit=sc.units.m))) assert sc.identical( geometry["y"], sc.DataArray(data=sc.array( dims=["y"], values=np.arange(0.0, 0.1, 0.01), unit=sc.units.m)))
def _create_empty_events_data_array( tof_dtype: Any = np.int64, tof_unit: Union[str, sc.Unit] = "ns", detector_id_dtype: Any = np.int32) -> sc.DataArray: data = sc.DataArray(data=sc.empty(dims=[_event_dimension], shape=[0], unit='counts', with_variances=True, dtype=np.float32), coords={ _time_of_flight: sc.empty(dims=[_event_dimension], shape=[0], dtype=tof_dtype, unit=tof_unit), _detector_dimension: sc.empty(dims=[_event_dimension], shape=[0], dtype=detector_id_dtype), }) indices = sc.array(dims=[_pulse_dimension], values=[], dtype='int64') return sc.DataArray(data=sc.bins(begin=indices, end=indices, dim=_event_dimension, data=data), coords={ 'pulse_time': sc.zeros(dims=[_pulse_dimension], shape=[0], dtype='datetime64', unit='ns') })
def load_dataset( self, group: Dict, dataset_name: str, dimensions: Optional[List[str]] = [], dtype: Optional[Any] = None, index=tuple()) -> sc.Variable: """ Load a dataset into a Scipp Variable (array or scalar) :param group: Group containing dataset to load :param dataset_name: Name of the dataset to load :param dimensions: Dimensions for the output Variable. If empty, yields scalar. :param dtype: Cast to this dtype during load, otherwise retain dataset dtype """ dataset = self.get_dataset_from_group(group, dataset_name) if dataset is None: raise MissingDataset() if dtype is None: dtype = self.get_dtype(dataset) try: units = _get_attribute_value(dataset, _nexus_units) except MissingAttribute: units = sc.units.dimensionless return sc.array(dims=dimensions, values=np.asarray(dataset[_nexus_values])[index], dtype=dtype, unit=units)
def test_illumination_correction_no_spill(): beam_size = 1.0 * sc.units.m sample_size = 10.0 * sc.units.m theta = sc.array(values=[30.0], unit=sc.units.deg, dims=['event']) expected_result = sc.scalar(1.0) actual_result = corrections.illumination_correction(beam_size, sample_size, theta) assert sc.allclose(actual_result, expected_result)
def _bin_events(data: DetectorData): if not bin_by_pixel: # If loading "raw" data, leave binned by pulse. return data.event_data if data.detector_ids is None: # If detector ids were not found in an associated detector group # we will just have to bin according to whatever # ids we have a events for (pixels with no recorded events # will not have a bin) event_id = data.event_data.bins.constituents['data'].coords[ _detector_dimension] data.detector_ids = sc.array(dims=[_detector_dimension], values=np.unique(event_id.values)) # Events in the NeXus file are effectively binned by pulse # (because they are recorded chronologically) # but for reduction it is more useful to bin by detector id # Broadcast pulse times to events data.event_data.bins.coords['pulse_time'] = sc.bins_like( data.event_data, fill_value=data.event_data.coords['pulse_time']) # TODO Look into using `erase=[_pulse_dimension]` instead of binning # underlying buffer. Must prove that performance can be unaffected. da = sc.bin(data.event_data.bins.constituents['data'], groups=[data.detector_ids]) # Add a single time-of-flight bin da = sc.DataArray(data=sc.broadcast(da.data, dims=da.dims + [_time_of_flight], shape=da.shape + [1]), coords={_detector_dimension: data.detector_ids}) if pixel_positions_loaded: # TODO: the name 'position' should probably not be hard-coded but moved # to a variable that cah be changed in a single place. da.coords['position'] = data.pixel_positions return da
def test_basic_stitching(): frames = sc.Dataset() shift = -5.0 frames['time_min'] = sc.array(dims=['frame'], values=[0.0], unit=sc.units.us) frames['time_max'] = sc.array(dims=['frame'], values=[10.0], unit=sc.units.us) frames['time_correction'] = sc.array(dims=['frame'], values=[shift], unit=sc.units.us) frames["wfm_chopper_mid_point"] = sc.vector(value=[0., 0., 2.0], unit='m') data = sc.DataArray(data=sc.ones(dims=['t'], shape=[100], unit=sc.units.counts), coords={ 't': sc.linspace(dim='t', start=0.0, stop=10.0, num=101, unit=sc.units.us), 'source_position': sc.vector(value=[0., 0., 0.], unit='m') }) nbins = 10 stitched = wfm.stitch(data=data, dim='t', frames=frames, bins=nbins) # Note dimension change to TOF as well as shift assert sc.identical( sc.values(stitched), sc.DataArray( data=sc.ones(dims=['tof'], shape=[nbins], unit=sc.units.counts) * nbins, coords={ 'tof': sc.linspace(dim='tof', start=0.0 - shift, stop=10.0 - shift, num=nbins + 1, unit=sc.units.us), 'source_position': sc.vector(value=[0., 0., 2.], unit='m') }))
def make_unphysical_tof(t0, da): # 0 < t < t0, t < 0, t > t0 tof = sc.concat([t0 - t0 / 2, sc.full_like(t0, -2), 2 * t0], 'tof') is_unphysical = sc.array(dims=['energy_transfer'], values=[True, True, False]).broadcast( ['energy_transfer', 'spectrum'], [3, da.sizes['spectrum']]) return tof, is_unphysical
def test_simple_case_any_naming(): ds = _make_simple_dataset(u=2, v=10, w=10) grouped = groupby2D(ds, nx_target=5, ny_target=5, x='w', y='v', z='u') assert grouped['a'].shape == [2, 5, 5] projection = sc.array(dims=['v', 'w'], values=np.ones((5, 5))) * 4 expected_data = sc.concat([projection, projection], dim='u') assert sc.all( sc.isclose(grouped['a'].data, expected_data, atol=1e-14 * sc.units.one)).value
def make_tof_binned_events(): buffer = sc.DataArray(sc.zeros(dims=['event'], shape=[7], dtype=float), coords={ 'tof': sc.array(dims=['event'], values=[ 1000.0, 3000.0, 2000.0, 4000.0, 5000.0, 6000.0, 3000.0 ], unit='us') }) return sc.bins(data=buffer, dim='event', begin=sc.array(dims=['spectrum'], values=[0, 4], dtype='int64'), end=sc.array(dims=['spectrum'], values=[4, 7], dtype='int64'))
def test_groupby2d_simple_case_neutron_specific(): data = sc.array(dims=['wavelength', 'y', 'x'], values=np.arange(100.0).reshape(1, 10, 10)) wav = sc.scalar(value=1.0) x = sc.array(dims=['x'], values=np.arange(10)) y = sc.array(dims=['y'], values=np.arange(10)) source_position = sc.vector(value=[0, 0, -10]) ds = sc.Dataset(data={'a': data}, coords={ 'y': y, 'x': x, 'wavelength': wav, 'source_position': source_position }) grouped = groupby2D(ds, 5, 5) assert grouped['a'].shape == [1, 5, 5] grouped = groupby2D(ds, 1, 1) assert grouped['a'].shape == [1, 1, 1] assert 'source_position' in grouped['a'].meta
def test_stitching_on_beamline(event_mode, dim): wavelengths = sc.array(dims=['event'], values=[1.75, 3.2, 4.5, 6.0, 7.0, 8.25], unit='angstrom') stitched, dlambda_over_lambda = _do_stitching_on_beamline( wavelengths, dim=dim, event_mode=event_mode) for i in range(len(wavelengths)): _check_lambda_inside_resolution(wavelengths['event', i], dlambda_over_lambda, stitched, event_mode=event_mode)
def test_beamline_compute_two_theta(in_ws, in_da): out_mantid = sc.array(dims=['spectrum'], unit='rad', values=[ in_ws.detectorInfo().twoTheta(i) for i in range(in_ws.detectorInfo().size()) ]) in_da = scn.mantid.from_mantid(in_ws) out_scipp = scn.two_theta(in_da) assert sc.allclose(out_scipp, out_mantid, rtol=1e-13 * sc.units.one, atol=1e-13 * out_scipp.unit)
def _check_lambda_inside_resolution(lam, dlam_over_lam, data, event_mode=False, check_value=True): dlam = 0.5 * dlam_over_lam * lam if event_mode: sum_in_range = sc.bin(data, edges=[ sc.array(dims=['wavelength'], values=[(lam - dlam).value, (lam + dlam).value], unit=lam.unit) ]).bins.sum().data['wavelength', 0] else: sum_in_range = sc.sum(data['wavelength', lam - dlam:lam + dlam]).data assert sc.isclose(sum_in_range, 1.0 * sc.units.counts).value is check_value
def test_array_creates_correct_variable(): dims = ['x'] values = [1, 2, 3] variances = [4, 5, 6] unit = sc.units.m dtype = sc.dtype.float64 var = sc.array(dims=dims, values=values, variances=variances, unit=unit, dtype=dtype) expected = sc.Variable(dims=dims, values=values, variances=variances, unit=unit, dtype=dtype) comparison = var == expected assert comparison.values.all()
def test_raises_if_data_and_event_data_found( nexus_group: Tuple[Callable, LoadFromNexus]): resource, loader = nexus_group da = sc.DataArray( sc.array(dims=['xx', 'yy'], values=[[1.1, 2.2], [3.3, 4.4]])) event_data = EventData( event_id=np.array([1, 2, 4, 1, 2, 2]), event_time_offset=np.array([456, 743, 347, 345, 632, 23]), event_time_zero=np.array([1, 2, 3, 4]), event_index=np.array([0, 3, 3, 5]), ) builder = NexusBuilder() builder.add_detector( Detector(detector_numbers=np.array([1, 2, 3, 4]), data=da, event_data=event_data)) with resource(builder)() as f: detector = nexus.NXroot(f, loader)['entry/detector_0'] with pytest.raises(nexus.NexusStructureError): detector[...]
def test_convert_with_factor_type_promotion(): tof = make_test_data(coords=('tof', 'L1', 'L2', 'two_theta')) tof.coords['tof'] = sc.array(dims=['tof'], values=[4000, 5000, 6100, 7300], unit='us', dtype='float32') for target in TOF_TARGET_DIMS: res = scn.convert(tof, origin='tof', target=target, scatter=True) assert res.coords[target].dtype == sc.DType.float32 for key in ('incident_energy', 'final_energy'): inelastic = tof.copy() inelastic.coords[key] = sc.scalar(35, dtype=sc.DType.float32, unit=sc.units.meV) res = scn.convert(inelastic, origin='tof', target='energy_transfer', scatter=True) assert res.coords['energy_transfer'].dtype == sc.DType.float32
def test_stitching_on_beamline_bad_wavelength(event_mode, dim): # Create 6 neutrons. The first wavelength is in this case too short to pass through # the WFM choppers. wavelengths = sc.array(dims=['event'], values=[1.5, 3.2, 4.5, 6.0, 7.0, 8.25], unit='angstrom') stitched, dlambda_over_lambda = _do_stitching_on_beamline( wavelengths, dim=dim, event_mode=event_mode) # The first wavelength should fail the check, since anything not passing through # the choppers won't satisfy the dlambda/lambda condition. _check_lambda_inside_resolution(wavelengths['event', 0], dlambda_over_lambda, stitched, check_value=False, event_mode=event_mode) for i in range(1, len(wavelengths)): _check_lambda_inside_resolution(wavelengths['event', i], dlambda_over_lambda, stitched, event_mode=event_mode)
def load_dataset( self, group: h5py.Group, dataset_name: str, dimensions: Optional[List[str]] = [], dtype: Optional[Any] = None, index=tuple()) -> sc.Variable: """ Load an HDF5 dataset into a Scipp Variable (array or scalar) :param group: Group containing dataset to load :param dataset_name: Name of the dataset to load :param dimensions: Dimensions for the output Variable. Empty for reading scalars :param dtype: Cast to this dtype during load, otherwise retain dataset dtype """ try: dataset = group[dataset_name] except KeyError: raise MissingDataset() if self.is_group(dataset): raise MissingDataset(f"Attempted to load a group " f"({dataset_name}) as a dataset.") if dtype is None: dtype = _ensure_supported_int_type(dataset.dtype.type) if index == tuple(): variable = sc.empty(dims=dimensions, shape=dataset.shape, dtype=dtype, unit=self.get_unit(dataset)) if variable.values.flags[ "C_CONTIGUOUS"] and variable.values.size > 0: dataset.read_direct(variable.values) else: variable.values = dataset return variable return sc.array(dims=dimensions, unit=self.get_unit(dataset), values=dataset[index].astype(dtype))
def test_loading_event_data_creates_automatic_detector_numbers_if_not_present_in_file( nexus_group: Tuple[Callable, LoadFromNexus]): event_time_offsets = np.array([456, 743, 347, 345, 632, 23]) event_data = EventData( event_id=np.array([1, 2, 4, 1, 2, 2]), event_time_offset=event_time_offsets, event_time_zero=np.array([1, 2, 3]), event_index=np.array([0, 3, 5]), ) builder = NexusBuilder() builder.add_detector(Detector(event_data=event_data)) resource, loader = nexus_group with resource(builder)() as f: detector = nexus.NXroot(f, loader)['entry/detector_0'] assert detector.dims == ['detector_number'] with pytest.raises(nexus.NexusStructureError): assert detector.shape == (4, ) loaded = detector[...] assert sc.identical( loaded.bins.size().data, sc.array(dims=['detector_number'], dtype='int64', values=[2, 3, 0, 1]))
def test_loads_event_data_mapped_to_detector_numbers_based_on_their_event_id( nexus_group: Tuple[Callable, LoadFromNexus]): event_time_offsets = np.array([456, 743, 347, 345, 632, 23]) event_data = EventData( event_id=np.array([1, 2, 3, 1, 2, 2]), event_time_offset=event_time_offsets, event_time_zero=np.array([1, 2, 3, 4]), event_index=np.array([0, 3, 3, 5]), ) builder = NexusBuilder() builder.add_detector( Detector(detector_numbers=np.array([1, 2, 3, 4]), event_data=event_data)) resource, loader = nexus_group with resource(builder)() as f: detector = nexus.NXroot(f, loader)['entry/detector_0'] assert detector.dims == ['detector_number'] assert detector.shape == (4, ) loaded = detector[...] assert sc.identical( loaded.bins.size().data, sc.array(dims=['detector_number'], dtype='int64', values=[2, 3, 1, 0]))
def test_time_open_closed(params): dim = 'frame' chopper = ch.make_chopper( frequency=sc.scalar(0.5, unit=sc.units.one / sc.units.s), phase=sc.scalar(0., unit='rad'), position=params['position'], cutout_angles_begin=sc.array(dims=[dim], values=np.pi * np.array([0.0, 0.5, 1.0]), unit='rad'), cutout_angles_end=sc.array(dims=[dim], values=np.pi * np.array([0.5, 1.0, 1.5]), unit='rad'), kind=params['kind']) assert sc.allclose( ch.time_open(chopper), sc.to_unit(sc.array(dims=[dim], values=[0.0, 0.5, 1.0], unit='s'), 'us')) assert sc.allclose( ch.time_closed(chopper), sc.to_unit(sc.array(dims=[dim], values=[0.5, 1.0, 1.5], unit='s'), 'us')) chopper["phase"] = sc.scalar(2.0 * np.pi / 3.0, unit='rad') assert sc.allclose( ch.time_open(chopper), sc.to_unit( sc.array(dims=[dim], values=np.array([0.0, 0.5, 1.0]) + 2.0 / 3.0, unit='s'), 'us')) assert sc.allclose( ch.time_closed(chopper), sc.to_unit( sc.array(dims=[dim], values=np.array([0.5, 1.0, 1.5]) + 2.0 / 3.0, unit='s'), 'us'))
def make_beamline() -> dict: """ ODIN chopper cascade and component positions. Chopper opening angles taken from Schmakat et al. (2020) https://www.sciencedirect.com/science/article/pii/S0168900220308640 Note that the values listed in the paper for the FOC1 opening angles are wrong. The correct values are used here. """ dim = 'frame' beamline = { "source_pulse_length": sc.scalar(2.86e+03, unit='us'), "source_pulse_t_0": sc.scalar(130.0, unit='us'), "source_position": sc.vector(value=[0.0, 0.0, 0.0], unit='m') } beamline["chopper_wfm_1"] = sc.scalar( make_chopper(frequency=sc.scalar(56.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 6.775], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 91.93, 142.23, 189.40, 233.63, 275.10, 313.99 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[5.74, 8.98, 12.01, 14.85, 17.52, 20.02], unit='deg'), kind="wfm")) beamline["chopper_wfm_2"] = sc.scalar( make_chopper(frequency=sc.scalar(56.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 7.225], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 97.67, 151.21, 201.41, 248.48, 292.62, 334.01 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[5.74, 8.98, 12.01, 14.85, 17.52, 20.02], unit='deg'), kind="wfm")) beamline["chopper_foc_1"] = sc.scalar( make_chopper(frequency=sc.scalar(42.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 8.4], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 81.12, 127.82, 171.60, 212.66, 251.16, 288.85 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[11.06, 13.06, 14.94, 16.70, 18.36, 19.91], unit='deg'), kind="frame_overlap")) beamline["chopper_foc_2"] = sc.scalar( make_chopper(frequency=sc.scalar(42.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 12.20], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 106.57, 174.42, 238.04, 297.53, 353.48, 46.65 + 360.0 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[32.90, 33.54, 34.15, 34.71, 35.24, 35.74], unit='deg'), kind="frame_overlap")) beamline["chopper_foc_3"] = sc.scalar( make_chopper(frequency=sc.scalar(28.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 17.0], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 92.47, 155.52, 214.65, 270.09, 322.08, 11.39 + 360.0 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[40.32, 39.61, 38.94, 38.31, 37.72, 37.16], unit='deg'), kind="frame_overlap")) beamline["chopper_foc_4"] = sc.scalar( make_chopper(frequency=sc.scalar(14.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 23.69], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 61.17, 105.11, 146.32, 184.96, 221.19, 255.72 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[32.98, 31.82, 30.74, 29.72, 28.77, 27.87], unit='deg'), kind="frame_overlap")) beamline["chopper_foc_5"] = sc.scalar( make_chopper(frequency=sc.scalar(14.0, unit="Hz"), phase=sc.scalar(0.0, unit='deg'), position=sc.vector(value=[0, 0, 33.0], unit='m'), cutout_angles_center=sc.array(dims=[dim], values=[ 82.20, 143.05, 200.44, 254.19, 304.68, 353.46 ], unit='deg'), cutout_angles_width=sc.array( dims=[dim], values=[50.81, 48.55, 46.42, 44.43, 42.56, 40.80], unit='deg'), kind="frame_overlap")) return beamline
def _load_event_group(group: Group, nexus: LoadFromNexus, quiet: bool, select=tuple()) -> DetectorData: _check_for_missing_fields(group, nexus) index = to_plain_index([_pulse_dimension], select) def shape(name): return nexus.get_shape(nexus.get_dataset_from_group(group, name)) max_index = shape("event_index")[0] single = False if index is Ellipsis or index == tuple(): last_loaded = False else: if isinstance(index, int): single = True start, stop, _ = slice(index, None).indices(max_index) if start == stop: raise IndexError('Index {start} is out of range') index = slice(start, start + 1) start, stop, stride = index.indices(max_index) if stop + stride > max_index: last_loaded = False else: stop += stride last_loaded = True index = slice(start, stop, stride) event_index = nexus.load_dataset_from_group_as_numpy_array( group, "event_index", index) event_time_zero = _load_event_time_zero(group, nexus, index) num_event = shape("event_time_offset")[0] # Some files contain uint64 "max" indices, which turn into negatives during # conversion to int64. This is a hack to get arround this. event_index[event_index < 0] = num_event if len(event_index) > 0: event_select = slice(event_index[0], event_index[-1] if last_loaded else num_event) else: event_select = slice(None) if nexus.dataset_in_group(group, "event_id")[0]: event_id = nexus.load_dataset(group, "event_id", [_event_dimension], index=event_select) else: event_id = None event_time_offset = nexus.load_dataset(group, "event_time_offset", [_event_dimension], index=event_select) # Weights are not stored in NeXus, so use 1s weights = sc.ones(dims=[_event_dimension], shape=event_time_offset.shape, unit='counts', dtype=np.float32, with_variances=True) events = sc.DataArray(data=weights, coords={'event_time_offset': event_time_offset}) if event_id is not None: events.coords['event_id'] = event_id if not last_loaded: event_index = np.append(event_index, num_event) else: # Not a bin-edge coord, all events in bin are associated with same (previous) # pulse time value event_time_zero = event_time_zero[:-1] event_index = sc.array(dims=[_pulse_dimension], values=event_index, dtype=sc.DType.int64) event_index -= event_index.min() # There is some variation in the last recorded event_index in files from different # institutions. We try to make sure here that it is what would be the first index of # the next pulse. In other words, ensure that event_index includes the bin edge for # the last pulse. if single: begins = event_index[_pulse_dimension, 0] ends = event_index[_pulse_dimension, 1] event_time_zero = event_time_zero[_pulse_dimension, 0] else: begins = event_index[_pulse_dimension, :-1] ends = event_index[_pulse_dimension, 1:] try: binned = sc.bins(data=events, dim=_event_dimension, begin=begins, end=ends) except sc.SliceError: raise BadSource( f"Event index in NXEvent at {group.name}/event_index was not" f" ordered. The index must be ordered to load pulse times.") if not quiet: print(f"Loaded {len(event_time_offset)} events from " f"{nexus.get_name(group)} containing {num_event} events") return sc.DataArray(data=binned, coords={'event_time_zero': event_time_zero})