def setup(data, begin, end): background = subtract_background_mean(data, 'tof', begin, end) del background.coords[ 'sample-position'] # ensure unit conversion treats this a monitor background = sc.neutron.convert(background, 'tof', 'wavelength') background = sc.rebin(background, 'wavelength', wavelength_bins) return background
def map_to_bins(data, dim, edges): data = data.copy() to_bin_edges(data, dim) bin_width = data.coords[dim][dim, 1:] - data.coords[dim][dim, :-1] bin_width.unit = sc.units.one data *= bin_width data = sc.rebin(data, dim, edges) bin_width = edges[dim, 1:] - edges[dim, :-1] bin_width.unit = sc.units.one data /= bin_width return data
def test_rebin(): dataset = sc.Dataset() dataset['data'] = sc.Variable(['x'], values=np.array(10 * [1.0]), unit=sc.units.counts) dataset.coords['x'] = sc.Variable(['x'], values=np.arange(11.0)) new_coord = sc.Variable(dims=['x'], values=np.arange(0.0, 11, 2)) dataset = sc.rebin(dataset, 'x', new_coord) np.testing.assert_array_equal(dataset['data'].values, np.array(5 * [2])) np.testing.assert_array_equal(dataset.coords['x'].values, np.arange(0, 11, 2))
def process_event_data(file, lambda_binning): """ Load and reduce event data (used for Vanadium and Empty instrument) file: Nexus file to be loaded and its data reduced in this script lambda_binning: lambda_min, lamba_max, number_of_bins """ # load nexus file event_data = scn.load(file, advanced_geometry=True, load_pulse_times=False, mantid_args={'LoadMonitors': True}) # ################################ # Monitor correction # extract monitor and convert from tof to wavelength mon4_lambda = scn.convert(event_data.attrs['monitor4'].values, 'tof', 'wavelength', scatter=False) mon4_smooth = smooth_data(mon4_lambda, dim='wavelength', NPoints=40) del mon4_lambda # ################################ # vana and EC # convert to lambda event_lambda = scn.convert(event_data, 'tof', 'wavelength', scatter=True) # normalize to monitor lambda_min, lambda_max, number_bins = lambda_binning edges_lambda = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(lambda_min, lambda_max, num=number_bins)) mon_rebin = sc.rebin(mon4_smooth, 'wavelength', edges_lambda) del mon4_smooth event_lambda_norm = event_lambda.bins / sc.lookup(func=mon_rebin, dim='wavelength') del mon_rebin, event_lambda return event_lambda_norm
def _stitch_dense_data( item: sc.DataArray, frames: sc.Dataset, dim: str, new_dim: str, bins: Union[int, sc.Variable]) -> Union[sc.DataArray, dict]: # Make empty data container if isinstance(bins, int): new_coord = sc.linspace( dim=new_dim, start=(frames["time_min"]["frame", 0] - frames["time_correction"]["frame", 0]).value, stop=(frames["time_max"]["frame", -1] - frames["time_correction"]["frame", -1]).value, num=bins + 1, unit=frames["time_min"].unit, ) else: new_coord = bins dims = [] shape = [] for dim_ in item.dims: if dim_ != dim: dims.append(dim_) shape.append(item.sizes[dim_]) else: dims.append(new_dim) shape.append(new_coord.sizes[new_dim] - 1) out = sc.DataArray(data=sc.zeros(dims=dims, shape=shape, with_variances=item.variances is not None, unit=item.unit), coords={new_dim: new_coord}) for group in ["coords", "attrs"]: for key in getattr(item, group): if key != dim: getattr(out, group)[key] = getattr(item, group)[key].copy() for i in range(frames.sizes["frame"]): section = item[dim, frames["time_min"].data[ "frame", i]:frames["time_max"].data["frame", i]].rename_dims({dim: new_dim}) section.coords[new_dim] = section.coords[dim] - frames[ "time_correction"].data["frame", i] if new_dim != dim: del section.coords[dim] out += sc.rebin(section, new_dim, out.coords[new_dim]) return out
def to_wavelength(data, transmission, direct_beam, direct_beam_transmission, masks, wavelength_bins): transmission = transmission_fraction(transmission, direct_beam_transmission, wavelength_bins) data = data.copy() for name, mask in masks.items(): data.masks[name] = mask data = sc.neutron.convert(data, 'tof', 'wavelength', out=data) data = sc.rebin(data, 'wavelength', wavelength_bins) monitor = data.attrs['monitor1'].value monitor = subtract_background_mean(monitor, 'tof', 40000.0 * sc.units.us, 99000.0 * sc.units.us) monitor = sc.neutron.convert(monitor, 'tof', 'wavelength', out=monitor) monitor = sc.rebin(monitor, 'wavelength', wavelength_bins) direct_beam = contrib.map_to_bins(direct_beam, 'wavelength', monitor.coords['wavelength']) direct_beam = monitor * transmission * direct_beam d = sc.Dataset({'data': data, 'norm': solid_angle(data) * direct_beam}) contrib.to_bin_centers(d, 'wavelength') return d
def process_vanadium_data(vanadium, empty_instr, lambda_binning, calibration=None, **absorp): """ Create corrected vanadium dataset Correction applied to Vanadium data only 1. Subtract empty instrument 2. Correct absorption 3. Use calibration for grouping 4. Focus into groups Parameters ---------- vanadium : Vanadium nexus datafile empty_instr: Empty instrument nexus file lambda_binning: format=(lambda_min, lambda_min, number_of_bins) lambda_min and lambda_max are in Angstroms calibration: calibration file Mantid format **absorp: dictionary containing information to correct absorption for sample and vanadium only the inputs related to Vanadium will be selected to calculate the correction see docstrings of powder_reduction for more details see help of Mantid's algorithm CylinderAbsorption for details https://docs.mantidproject.org/nightly/algorithms/CylinderAbsorption-v1.html """ vana_red = process_event_data(vanadium, lambda_binning) ec_red = process_event_data(empty_instr, lambda_binning) # vana - EC vana_red -= ec_red del ec_red # Absorption correction applied if bool(absorp): # The values of number_density, scattering and attenuation are hard-coded since they must # correspond to Vanadium. Only radius and height of the Vanadium cylindrical sample # shape can be set. The names of these inputs if present have to be renamed to match # the requirements of Mantid's algorithm CylinderAbsorption # Create dictionary to calculate absorption correction for Vanadium. absorp_vana = { key.replace('Vanadium', 'Sample'): value for key, value in absorp.items() if 'Vanadium' in key } absorp_vana['SampleNumberDensity'] = 0.07118 absorp_vana['ScatteringXSection'] = 5.16 absorp_vana['AttenuationXSection'] = 4.8756 correction = absorption_correction(vanadium, lambda_binning, **absorp_vana) # the 3 following lines of code are to place info about source and sample # position at the right place in the correction dataArray in order to # proceed to the normalization del correction.coords['source_position'] del correction.coords['sample_position'] del correction.coords['position'] correction = sc.rebin( correction, 'wavelength', sc.Variable(['wavelength'], values=vana_red.coords['wavelength'].values, unit=sc.units.angstrom)) vana_red /= correction del correction # convert to TOF vana_red_tof = sc.neutron.convert(vana_red, 'wavelength', 'tof', realign='linear') del vana_red # convert to d-spacing (no calibration applied) vana_dspacing = sc.neutron.convert(vana_red_tof, 'tof', 'd-spacing', realign='linear') del vana_red_tof # Calibration # Load input_load_cal = {'InstrumentFilename': 'WISH_Definition.xml'} calvana = load_calibration(calibration, mantid_args=input_load_cal) # Merge table with detector->spectrum mapping from vanadium # (implicitly checking that detectors between vanadium and calibration are the same) cal_vana = sc.merge(calvana, vana_dspacing.coords['detector_info'].value) # Compute spectrum mask from detector mask maskvana = sc.groupby(cal_vana['mask'], group='spectrum').any('detector') # Compute spectrum groups from detector groups gvana = sc.groupby(cal_vana['group'], group='spectrum') groupvana = gvana.min('detector') assert groupvana == gvana.max('detector'), \ "Calibration table has mismatching group for detectors in same spectrum" vana_dspacing.coords['group'] = groupvana.data vana_dspacing.masks['mask'] = maskvana.data # Focus focused_vana = sc.groupby(vana_dspacing, group='group').sum('spectrum') return focused_vana
def powder_reduction(sample='sample.nxs', calibration=None, vanadium=None, empty_instr=None, lambda_binning=(0.7, 10.35, 5615), **absorp): """ Simple WISH reduction workflow Note ---- The sample data were not recorded using the same layout of WISH as the Vanadium and empty instrument. That's why: - loading calibration for Vanadium used a different IDF - the Vanadium correction involved cropping the sample data to the first 5 groups (panels) ---- Corrections applied: - Vanadium correction - Absorption correction - Normalization by monitors - Conversion considering calibration - Masking and grouping detectors into panels Parameters ---------- sample: Nexus event file calibration: .cal file following Mantid's standards The columns correspond to detectors' IDs, offset, selection of detectors and groups vanadium: Nexus event file empty_instr: Nexus event file lambda_binning: min, max and number of steps for binning in wavelength min and max are in Angstroms **absorp: dictionary containing information to correct absorption for Sample and Vanadium. There could be only up to two elements related to the correction for Vanadium: the radius and height of the cylindrical sample shape. To distinguish them from the inputs related to the sample, their names in the dictionary are 'CylinderVanadiumRadius' and 'CylinderVanadiumHeight'. The other keys of the 'absorp' dictionary follow Mantid's syntax and are related to the sample data only. see help of Mantid's algorithm CylinderAbsorption for details https://docs.mantidproject.org/nightly/algorithms/CylinderAbsorption-v1.html Returns ------- Scipp dataset containing reduced data in d-spacing Hints ----- To plot the output data, one can histogram in d-spacing and sum according to groups using scipp.histogram and sc.sum, respectively. """ # Load counts sample_data = sc.neutron.load(sample, advanced_geometry=True, load_pulse_times=False, mantid_args={'LoadMonitors': True}) # Load calibration if calibration is not None: input_load_cal = {"InstrumentName": "WISH"} cal = load_calibration(calibration, mantid_args=input_load_cal) # Merge table with detector->spectrum mapping from sample # (implicitly checking that detectors between sample and calibration are the same) cal_sample = sc.merge(cal, sample_data.coords['detector_info'].value) # Compute spectrum mask from detector mask mask = sc.groupby(cal_sample['mask'], group='spectrum').any('detector') # Compute spectrum groups from detector groups g = sc.groupby(cal_sample['group'], group='spectrum') group = g.min('detector') assert group == g.max('detector'), \ "Calibration table has mismatching group for detectors in same spectrum" sample_data.coords['group'] = group.data sample_data.masks['mask'] = mask.data # Correct 4th monitor spectrum # There are 5 monitors for WISH. Only one, the fourth one, is selected for # correction (like in the real WISH workflow). # Select fourth monitor and convert from tof to wavelength mon4_lambda = sc.neutron.convert(sample_data.attrs['monitor4'].values, 'tof', 'wavelength') # Spline background mon4_spline_background = bspline_background(mon4_lambda, sc.Dim('wavelength'), smoothing_factor=70) # Smooth monitor mon4_smooth = smooth_data(mon4_spline_background, dim='wavelength', NPoints=40) # Delete intermediate data del mon4_lambda, mon4_spline_background # Correct data # 1. Normalize to monitor # Convert to wavelength (counts) sample_lambda = sc.neutron.convert(sample_data, 'tof', 'wavelength') # Rebin monitors' data lambda_min, lambda_max, number_bins = lambda_binning edges_lambda = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(lambda_min, lambda_max, num=number_bins)) mon_rebin = sc.rebin(mon4_smooth, 'wavelength', edges_lambda) # Realign sample data sample_lambda.realign({'wavelength': edges_lambda}) sample_lambda /= mon_rebin del mon_rebin, mon4_smooth # 2. absorption correction if bool(absorp): # Copy dictionary of absorption parameters absorp_sample = absorp.copy() # Remove input related to Vanadium if present in absorp dictionary found_vana_info = [ key for key in absorp_sample.keys() if 'Vanadium' in key ] for item in found_vana_info: absorp_sample.pop(item, None) # Calculate absorption correction for sample data correction = absorption_correction(sample, lambda_binning, **absorp_sample) # the 3 following lines of code are to place info about source and sample # position at the right place in the correction dataArray in order to # proceed to the normalization del correction.coords['source_position'] del correction.coords['sample_position'] del correction.coords['position'] correction_rebin = sc.rebin(correction, 'wavelength', edges_lambda) del correction sample_lambda /= correction_rebin del sample_data sample_tof = sc.neutron.convert(sample_lambda, 'wavelength', 'tof', realign='linear') del sample_lambda # 3. Convert to d-spacing taking calibration into account # has to switch to standard conversion in all cases, while support of convert_with_calibration # for realign='linear' is implemented sample_dspacing = sc.neutron.convert(sample_tof, 'tof', 'd-spacing', realign='linear') del cal_sample # if calibration is None: # # No calibration data, use standard convert algorithm # sample_dspacing = sc.neutron.convert(sample_tof, 'tof', 'd-spacing', realign='linear') # # else: # # Calculate dspacing from calibration file # sample_dspacing = sc.neutron.diffraction.convert_with_calibration(sample_tof, cal_sample) # del cal_sample # 4. Focus panels # Assuming sample is in d-spacing: Focus into groups focused = sc.groupby(sample_dspacing, group='group').sum('spectrum') del sample_dspacing # 5. Vanadium correction (requires Vanadium and Empty instrument) if vanadium is not None and empty_instr is not None: print("Proceed with reduction of Vanadium data ") vana_red_focused = process_vanadium_data(vanadium, empty_instr, lambda_binning, calibration, **absorp) # The following selection of groups depends on the loaded data for # Sample, Vanadium and Empty instrument focused = focused['group', 0:5].copy() # histogram vanadium for normalizing + cleaning 'metadata' vana_histo = sc.histogram(vana_red_focused) del vana_red_focused vana_histo.coords['detector_info'] = focused.coords[ 'detector_info'].copy() del vana_histo.coords['source_position'] del vana_histo.coords['sample_position'] # normalize by vanadium focused /= vana_histo del vana_histo return focused
def process_vanadium_data(vanadium, empty_instr, lambda_binning, calibration=None, **absorp): """ Create corrected vanadium dataset Correction applied to Vanadium data only 1. Subtract empty instrument 2. Correct absorption 3. Use calibration for grouping 4. Focus into groups Parameters ---------- vanadium : Vanadium nexus datafile empty_instr: Empty instrument nexus file lambda_binning: format=(lambda_min, lambda_min, number_of_bins) lambda_min and lambda_max are in Angstroms calibration: calibration file Mantid format **absorp: dictionary containing information to correct absorption for sample and vanadium Only the inputs related to Vanadium will be selected to calculate the correction see docstrings of powder_reduction for more details see help of Mantid's algorithm CylinderAbsorption for details https://docs.mantidproject.org/nightly/algorithms/CylinderAbsorption-v1.html """ vana_red = process_event_data(vanadium, lambda_binning) ec_red = process_event_data(empty_instr, lambda_binning) # remove 'spectrum' from wavelength coordinate and match this coordinate # between Vanadium and Empty instrument data min_lambda = vana_red.coords['wavelength'].values[:, 0].min() max_lambda = vana_red.coords['wavelength'].values[:, 1].max() vana_red.coords['wavelength'] = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(min_lambda, max_lambda, num=2)) ec_red.coords['wavelength'] = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(min_lambda, max_lambda, num=2)) # vana - EC ec_red.coords['wavelength'] = vana_red.coords['wavelength'] vana_red.bins.concatenate(-ec_red, out=vana_red) del ec_red # Absorption correction applied if bool(absorp): # The values of number_density, scattering and attenuation are # hard-coded since they must correspond to Vanadium. Only radius and # height of the Vanadium cylindrical sample shape can be set. The # names of these inputs if present have to be renamed to match # the requirements of Mantid's algorithm CylinderAbsorption # Create dictionary to calculate absorption correction for Vanadium. absorp_vana = { key.replace('Vanadium', 'Sample'): value for key, value in absorp.items() if 'Vanadium' in key } absorp_vana['SampleNumberDensity'] = 0.07118 absorp_vana['ScatteringXSection'] = 5.16 absorp_vana['AttenuationXSection'] = 4.8756 correction = absorption_correction(vanadium, lambda_binning, **absorp_vana) # the 3 following lines of code are to place info about source and # sample position at the right place in the correction dataArray in # order to proceed to the normalization del correction.coords['source_position'] del correction.coords['sample_position'] del correction.coords['position'] lambda_min, lambda_max, number_bins = lambda_binning edges_lambda = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(lambda_min, lambda_max, num=number_bins)) correction = sc.rebin(correction, 'wavelength', edges_lambda) vana_red = vana_red.bins / sc.lookup(func=correction, dim='wavelength') del correction # Calibration # Load input_load_cal = {'InstrumentFilename': 'WISH_Definition.xml'} calvana = load_calibration(calibration, mantid_args=input_load_cal) # Merge table with detector->spectrum mapping from vanadium # (implicitly checking that detectors between vanadium and # calibration are the same) cal_vana = sc.merge(calvana, vana_red.coords['detector_info'].value) del calvana # Compute spectrum mask from detector mask maskvana = sc.groupby(cal_vana['mask'], group='spectrum').any('detector') # Compute spectrum groups from detector groups gvana = sc.groupby(cal_vana['group'], group='spectrum') groupvana = gvana.min('detector') assert sc.identical(groupvana, gvana.max('detector')), \ ("Calibration table has mismatching group " "for detectors in same spectrum") vana_red.coords['group'] = groupvana.data vana_red.masks['mask'] = maskvana.data # convert to d-spacing with calibration vana_dspacing = convert_with_calibration(vana_red, cal_vana) del vana_red, cal_vana # Focus focused_vana = \ sc.groupby(vana_dspacing, group='group').bins.concatenate('spectrum') del vana_dspacing return focused_vana
def _do_stitching_on_beamline(wavelengths, dim, event_mode=False): # Make beamline parameters for 6 frames coords = wfm.make_fake_beamline(nframes=6) # They are all created half-way through the pulse. # Compute their arrival time at the detector. alpha = sc.to_unit(constants.m_n / constants.h, 's/m/angstrom') dz = sc.norm(coords['position'] - coords['source_position']) arrival_times = sc.to_unit(alpha * dz * wavelengths, 'us') + coords['source_pulse_t_0'] + ( 0.5 * coords['source_pulse_length']) coords[dim] = arrival_times # Make a data array that contains the beamline and the time coordinate tmin = sc.min(arrival_times) tmax = sc.max(arrival_times) dt = 0.1 * (tmax - tmin) if event_mode: num = 2 else: num = 2001 time_binning = sc.linspace(dim=dim, start=(tmin - dt).value, stop=(tmax + dt).value, num=num, unit=dt.unit) events = sc.DataArray(data=sc.ones(dims=['event'], shape=arrival_times.shape, unit=sc.units.counts, with_variances=True), coords=coords) if event_mode: da = sc.bin(events, edges=[time_binning]) else: da = sc.histogram(events, bins=time_binning) # Find location of frames frames = wfm.get_frames(da) stitched = wfm.stitch(frames=frames, data=da, dim=dim, bins=2001) wav = scn.convert(stitched, origin='tof', target='wavelength', scatter=False) if event_mode: out = wav else: out = sc.rebin(wav, dim='wavelength', bins=sc.linspace(dim='wavelength', start=1.0, stop=10.0, num=1001, unit='angstrom')) choppers = {key: da.meta[key].value for key in ch.find_chopper_keys(da)} # Distance between WFM choppers dz_wfm = sc.norm(choppers["chopper_wfm_2"]["position"].data - choppers["chopper_wfm_1"]["position"].data) # Delta_lambda / lambda dlambda_over_lambda = dz_wfm / sc.norm( coords['position'] - frames['wfm_chopper_mid_point'].data) return out, dlambda_over_lambda