// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include "HDF5NXmx.h" #include "../common/GitInfo.h" #include "../include/spdlog/fmt/fmt.h" #include "MakeDirectory.h" #include "../common/time_utc.h" #include "gemmi/symmetry.hpp" NXmx::NXmx(const StartMessage &start) : start_message(start), filename(start.file_prefix + "_master.h5") { uint64_t tmp_suffix; try { if (!start.arm_date.empty()) tmp_suffix = parse_UTC_to_ms(start.arm_date); } catch (...) { tmp_suffix = std::chrono::system_clock::now().time_since_epoch().count(); } tmp_filename = fmt::format("{}.{:08x}.tmp", filename, tmp_suffix); if (start.overwrite.has_value()) overwrite = start.overwrite.value(); MakeDirectory(filename); bool v1_10 = (start.file_format == FileWriterFormat::NXmxVDS); hdf5_file = std::make_unique(tmp_filename, v1_10); hdf5_file->Attr("file_name", filename); hdf5_file->Attr("HDF5_Version", hdf5_version()); HDF5Group(*hdf5_file, "/entry").NXClass("NXentry").SaveScalar("definition", "NXmx"); hdf5_file->SaveScalar("/entry/start_time", start.arm_date); Facility(start); Detector(start); Metrology(start); Beam(start); Attenuator(start); UserData(start); MX(start); Fluorescence(start); } NXmx::~NXmx() { if (!std::filesystem::exists(filename.c_str()) || overwrite) std::rename(tmp_filename.c_str(), filename.c_str()); } std::string HDF5Metadata::DataFileName(const StartMessage &msg, int64_t file_number) { if (file_number < 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "File number cannot be negative"); if (msg.source_name == "SwissFEL") { if (file_number >= 10000) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Format doesn't allow for 10'000 or more files"); else if (msg.detector_serial_number.empty()) return fmt::format("{:s}{:04d}.JF.h5", msg.file_prefix, file_number + 1); else return fmt::format("{:s}{:04d}.{:s}.h5", msg.file_prefix, file_number + 1, msg.detector_serial_number); } else { if (file_number >= 1000000) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Format doesn't allow for 1 million or more files"); else return fmt::format("{:s}_data_{:06d}.h5", msg.file_prefix, file_number + 1); } } void NXmx::LinkToData(const StartMessage &start, const EndMessage &end) { hsize_t total_images = end.max_image_number; hsize_t images_per_file = start.images_per_file; hsize_t file_count = 0; if (start.images_per_file > 0) { file_count = total_images / images_per_file; if (total_images % images_per_file > 0) file_count++; } HDF5Group(*hdf5_file, "/entry/data").NXClass("NXdata"); for (uint32_t file_id = 0; file_id < file_count; file_id++) { char buff[32]; snprintf(buff,32,"/entry/data/data_%06d", file_id+1); hdf5_file->ExternalLink(HDF5Metadata::DataFileName(start, file_id), "/entry/data/data", std::string(buff)); } } void NXmx::LinkToData_VDS(const StartMessage &start, const EndMessage &end) { hsize_t total_images = end.max_image_number; hsize_t width = start.image_size_x; hsize_t height = start.image_size_y; if (total_images > 0) { HDF5Group(*hdf5_file, "/entry/data").NXClass("NXdata"); auto data_dataset = VDS(start, "/entry/data/data", {total_images, height, width}, HDF5DataType(start.bit_depth_image / 8, start.pixel_signed)); data_dataset->Attr("image_nr_low", (int32_t) 1) .Attr("image_nr_high",(int32_t) total_images); VDS(start, "/entry/detector/data_collection_efficiency_image", "/entry/instrument/detector/detectorSpecific/data_collection_efficiency_image", {total_images}, HDF5DataType(0.0f)); VDS(start, "/entry/detector/pixel_sum", "/entry/instrument/detector/detectorSpecific/pixel_sum", {total_images}, HDF5DataType((int64_t) 0)); HDF5Group(*hdf5_file, "/entry/image").NXClass("NXCollection"); VDS(start, "/entry/image/max_value", {total_images}, HDF5DataType((int64_t)0)); VDS(start, "/entry/image/min_value", {total_images}, HDF5DataType((int64_t)0)); VDS(start, "/entry/image/error_pixels", {total_images}, HDF5DataType((int64_t)0)); VDS(start, "/entry/image/saturated_pixels", {total_images}, HDF5DataType((int64_t)0)); if (start.max_spot_count > 0) { VDS(start, "/entry/MX/peakXPosRaw",{total_images, start.max_spot_count}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/peakYPosRaw",{total_images, start.max_spot_count}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/peakTotalIntensity",{total_images, start.max_spot_count}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/peakIceRingRes", {total_images, start.max_spot_count}, HDF5DataType((uint8_t) 0)); VDS(start, "/entry/MX/nPeaks",{total_images}, HDF5DataType((uint32_t) 0)); VDS(start, "/entry/MX/strongPixels", {total_images}, HDF5DataType((uint32_t) 0)); VDS(start, "/entry/MX/bkgEstimate", {total_images}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/resolutionEstimate",{total_images}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/peakCountUnfiltered",{total_images}, HDF5DataType((uint32_t) 0)); VDS(start, "/entry/MX/peakCountIceRingRes",{total_images}, HDF5DataType((uint32_t) 0)); VDS(start, "/entry/MX/peakCountLowRes",{total_images}, HDF5DataType((uint32_t) 0)); VDS(start, "/entry/MX/peakCountIndexed",{total_images}, HDF5DataType((uint32_t) 0)); } if (start.indexing_algorithm != IndexingAlgorithmEnum::None) { VDS(start, "/entry/MX/peakIndexed", {total_images, start.max_spot_count}, HDF5DataType((uint8_t) 0)); VDS(start, "/entry/MX/peakH", {total_images, start.max_spot_count}, HDF5DataType((int32_t) 0)); VDS(start, "/entry/MX/peakK", {total_images, start.max_spot_count}, HDF5DataType((int32_t) 0)); VDS(start, "/entry/MX/peakL", {total_images, start.max_spot_count}, HDF5DataType((int32_t) 0)); VDS(start, "/entry/MX/peakDistEwaldSphere", {total_images, start.max_spot_count}, HDF5DataType((float) 0)); VDS(start, "/entry/MX/imageIndexed", {total_images}, HDF5DataType((uint8_t) 0)); VDS(start, "/entry/MX/latticeIndexed", {total_images,9}, HDF5DataType((float) 0)); VDS(start, "/entry/MX/profileRadius", {total_images}, HDF5DataType(0.0f)); VDS(start, "/entry/MX/bFactor", {total_images}, HDF5DataType(0.0f)); } if (start.geom_refinement_algorithm != GeomRefinementAlgorithmEnum::None) { VDS(start, "/entry/detector/beam_center_x", "/entry/instrument/detector/refined_beam_center_x", {total_images}, HDF5DataType(0.0f)); VDS(start, "/entry/detector/beam_center_y", "/entry/instrument/detector/refined_beam_center_y", {total_images}, HDF5DataType(0.0f)); } if (!start.az_int_bin_to_q.empty()) { size_t azimuthal_bins = start.az_int_phi_bin_count.value_or(1); size_t q_bins = start.az_int_q_bin_count.value_or(1); if (q_bins > 0 & azimuthal_bins > 0) { VDS(start, "/entry/azint/image", {total_images, azimuthal_bins, q_bins}, HDF5DataType(0.0f)); } } if (start.xfel_pulse_id.value_or(false)) { HDF5Group(*hdf5_file, "/entry/xfel").NXClass("NXcollection"); VDS(start, "/entry/xfel/pulseID", {total_images}, HDF5DataType((uint64_t) 0)); VDS(start, "/entry/xfel/eventCode", {total_images}, HDF5DataType((uint32_t) 0)); } if (start.storage_cell_number) VDS(start, "/entry/detector/storage_cell_image", "/entry/instrument/detector/detectorSpecific/storage_cell_image", {total_images}, HDF5DataType((uint8_t) 0)); if (!start.rois.empty()) { HDF5Group(*hdf5_file, "/entry/roi").NXClass("NXcollection"); for (const auto &r: start.rois) { std::string roi = r.name; HDF5Group(*hdf5_file, "/entry/roi/" + roi); VDS(start, "/entry/roi/" + roi + "/max", {total_images}, HDF5DataType((int64_t) 0)); VDS(start, "/entry/roi/" + roi + "/sum", {total_images}, HDF5DataType((int64_t) 0)); VDS(start, "/entry/roi/" + roi + "/sum_sq", {total_images}, HDF5DataType((int64_t) 0)); VDS(start, "/entry/roi/" + roi + "/npixel", {total_images}, HDF5DataType((int64_t) 0)); VDS(start, "/entry/roi/" + roi + "/x", {total_images}, HDF5DataType((float) 0)); VDS(start, "/entry/roi/" + roi + "/y", {total_images}, HDF5DataType((float) 0)); } } } } std::unique_ptr NXmx::VDS(const StartMessage &start, const std::string &name, const std::vector &dim, const HDF5DataType &data_type) { return VDS(start, name, name, dim, data_type); } std::unique_ptr NXmx::VDS(const StartMessage &start, const std::string &name_src, const std::string &name_dest, const std::vector &dim, const HDF5DataType &data_type) { if (dim.empty() || dim.size() > 3) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Dimension must be in range 1-3"); hsize_t images_per_file = start.images_per_file; hsize_t file_count = 0; if (start.images_per_file > 0) { file_count = dim[0] / images_per_file; if (dim[0] % images_per_file > 0) file_count++; } HDF5DataSpace full_data_space(dim); HDF5Dcpl dcpl; if (dim.size() == 3) dcpl.SetChunking({1, dim[1], dim[2]}); for (hsize_t file_id = 0; file_id < file_count; file_id++) { hsize_t images_in_file = images_per_file; if (file_id == file_count - 1) images_in_file = dim[0] - (file_count - 1) * images_per_file; HDF5DataSpace virtual_data_space(dim); auto dim_src = dim; dim_src[0] = images_in_file; HDF5DataSpace src_data_space(dim_src); std::vector start_dim(dim.size()); start_dim[0] = file_id * images_per_file; virtual_data_space.SelectHyperslab(start_dim, dim_src); dcpl.SetVirtual(HDF5Metadata::DataFileName(start, file_id), name_src,src_data_space, virtual_data_space); } return std::make_unique(*hdf5_file, name_dest, data_type, full_data_space, dcpl); } void NXmx::Detector(const StartMessage &start) { HDF5Group group(*hdf5_file, "/entry/instrument/detector"); group.NXClass("NXdetector"); SaveScalar(group, "depends_on", "/entry/instrument/detector/transformations/rot3"); SaveScalar(group, "beam_center_x", start.beam_center_x)->Units("pixel"); SaveScalar(group, "beam_center_y", start.beam_center_y)->Units("pixel"); SaveScalar(group, "distance", start.detector_distance)->Units("m"); SaveScalar(group, "detector_distance", start.detector_distance)->Units("m"); SaveScalar(group, "count_time", start.count_time)->Units("s"); SaveScalar(group, "frame_time", start.frame_time)->Units("s"); SaveScalar(group, "sensor_thickness", start.sensor_thickness)->Units("m"); if (start.threshold_energy.size() == 1) SaveScalar(group, "threshold_energy", start.threshold_energy.begin()->second)->Units("eV"); SaveScalar(group, "x_pixel_size", start.pixel_size_x)->Units("m"); SaveScalar(group, "y_pixel_size", start.pixel_size_y)->Units("m"); SaveScalar(group, "sensor_material", start.sensor_material); SaveScalar(group, "description", start.detector_description); if (!start.detector_serial_number.empty()) { SaveScalar(group, "detector_number", start.detector_serial_number); SaveScalar(group, "serial_number", start.detector_serial_number); } SaveScalar(group, "bit_depth_image", start.bit_depth_image); if (start.bit_depth_readout) SaveScalar(group, "bit_depth_readout", start.bit_depth_readout.value()); SaveScalar(group, "saturation_value", start.saturation_value); if (start.error_value) SaveScalar(group, "error_value", start.error_value.value()); // this is not NXmx SaveScalar(group, "flatfield_applied", start.flatfield_enabled); SaveScalar(group, "pixel_mask_applied", start.pixel_mask_enabled); if (start.jungfrau_conversion_enabled) SaveScalar(group, "jungfrau_conversion_applied", start.jungfrau_conversion_enabled.value()); if (start.jungfrau_conversion_factor) SaveScalar(group, "jungfrau_conversion_factor", start.jungfrau_conversion_factor.value())->Units("eV"); SaveScalar(group, "geometry_transformation_applied", start.geometry_transformation_enabled.value_or(true)); SaveScalar(group, "acquisition_type", "triggered"); SaveScalar(group, "countrate_correction_applied", start.countrate_correction_enabled); SaveScalar(group, "number_of_cycles", start.summation); HDF5Group det_specific(group, "detectorSpecific"); det_specific.NXClass("NXcollection"); if (!start.jfjoch_release.empty()) SaveScalar(det_specific, "jfjoch_release", start.jfjoch_release); SaveScalar(det_specific, "jfjoch_writer_release", jfjoch_version()); if (start.summation_mode.has_value()) SaveScalar(det_specific, "summation_mode", start.summation_mode.value()); if (start.detect_ice_rings.has_value()) SaveScalar(det_specific, "detect_ice_rings", start.detect_ice_rings.value()); SaveScalar(det_specific, "x_pixels_in_detector", static_cast(start.image_size_x)); SaveScalar(det_specific, "y_pixels_in_detector", static_cast(start.image_size_y)); SaveScalar(det_specific, "software_git_commit", jfjoch_git_sha1()); SaveScalar(det_specific, "software_git_date", jfjoch_git_date()); if (start.storage_cell_number) { SaveScalar(det_specific, "storage_cell_number", static_cast(start.storage_cell_number.value())); if (start.storage_cell_number.value() > 1) SaveScalar(det_specific, "storage_cell_delay", static_cast(start.storage_cell_delay_ns))->Units( "ns"); } if (start.data_reduction_factor_serialmx) det_specific.SaveScalar("data_reduction_factor_serialmx", start.data_reduction_factor_serialmx.value()); if (!start.gain_file_names.empty()) det_specific.SaveVector("gain_file_names", start.gain_file_names); if (start.pixel_mask.size() == 1) { // Currently only handling single pixel mask CompressionAlgorithm mask_alg = CompressionAlgorithm::BSHUF_LZ4; if (start.file_format == FileWriterFormat::NXmxLegacy) mask_alg = CompressionAlgorithm::NO_COMPRESSION; std::vector dims = {start.image_size_y, start.image_size_x}; group.SaveVector("pixel_mask", start.pixel_mask.begin()->second, dims, mask_alg); hdf5_file->HardLink("/entry/instrument/detector/pixel_mask", "/entry/instrument/detector/detectorSpecific/pixel_mask"); } } void NXmx::Detector(const StartMessage &start, const EndMessage &end) { if (start.images_per_trigger.has_value() && start.images_per_trigger.value() > 0) { SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/nimages", start.images_per_trigger.value()); SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/ntrigger", (end.max_image_number + start.images_per_trigger.value() - 1)/ start.images_per_trigger.value()); } else { SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/nimages", end.max_image_number); SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/ntrigger", 1); } if (end.images_collected_count) SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/nimages_collected", end.images_collected_count.value()); if (end.images_sent_to_write_count) SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/nimages_written", end.images_sent_to_write_count.value()); if (end.efficiency) SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/data_collection_efficiency", end.efficiency.value()); if (end.max_receiver_delay) SaveScalar(*hdf5_file, "/entry/instrument/detector/detectorSpecific/max_receiver_delay", end.max_receiver_delay.value()); } void NXmx::MX(const StartMessage &start) { HDF5Group(*hdf5_file, "/entry/MX").NXClass("NXcollection"); switch (start.indexing_algorithm) { case IndexingAlgorithmEnum::FFBIDX: hdf5_file->SaveScalar("/entry/MX/indexing_algorithm", "FFBIDX"); break; case IndexingAlgorithmEnum::FFTW: hdf5_file->SaveScalar("/entry/MX/indexing_algorithm", "FFT (FFTW)"); break; case IndexingAlgorithmEnum::FFT: hdf5_file->SaveScalar("/entry/MX/indexing_algorithm", "FFT (CUDA)"); break; default: break; } switch (start.geom_refinement_algorithm) { case GeomRefinementAlgorithmEnum::BeamCenter: hdf5_file->SaveScalar("/entry/MX/geom_refinement_algorithm", "beam_center"); break; default: break; } } void NXmx::DetectorModule(const std::string &name, const std::vector &origin, const std::vector &size, const std::vector &fast_axis, const std::vector &slow_axis, const std::string &nx_axis, double pixel_size_mm) { HDF5Group module_group(*hdf5_file, "/entry/instrument/detector/" + name); module_group.NXClass("NXdetector_module"); module_group.SaveVector("data_origin", origin); module_group.SaveVector("data_size", size); SaveScalar(module_group, "fast_pixel_direction", pixel_size_mm)-> Transformation("m", "/entry/instrument/detector/transformations/" + nx_axis, "", "", "translation", fast_axis, {0,0,0}, ""); SaveScalar(module_group, "slow_pixel_direction", pixel_size_mm)-> Transformation("m", "/entry/instrument/detector/transformations/" + nx_axis, "", "", "translation", slow_axis, {0,0,0}, ""); SaveScalar(module_group, "module_offset", 0)-> Transformation("m", "/entry/instrument/detector/transformations/" + nx_axis, "", "", "translation", {0,0,0}); } void NXmx::Facility(const StartMessage &start) { HDF5Group(*hdf5_file, "/entry/source").NXClass("NXsource"); SaveScalar(*hdf5_file, "/entry/source/name", start.source_name); if (!start.source_type.empty()) SaveScalar(*hdf5_file, "/entry/source/type", start.source_type); if (start.ring_current_mA) { SaveScalar(*hdf5_file, "/entry/source/current", start.ring_current_mA.value() / 1000.0)->Units("A"); } HDF5Group(*hdf5_file, "/entry/instrument").NXClass("NXinstrument"); SaveScalar(*hdf5_file, "/entry/instrument/name", start.instrument_name); } void NXmx::Beam(const StartMessage &start) { HDF5Group group(*hdf5_file, "/entry/instrument/beam"); group.NXClass("NXbeam"); SaveScalar(group, "incident_wavelength", start.incident_wavelength)->Units("angstrom"); if (start.total_flux) SaveScalar(group, "total_flux", start.total_flux.value())->Units("Hz"); } void NXmx::Fluorescence(const StartMessage &start) { if (start.fluorescence_spectrum.empty()) return; HDF5Group group(*hdf5_file, "/entry/instrument/fluorescence"); group.NXClass("NXcollection"); group.SaveVector("energy", start.fluorescence_spectrum.GetEnergy_eV())->Units("eV"); group.SaveVector("data", start.fluorescence_spectrum.GetData()); } void NXmx::Metrology(const StartMessage &start) { HDF5Group transformations(*hdf5_file, "/entry/instrument/detector/transformations"); transformations.NXClass("NXtransformations"); std::vector vector{start.beam_center_x * start.pixel_size_x, start.beam_center_y * start.pixel_size_y, start.detector_distance}; double vector_length = sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); std::vector vector_norm{vector[0] / vector_length, vector[1]/vector_length, vector[2]/vector_length}; SaveScalar(transformations, "translation", vector_length)-> Transformation("m", ".", "detector", "detector_arm", "translation", vector_norm); // https://manual.nexusformat.org/classes/base_classes/NXdetector_module.html?highlight=nxdetector_module // The order of indices (i, j or i, j, k) is slow to fast. // though EIGER has is the other way round // Confusing.... std::vector origin = {0, 0}; std::vector size = {static_cast(start.image_size_y), static_cast(start.image_size_x)}; const double rot1 = start.poni_rot1.value_or(0.0); const double rot2 = start.poni_rot2.value_or(0.0); const double rot3 = start.poni_rot3.value_or(0.0); SaveScalar(transformations, "rot1", rot1)-> Transformation("rad", "/entry/instrument/detector/transformations/translation", "detector", "detector_arm", "rotation", std::vector{1.0, 0.0, 0.0}); SaveScalar(transformations, "rot2", rot2)-> Transformation("rad", "/entry/instrument/detector/transformations/rot1", "detector", "detector_arm", "rotation", std::vector{0.0, -1.0, 0.0}); SaveScalar(transformations, "rot3", rot3)-> Transformation("rad", "/entry/instrument/detector/transformations/rot2", "detector", "detector_arm", "rotation", std::vector{0.0, 0.0, -1.0}); DetectorModule("module", origin, size, {-1,0,0}, {0,-1,0}, "rot3", start.pixel_size_x); } void NXmx::Sample(const StartMessage &start, const EndMessage &end) { HDF5Group group(*hdf5_file, "/entry/sample"); group.NXClass("NXsample"); if (!start.sample_name.empty()) group.SaveScalar("name", start.sample_name); if (start.space_group_number) { group.SaveScalar("space_group_number", start.space_group_number.value()); auto *sg = gemmi::find_spacegroup_by_number(start.space_group_number.value()); if (sg != nullptr) group.SaveScalar("space_group", sg->short_name()); } if (start.unit_cell) { std::vector v = {start.unit_cell->a, start.unit_cell->b, start.unit_cell->c, start.unit_cell->alpha, start.unit_cell->beta, start.unit_cell->gamma}; group.SaveVector("unit_cell", v); } if (start.sample_temperature_K) group.SaveScalar("temperature", start.sample_temperature_K.value())->Units("K"); std::string depends_on = "."; if ((end.max_image_number > 0) && start.goniometer) { HDF5Group transformations(group, "transformations"); transformations.NXClass("NXtransformations"); hdf5_file->HardLink("/entry/sample/transformations","/entry/sample/goniometer"); SaveVector(transformations, start.goniometer->GetName(), start.goniometer->GetAngleContainer(end.max_image_number))-> Transformation("deg", depends_on, "", "", "rotation", start.goniometer->GetAxisVector(), {0,0,0}, ""); SaveVector(transformations, start.goniometer->GetName() + "_end", start.goniometer->GetAngleContainerEnd(end.max_image_number)) ->Units("deg"); SaveScalar(transformations, start.goniometer->GetName() + "_range_average", start.goniometer->GetIncrement_deg()) ->Units("deg"); SaveScalar(transformations, start.goniometer->GetName() + "_range_total", start.goniometer->GetIncrement_deg() * end.max_image_number) ->Units("deg"); depends_on = "/entry/sample/transformations/" + start.goniometer->GetName(); auto helical = start.goniometer->GetHelicalStep(); if (helical.has_value()) { SaveVector(transformations, start.goniometer->GetName() + "_helical_x", start.goniometer->GetXContainer_m(end.max_image_number))-> Transformation("m", depends_on, "", "", "translation", {1, 0, 0}, {0,0,0}, ""); depends_on = "/entry/sample/transformations/" + start.goniometer->GetName() + "_helical_x"; SaveVector(transformations, start.goniometer->GetName() + "_helical_y", start.goniometer->GetYContainer_m(end.max_image_number))-> Transformation("m", depends_on, "", "", "translation", {0, 1, 0}, {0,0,0}, ""); depends_on = "/entry/sample/transformations/" + start.goniometer->GetName() + "_helical_y"; SaveVector(transformations, start.goniometer->GetName() + "_helical_z", start.goniometer->GetZContainer_m(end.max_image_number))-> Transformation("m", depends_on, "", "", "translation", {0, 0, 1}, {0,0,0}, ""); depends_on = "/entry/sample/transformations/" + start.goniometer->GetName() + "_helical_z"; } } else if (start.grid_scan.has_value()) { HDF5Group grid_scan_group(group, "grid_scan"); grid_scan_group.NXClass("NXcollection"); SaveScalar(grid_scan_group, "snake_scan", start.grid_scan->IsSnakeScan()); SaveScalar(grid_scan_group, "vertical_scan", start.grid_scan->IsVerticalScan()); SaveScalar(grid_scan_group, "n_fast", start.grid_scan->GetNFast()); SaveScalar(grid_scan_group, "step_x", start.grid_scan->GetGridStepX_um() * 1e-6)->Units("m"); SaveScalar(grid_scan_group, "step_y", start.grid_scan->GetGridStepY_um() * 1e-6)->Units("m"); HDF5Group transformations(group, "transformations"); transformations.NXClass("NXtransformations"); hdf5_file->HardLink("/entry/sample/transformations","/entry/sample/goniometer"); SaveVector(transformations,"grid_scan_x", start.grid_scan->GetXContainer_m(end.max_image_number)) ->Transformation("m", depends_on, "", "", "translation", {1, 0, 0}, {0,0,0}, ""); depends_on = "/entry/sample/transformations/grid_scan_x"; SaveVector(transformations,"grid_scan_y", start.grid_scan->GetYContainer_m(end.max_image_number)) ->Transformation("m", depends_on, "", "", "translation", {0, 1, 0}, {0,0,0}, ""); depends_on = "/entry/sample/transformations/grid_scan_y"; } group.SaveScalar("depends_on", depends_on); } void NXmx::Attenuator(const StartMessage &start) { if (start.attenuator_transmission) { HDF5Group group(*hdf5_file, "/entry/instrument/attenuator"); group.NXClass("NXattenuator"); SaveScalar(group, "attenuator_transmission", start.attenuator_transmission.value()); } } void NXmx::WriteCalibration(const CompressedImage &image) { if (!calibration_group_created) { calibration_group_created = true; HDF5Group(*hdf5_file, "/entry/instrument/detector/calibration").NXClass("NXcollection"); } SaveCBORImage("/entry/instrument/detector/calibration/" + image.GetChannel(), image); } void NXmx::SaveCBORImage(const std::string &hdf5_path, const CompressedImage &image) { std::vector dims = {image.GetHeight(), image.GetWidth()}; HDF5DataType data_type(image.GetMode()); HDF5Dcpl dcpl; if (image.GetCompressionAlgorithm() != CompressionAlgorithm::NO_COMPRESSION) { dcpl.SetCompression(image.GetCompressionAlgorithm(), 0); dcpl.SetChunking(dims); } HDF5DataSpace data_space(dims); auto dataset = std::make_unique(*hdf5_file, hdf5_path, data_type, data_space, dcpl); if (image.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION) dataset->Write(data_type, image.GetCompressed()); else dataset->WriteDirectChunk(image.GetCompressed(), image.GetCompressedSize(), {0, 0}); } void NXmx::AzimuthalIntegration(const StartMessage &start, const EndMessage &end) { if (!start.az_int_bin_to_q.empty()) { size_t phi_bins = start.az_int_phi_bin_count.value_or(1); size_t q_bin = start.az_int_q_bin_count.value_or(1); std::vector dim = {phi_bins, q_bin}; HDF5Group az_int_group(*hdf5_file, "/entry/azint"); az_int_group.NXClass("NXcollection"); az_int_group.SaveVector("bin_to_q", start.az_int_bin_to_q, dim)->Units("reciprocal Angstrom"); if (!start.az_int_bin_to_two_theta.empty()) az_int_group.SaveVector("bin_to_two_theta", start.az_int_bin_to_two_theta, dim)->Units("degrees"); if (!start.az_int_bin_to_phi.empty()) az_int_group.SaveVector("bin_to_phi", start.az_int_bin_to_phi, dim)->Units("degrees"); for (const auto &[x,y]: end.az_int_result) az_int_group.SaveVector(x, y, dim); } } void NXmx::ADUHistogram(const EndMessage &end) { if (!end.adu_histogram.empty()) { HDF5Group adu_histo_group(*hdf5_file, "/entry/instrument/detector/detectorSpecific/adu_histogram"); adu_histo_group.SaveScalar("bin_width", end.adu_histogram_bin_width); for (const auto &[x, y]: end.adu_histogram) adu_histo_group.SaveVector(x, y); } } void NXmx::Finalize(const EndMessage &end) { if (end.end_date) { hdf5_file->Attr("file_time", end.end_date.value()); hdf5_file->SaveScalar("/entry/end_time", end.end_date.value()); hdf5_file->SaveScalar("/entry/end_time_estimated", end.end_date.value()); } else { std::string time_now = time_UTC(std::chrono::system_clock::now()); hdf5_file->Attr("file_time", time_now); hdf5_file->SaveScalar("/entry/end_time", time_now); hdf5_file->SaveScalar("/entry/end_time_estimated", time_now); } Detector(start_message, end); Sample(start_message, end); AzimuthalIntegration(start_message, end); ADUHistogram(end); if (start_message.file_format == FileWriterFormat::NXmxVDS) LinkToData_VDS(start_message, end); else LinkToData(start_message, end); if (end.indexing_rate) { SaveScalar(*hdf5_file, "/entry/MX/imageIndexedMean", end.indexing_rate.value()); } if (end.bkg_estimate) { SaveScalar(*hdf5_file, "/entry/MX/bkgEstimateMean", end.bkg_estimate.value()); } } void NXmx::UserData(const StartMessage &start) { if (!start.user_data.empty() && start.user_data.contains("hdf5") && start.user_data["hdf5"].is_object()) { HDF5Group group(*hdf5_file, "/entry/user"); group.NXClass("NXcollection"); for (const auto &[x,y]: start.user_data["hdf5"].items()) { if (y.is_number()) group.SaveScalar(x, y.get()); else if (y.is_string()) group.SaveScalar(x, y.get()); } } }