708 lines
32 KiB
C++
708 lines
32 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <cmath>
|
|
|
|
#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") {
|
|
|
|
tmp_filename = fmt::format("{}.{:x}.tmp", filename, std::chrono::system_clock::now().time_since_epoch().count());
|
|
|
|
if (start.overwrite.has_value())
|
|
overwrite = start.overwrite.value();
|
|
|
|
MakeDirectory(filename);
|
|
|
|
bool v1_10 = (start.file_format == FileWriterFormat::NXmxVDS);
|
|
|
|
hdf5_file = std::make_unique<HDF5File>(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<HDF5DataSet> NXmx::VDS(const StartMessage &start,
|
|
const std::string &name,
|
|
const std::vector<hsize_t> &dim,
|
|
const HDF5DataType &data_type) {
|
|
return VDS(start, name, name, dim, data_type);
|
|
}
|
|
|
|
std::unique_ptr<HDF5DataSet> NXmx::VDS(const StartMessage &start,
|
|
const std::string &name_src,
|
|
const std::string &name_dest,
|
|
const std::vector<hsize_t> &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<hsize_t> 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<HDF5DataSet>(*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<uint32_t>(start.image_size_x));
|
|
SaveScalar(det_specific, "y_pixels_in_detector", static_cast<uint32_t>(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<uint32_t>(start.storage_cell_number.value()));
|
|
if (start.storage_cell_number.value() > 1)
|
|
SaveScalar(det_specific, "storage_cell_delay", static_cast<uint32_t>(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<hsize_t> 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<int32_t> &origin, const std::vector<int32_t> &size,
|
|
const std::vector<double> &fast_axis, const std::vector<double> &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<double> 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<double> 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<int32_t> origin = {0, 0};
|
|
std::vector<int32_t> size = {static_cast<int32_t>(start.image_size_y),
|
|
static_cast<int32_t>(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<double>{1.0, 0.0, 0.0});
|
|
|
|
SaveScalar(transformations, "rot2", rot2)->
|
|
Transformation("rad",
|
|
"/entry/instrument/detector/transformations/rot1",
|
|
"detector", "detector_arm",
|
|
"rotation",
|
|
std::vector<double>{0.0, -1.0, 0.0});
|
|
|
|
SaveScalar(transformations, "rot3", rot3)->
|
|
Transformation("rad",
|
|
"/entry/instrument/detector/transformations/rot2",
|
|
"detector", "detector_arm",
|
|
"rotation",
|
|
std::vector<double>{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<float> 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 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<hsize_t> 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<HDF5DataSet>(*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<hsize_t> 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<double>());
|
|
else if (y.is_string())
|
|
group.SaveScalar(x, y.get<std::string>());
|
|
}
|
|
}
|
|
}
|