// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include "../frame_serialize/CBORStream2Serializer.h" #include "../frame_serialize/CBORStream2Deserializer.h" #include "../compression/JFJochCompressor.h" #include "../frame_serialize/CborUtil.h" TEST_CASE("CBORSerialize_Start", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); StartMessage message { .detector_distance = 0.0005, .beam_center_x = 456.6, .beam_center_y = 124.3, .number_of_images = 34567, .image_size_x = 456, .image_size_y = 457, .bit_depth_image = 32, .bit_depth_readout = 16, .pixel_signed = true, .countrate_correction_enabled = true, .incident_energy = 12400, .incident_wavelength = 0.988, .frame_time = 0.0001, .count_time = 0.000098, .saturation_value = 65534, .error_value = 65535, .pixel_size_x = 0.000075, .pixel_size_y = 0.000075, .sensor_thickness = 0.0005, .sensor_material = "Si", .unit_cell = UnitCell{.a = 45,.b = 37, .c = 45, .alpha = 90, .beta = 108,. gamma = 120}, .space_group_number = 154, .max_spot_count = 250, .storage_cell_number = 16, .storage_cell_delay_ns = 15345, .pixel_mask_enabled = true, .arm_date = "abc", .sample_name = "lyso", .file_prefix = "lyso1/dir/file", .images_per_file = 12345, .channels = {"default", "sc2"}, .detector_description = "EIGER 16M", .detector_serial_number = "123", .run_name = "bla", .run_number = 4567, .gain_file_names = {"abc" , "def", "/dsadasdsa/dadsadas/dsadsa/M056.bin"}, .detector_translation = {0.5f, 0.0f, 0.5f}, .source_type = "Synchrotron X-ray Source", .source_name = "Swiss Light Source", .instrument_name = "X06SA", .summation = 567, .az_int_bin_to_q = {0.1, 0.2, 0.3, 0.5}, .az_int_q_bin_count = 35, .az_int_phi_bin_count = 120, .total_flux = 123, .attenuator_transmission = 0.345, .write_master_file = true, .user_data = R"({"pi":3.1415, "z":"string"})"_json, .data_reduction_factor_serialmx = 0.75, .experiment_group = "p10001", .jfjoch_release = "1.4.98", .socket_number = 3, .writer_notification_zmq_addr = "tcp://1.2.3.4:5678", .jungfrau_conversion_enabled = true, .jungfrau_conversion_factor = 17.56f, .geometry_transformation_enabled = false, .overwrite = true, .file_format = FileWriterFormat::NXmxVDS, .ring_current_mA = 123, .sample_temperature_K = 345, .indexing_algorithm = IndexingAlgorithmEnum::FFT }; REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; CHECK(output_message.images_per_file == message.images_per_file); CHECK(output_message.detector_distance == Catch::Approx(message.detector_distance)); CHECK(output_message.beam_center_x == Catch::Approx(message.beam_center_x)); CHECK(output_message.beam_center_y == Catch::Approx(message.beam_center_y)); CHECK(output_message.number_of_images == message.number_of_images); CHECK(output_message.image_size_x == message.image_size_x); CHECK(output_message.image_size_y == message.image_size_y); CHECK(output_message.bit_depth_image == message.bit_depth_image); CHECK(output_message.bit_depth_readout == message.bit_depth_readout); CHECK(output_message.incident_energy == Catch::Approx(message.incident_energy)); CHECK(output_message.incident_wavelength == Catch::Approx(message.incident_wavelength)); CHECK(output_message.frame_time == Catch::Approx(message.frame_time)); CHECK(output_message.count_time == Catch::Approx(message.count_time)); CHECK(output_message.saturation_value == message.saturation_value); CHECK(output_message.error_value == message.error_value); CHECK(output_message.pixel_size_x == Catch::Approx(message.pixel_size_x)); CHECK(output_message.pixel_size_y == Catch::Approx(message.pixel_size_y)); CHECK(output_message.sensor_thickness == Catch::Approx(message.sensor_thickness)); CHECK(output_message.sensor_material == message.sensor_material); CHECK(output_message.pixel_mask_enabled == message.pixel_mask_enabled); CHECK(output_message.space_group_number == message.space_group_number); CHECK(output_message.arm_date == message.arm_date); CHECK(output_message.storage_cell_number == message.storage_cell_number); CHECK(output_message.storage_cell_delay_ns == message.storage_cell_delay_ns); CHECK(output_message.pixel_signed == message.pixel_signed); CHECK(output_message.sample_name == message.sample_name); CHECK(output_message.file_prefix == message.file_prefix); CHECK(output_message.max_spot_count == message.max_spot_count); CHECK(output_message.channels == message.channels); CHECK(output_message.detector_description == message.detector_description); CHECK(output_message.detector_serial_number == message.detector_serial_number); CHECK(output_message.run_name == message.run_name); CHECK(output_message.run_number == message.run_number); CHECK(output_message.source_type == message.source_type); CHECK(output_message.source_name == message.source_name); CHECK(output_message.instrument_name == message.instrument_name); CHECK(output_message.az_int_q_bin_count == message.az_int_q_bin_count); CHECK(output_message.az_int_phi_bin_count == message.az_int_phi_bin_count); CHECK(output_message.summation == message.summation); CHECK(output_message.az_int_bin_to_q == message.az_int_bin_to_q); for (int i = 0; i < 3; i++) CHECK(output_message.detector_translation[i] == message.detector_translation[i]); CHECK(output_message.unit_cell); CHECK(output_message.unit_cell->a == message.unit_cell->a); CHECK(output_message.unit_cell->b == message.unit_cell->b); CHECK(output_message.unit_cell->c == message.unit_cell->c); CHECK(output_message.unit_cell->alpha == message.unit_cell->alpha); CHECK(output_message.unit_cell->beta == message.unit_cell->beta); CHECK(output_message.unit_cell->gamma == message.unit_cell->gamma); REQUIRE (output_message.total_flux); CHECK(output_message.total_flux.value() == message.total_flux.value()); REQUIRE (output_message.attenuator_transmission); CHECK(output_message.attenuator_transmission.value() == message.attenuator_transmission.value()); CHECK(output_message.user_data == message.user_data); REQUIRE(output_message.user_data.is_object()); CHECK(output_message.user_data.size() == 2); CHECK(output_message.gain_file_names == message.gain_file_names); CHECK(output_message.countrate_correction_enabled == message.countrate_correction_enabled); CHECK(output_message.flatfield_enabled == message.flatfield_enabled); CHECK(output_message.write_master_file == message.write_master_file); CHECK(output_message.data_reduction_factor_serialmx == message.data_reduction_factor_serialmx); CHECK(output_message.experiment_group == message.experiment_group); CHECK(output_message.jfjoch_release == message.jfjoch_release); CHECK(output_message.socket_number == message.socket_number); CHECK(output_message.writer_notification_zmq_addr == message.writer_notification_zmq_addr); CHECK(output_message.geometry_transformation_enabled == message.geometry_transformation_enabled); CHECK(output_message.jungfrau_conversion_enabled == message.jungfrau_conversion_enabled); CHECK(output_message.jungfrau_conversion_factor == message.jungfrau_conversion_factor); CHECK(output_message.overwrite == message.overwrite); CHECK(output_message.file_format == message.file_format); CHECK(output_message.sample_temperature_K == message.sample_temperature_K); CHECK(output_message.ring_current_mA == message.ring_current_mA); CHECK(output_message.indexing_algorithm == message.indexing_algorithm); } TEST_CASE("CBORSerialize_Start_GoniometerAxis", "[CBOR]") { GoniometerAxis axis("z", 115, 0.456, {1,2,3}, Coord{50,20,30}); axis.ScreeningWedge(0.01); StartMessage message{ .goniometer = axis }; std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; REQUIRE(output_message.goniometer.has_value()); CHECK(output_message.goniometer->GetName() == axis.GetName()); CHECK(output_message.goniometer->GetStart_deg() == axis.GetStart_deg()); CHECK(output_message.goniometer->GetIncrement_deg() == axis.GetIncrement_deg()); CHECK(output_message.goniometer->GetAxis().x == Catch::Approx(axis.GetAxis().x)); CHECK(output_message.goniometer->GetAxis().y == Catch::Approx(axis.GetAxis().y)); CHECK(output_message.goniometer->GetAxis().z == Catch::Approx(axis.GetAxis().z)); REQUIRE(output_message.goniometer->GetHelicalStep()); CHECK(output_message.goniometer->GetHelicalStep() == axis.GetHelicalStep()); REQUIRE(output_message.goniometer->GetScreeningWedge()); CHECK(output_message.goniometer->GetScreeningWedge() == axis.GetScreeningWedge()); } TEST_CASE("CBORSerialize_Start_GridScan", "[CBOR]") { GridScanSettings grid(123, 0.1, 34.0, true, true); StartMessage message{ .grid_scan = grid }; std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; REQUIRE(!output_message.goniometer.has_value()); REQUIRE(output_message.grid_scan.has_value()); CHECK(output_message.grid_scan->GetNFast() == grid.GetNFast()); CHECK(output_message.grid_scan->IsVerticalScan() == grid.IsVerticalScan()); CHECK(output_message.grid_scan->IsSnakeScan() == grid.IsSnakeScan()); CHECK(output_message.grid_scan->GetGridElemFast_um() == Catch::Approx(grid.GetGridElemFast_um())); CHECK(output_message.grid_scan->GetGridElemSlow_um() == Catch::Approx(grid.GetGridElemSlow_um())); } TEST_CASE("CBORSerialize_Start_ThresholdEnergy", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); StartMessage message{}; message.threshold_energy["thr1"] = 12500; message.threshold_energy["thr2"] = 17000; REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; REQUIRE(output_message.threshold_energy.size() == 2); REQUIRE(output_message.threshold_energy == message.threshold_energy); } TEST_CASE("CBORSerialize_Start_Fluorescence", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); StartMessage message{}; // prepare fluorescence spectrum std::vector energy{1.0f, 2.0f, 3.0f}; std::vector data{10.0f, 20.0f, 15.0f}; message.fluorescence_spectrum = XrayFluorescenceSpectrum(energy, data); REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); const StartMessage &out = *deserialized->start_message; REQUIRE(!out.fluorescence_spectrum.empty()); CHECK(out.fluorescence_spectrum.GetEnergy_eV() == energy); CHECK(out.fluorescence_spectrum.GetData() == data); } TEST_CASE("CBORSerialize_ROI", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); StartMessage message { .rois = { ROIConfig{ .type = ROIConfig::ROIType::Circle, .name = "roi1", .circle = ROIConfigCircle{ .r = 3.0, .x = 5.0, .y = 4.0 } }, ROIConfig{ .type = ROIConfig::ROIType::Box, .name = "roi2", .box = ROIConfigBox{ .xmin = 5, .xmax = 12, .ymin = 7, .ymax = 3 } }, ROIConfig{ .type = ROIConfig::ROIType::Azim, .name = "roi3", .azim = ROIConfigAzim{ .qmin = 4.0, .qmax = 5.0 } } } }; REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; REQUIRE(output_message.rois.size() == 3); for (int i = 0; i < 3; i++) { CHECK(output_message.rois[i].name == message.rois[i].name); CHECK(output_message.rois[i].type == message.rois[i].type); } CHECK(output_message.rois[0].circle.x == message.rois[0].circle.x); CHECK(output_message.rois[0].circle.y == message.rois[0].circle.y); CHECK(output_message.rois[0].circle.r == message.rois[0].circle.r); CHECK(output_message.rois[1].box.xmin == message.rois[1].box.xmin); CHECK(output_message.rois[1].box.xmax == message.rois[1].box.xmax); CHECK(output_message.rois[1].box.ymin == message.rois[1].box.ymin); CHECK(output_message.rois[1].box.ymax == message.rois[1].box.ymax); CHECK(output_message.rois[2].azim.qmin == message.rois[2].azim.qmin ); CHECK(output_message.rois[2].azim.qmax == message.rois[2].azim.qmax ); } TEST_CASE("CBORSerialize_Start_EmptyString", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); StartMessage message { .sample_name = "", .file_prefix = "" }; REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; CHECK(output_message.file_prefix.empty()); CHECK(output_message.sample_name.empty()); CHECK(output_message.arm_date.empty()); } TEST_CASE("CBORSerialize_Start_PixelMask", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector mask_0(456*457, 15); std::vector mask_1(456*457, 12); StartMessage message { .image_size_x = 456, .image_size_y = 457, .pixel_mask = {{"default" , mask_0}, {"mask_1" , mask_1}} }; REQUIRE_NOTHROW(serializer.SerializeSequenceStart(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::START); REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; REQUIRE(output_message.pixel_mask.size() == 2); REQUIRE(output_message.pixel_mask.contains("default")); REQUIRE(output_message.pixel_mask.contains("mask_1")); REQUIRE(output_message.pixel_mask["default"].size() == 456*457); REQUIRE(output_message.pixel_mask["mask_1"].size() == 456*457); REQUIRE(output_message.pixel_mask["default"] == mask_0); REQUIRE(output_message.pixel_mask["mask_1"] == mask_1); } TEST_CASE("CBORSerialize_Calibration", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector calib1(256); for (int i = 0; i < 256; i++) { calib1[i] = i * 34.567; } CompressedImage image1(calib1, 16, 16); image1.Channel("calib1"); REQUIRE_NOTHROW(serializer.SerializeCalibration(image1)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::CALIBRATION); REQUIRE(deserialized->calibration); CompressedImage &output_message = *deserialized->calibration; CHECK(output_message.GetMode() == CompressedImageMode::Float32); CHECK(output_message.GetWidth() == 16); CHECK(output_message.GetHeight() == 16); CHECK(output_message.GetChannel() == "calib1"); CHECK(memcmp(output_message.GetCompressed(), calib1.data(), 256 * sizeof(float)) == 0); } TEST_CASE("CBORSerialize_End", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); EndMessage message { .max_image_number = 57789, .images_collected_count = 50000, .images_sent_to_write_count = 40000, .max_receiver_delay = 3456, .efficiency = 0.99, .end_date = "ccc", .run_name = "bla5", .run_number = 45676782 }; REQUIRE_NOTHROW(serializer.SerializeSequenceEnd(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::END); REQUIRE(deserialized->end_message); EndMessage &output_message = *deserialized->end_message; REQUIRE(output_message.max_receiver_delay == message.max_receiver_delay); REQUIRE(output_message.max_image_number == message.max_image_number); REQUIRE(output_message.images_collected_count == message.images_collected_count); REQUIRE(output_message.images_sent_to_write_count == message.images_sent_to_write_count); REQUIRE(output_message.efficiency); REQUIRE(output_message.efficiency == Catch::Approx(message.efficiency.value())); REQUIRE(output_message.end_date == message.end_date); REQUIRE(output_message.run_number == message.run_number); REQUIRE(output_message.run_name == message.run_name); REQUIRE(output_message.az_int_result.empty()); } TEST_CASE("CBORSerialize_End_RadIntResult", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); EndMessage message { .max_image_number = 57789, .max_receiver_delay = 3456, .efficiency = 0.99, .end_date = "ccc", .run_name = "bla5", .run_number = 45676782 }; message.az_int_result["avg"] = {11.0, 12.0, 13.0}; message.az_int_result["file0"] = {56.0, 75.0, 34.0}; REQUIRE_NOTHROW(serializer.SerializeSequenceEnd(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::END); EndMessage &output_message = *deserialized->end_message; REQUIRE(output_message.az_int_result.size() == 2); REQUIRE(output_message.az_int_result.contains("avg")); REQUIRE(output_message.az_int_result.contains("file0")); CHECK(message.az_int_result["avg"] == output_message.az_int_result["avg"]); CHECK(message.az_int_result["file0"] == output_message.az_int_result["file0"]); } TEST_CASE("CBORSerialize_End_ADUHistogram", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); EndMessage message { .max_image_number = 57789, .max_receiver_delay = 3456, .efficiency = 0.99, .end_date = "ccc", .run_name = "bla5", .run_number = 45676782, .adu_histogram_bin_width = 55 }; message.adu_histogram["avg"] = {11, 12, 13}; message.adu_histogram["file0"] = {56, 75, 34}; REQUIRE_NOTHROW(serializer.SerializeSequenceEnd(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::END); REQUIRE(deserialized->end_message); EndMessage &output_message = *deserialized->end_message; REQUIRE(output_message.adu_histogram.size() == 2); REQUIRE(output_message.adu_histogram.contains("avg")); REQUIRE(output_message.adu_histogram.contains("file0")); CHECK(message.adu_histogram["avg"] == output_message.adu_histogram["avg"]); CHECK(message.adu_histogram["file0"] == output_message.adu_histogram["file0"]); CHECK(message.adu_histogram_bin_width == output_message.adu_histogram_bin_width); } TEST_CASE("CBORSerialize_Image", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2); DataMessage message { .number = 456, .image = image, .image_collection_efficiency = 0.11, .spots = spots, .spot_count_ice_rings = 157, .bkg_estimate = 12.345f, .indexing_result = true, .indexing_unit_cell = UnitCell{.a = 123, .b = 145, .c=67.5, .alpha = 90, .beta = 120, .gamma = 134}, .adu_histogram = {3, 4, 5, 8}, .timestamp = 1ul<<27 | 1ul <<35, .exptime = 1000, .run_name = "bla2", .run_number = 4567678, .saturated_pixel_count = 378, .error_pixel_count = 123, .strong_pixel_count = 1234, .min_viable_pixel_value = 123, .max_viable_pixel_value = 6789, .user_data = R"({"pi":3.1415, "z":"string"})"_json, .jf_info = UINT32_MAX, .receiver_aq_dev_delay = 2323, .storage_cell = 0xF, .xfel_pulse_id = UINT64_MAX - 5678, .xfel_event_code = UINT64_MAX - 123, .original_number = 12789 }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION); REQUIRE(image_array.image.GetWidth() == 256); REQUIRE(image_array.image.GetHeight() == 2); REQUIRE(image_array.image.GetMode() == CompressedImageMode::Uint16); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size() * sizeof(uint16_t)); REQUIRE(image_array.indexing_result == message.indexing_result); REQUIRE(image_array.number == 456); REQUIRE(image_array.run_number == message.run_number); REQUIRE(image_array.run_name == message.run_name); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size() * sizeof(uint16_t)) == 0); REQUIRE(image_array.xfel_pulse_id == message.xfel_pulse_id); REQUIRE(image_array.xfel_event_code == message.xfel_event_code); REQUIRE(image_array.jf_info == message.jf_info); REQUIRE(image_array.timestamp == message.timestamp); REQUIRE(image_array.storage_cell == message.storage_cell); REQUIRE(image_array.exptime == message.exptime); REQUIRE(image_array.receiver_aq_dev_delay == image_array.receiver_aq_dev_delay); REQUIRE(image_array.adu_histogram == message.adu_histogram); REQUIRE(image_array.saturated_pixel_count == message.saturated_pixel_count); REQUIRE(image_array.error_pixel_count == message.error_pixel_count); REQUIRE(image_array.strong_pixel_count == message.strong_pixel_count); REQUIRE(image_array.bkg_estimate == message.bkg_estimate); REQUIRE(image_array.image_collection_efficiency == message.image_collection_efficiency); REQUIRE(image_array.user_data == message.user_data); REQUIRE(image_array.original_number == message.original_number); REQUIRE(image_array.spot_count_ice_rings == message.spot_count_ice_rings); REQUIRE(image_array.indexing_unit_cell.has_value()); REQUIRE(image_array.indexing_unit_cell.value().a == message.indexing_unit_cell.value().a); REQUIRE(image_array.indexing_unit_cell.value().b == message.indexing_unit_cell.value().b); REQUIRE(image_array.indexing_unit_cell.value().c == message.indexing_unit_cell.value().c); REQUIRE(image_array.indexing_unit_cell.value().alpha == message.indexing_unit_cell.value().alpha); REQUIRE(image_array.indexing_unit_cell.value().beta == message.indexing_unit_cell.value().beta); REQUIRE(image_array.indexing_unit_cell.value().gamma == message.indexing_unit_cell.value().gamma); REQUIRE(image_array.min_viable_pixel_value == message.min_viable_pixel_value); REQUIRE(image_array.max_viable_pixel_value == message.max_viable_pixel_value); } TEST_CASE("CBORSerialize_Image_CrystalLattice", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2); std::vector latt = {1,2,3,4,5,6,7,8,9}; DataMessage message { .image = image, .indexing_lattice = CrystalLattice(latt) }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(deserialized->data_message->indexing_lattice); REQUIRE(deserialized->data_message->indexing_lattice->GetVector() == latt); } TEST_CASE("CBORSerialize_Image_2", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512*1024); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 1024, 512); DataMessage message { .number = 480, .image = image, .spots = spots, .indexing_result = true }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION); REQUIRE(image_array.image.GetWidth() == 1024); REQUIRE(image_array.image.GetHeight() == 512); REQUIRE(image_array.image.GetMode() == CompressedImageMode::Uint8); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size()); REQUIRE(image_array.indexing_result == message.indexing_result); REQUIRE(image_array.number == 480); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size()) == 0); REQUIRE(!image_array.original_number); } TEST_CASE("CBORSerialize_Image_Float", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512*1024); for (int i = 0; i < test.size(); i++) test[i] = i * 0.1f; CompressedImage image(test, 1024, 512); DataMessage message { .number = 480, .image = image, .spots = spots, .indexing_result = false }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION); REQUIRE(image_array.image.GetWidth() == 1024); REQUIRE(image_array.image.GetHeight() == 512); REQUIRE(image_array.image.GetMode() == CompressedImageMode::Float32); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size() * sizeof(float)); REQUIRE(image_array.indexing_result == message.indexing_result); REQUIRE(image_array.number == 480); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size() * sizeof(float)) == 0); } TEST_CASE("CBORSerialize_Image_Rgb", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512*1024); for (int i = 0; i < test.size(); i++) { test[i].r = (i / 256) % 256; test[i].g = i % 256; test[i].b = (i / 771) % 256; } CompressedImage image(test, 1024, 512); DataMessage message { .number = 480, .image = image, .spots = spots, .indexing_result = false }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::NO_COMPRESSION); REQUIRE(image_array.image.GetWidth() == 1024); REQUIRE(image_array.image.GetHeight() == 512); REQUIRE(image_array.image.GetMode() == CompressedImageMode::RGB); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size() * sizeof(rgb)); REQUIRE(image_array.indexing_result == message.indexing_result); REQUIRE(image_array.number == 480); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size() * sizeof(rgb)) == 0); } TEST_CASE("CBORSerialize_Image_Compressed", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2, CompressedImageMode::Int32, CompressionAlgorithm::BSHUF_LZ4); DataMessage message { .number = 456, .image = image, .spots = spots }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::BSHUF_LZ4); REQUIRE(image_array.image.GetWidth() == 256); REQUIRE(image_array.image.GetHeight() == 2); REQUIRE(image_array.image.GetMode() == CompressedImageMode::Int32); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size()); REQUIRE(image_array.number == 456); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size()) == 0); } TEST_CASE("CBORSerialize_Image_Rad_Int_Profile", "[CBOR]") { std::vector buffer(8 * 1024 * 1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2); DataMessage message{ .number = 789, .image = image, .az_int_profile = {4.0, 5.0, 7.0, 12.0, 13.25, 0.125} }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.number == 789); REQUIRE(image_array.image.GetCompressedSize() == test.size() * sizeof(uint16_t)); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size() * sizeof(uint16_t)) == 0); REQUIRE(image_array.az_int_profile == message.az_int_profile); } TEST_CASE("CBORSerialize_Image_Spots", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector spots; spots.push_back(SpotToSave{.x = 7, .y = 8, .intensity = 34, .indexed = false}); spots.push_back(SpotToSave{.x = 37, .y = 48, .intensity = 123, .indexed = true}); std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2); DataMessage message { .number = 789, .image = image, .spots = spots }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.number == 789); REQUIRE(image_array.image.GetCompressedSize() == test.size() * sizeof(uint16_t)); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size() * sizeof(uint16_t)) == 0); REQUIRE(image_array.spots.size() == 2); REQUIRE(image_array.spots[0].intensity == 34); REQUIRE(!image_array.spots[0].indexed); REQUIRE(image_array.spots[1].x == 37); REQUIRE(image_array.spots[1].y == 48); REQUIRE(image_array.spots[1].intensity == 123); REQUIRE(image_array.spots[1].indexed); } TEST_CASE("CBORSerialize_Image_Reflections") { // Prepare a few reflections with distinct values std::vector refs_in; refs_in.push_back(Reflection{ .h = 1, .k = 0, .l = -1, .predicted_x = 1024.5f, .predicted_y = 768.25f, .d = 2.345f }); refs_in.push_back(Reflection{ .h = -3, .k = 2, .l = 7, .predicted_x = 5.0f, .predicted_y = 10.0f, .d = 4.0f }); refs_in.push_back(Reflection{ .h = 0, .k = 0, .l = 1, .predicted_x = 0.0f, .predicted_y = 0.0f, .d = 10.0f }); // Minimal DataMessage carrying reflections std::vector test(512); CompressedImage image(test, 256, 2); DataMessage msg_in { .number = 789, .image = image, .reflections = refs_in }; // Serialize std::vector buffer(1 << 20); // 1 MB buffer for safety CBORStream2Serializer ser(buffer.data(), buffer.size()); ser.SerializeImage(msg_in); // Deserialize auto deserialized = CBORStream2Deserialize(buffer.data(), ser.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &msg_out = *deserialized->data_message; // Check reflections REQUIRE(msg_out.reflections.size() == refs_in.size()); for (size_t i = 0; i < refs_in.size(); ++i) { const auto& a = refs_in[i]; const auto& b = msg_out.reflections[i]; CHECK(a.h == b.h); CHECK(a.k == b.k); CHECK(a.l == b.l); CHECK(b.predicted_x == Catch::Approx(a.predicted_x).margin(1e-6f)); CHECK(b.predicted_y == Catch::Approx(a.predicted_y).margin(1e-6f)); CHECK(b.d == Catch::Approx(a.d).margin(1e-6f)); } } TEST_CASE("CBORSerialize_Image_ROI", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector test(512); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(test, 256, 2); DataMessage message { .number = 789, .image = image, }; message.roi["roi0"] = {.sum = -876, .sum_square = 89998, .max_count = -1, .pixels = 4567, .x_weighted = 123, .y_weighted = 122}; message.roi["roi1"] = {.sum = 876, .sum_square = 998, .max_count = 12, .pixels = 234, .x_weighted = -11222233443123LL, .y_weighted = -5}; REQUIRE_NOTHROW(serializer.SerializeImage(message)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.number == 789); REQUIRE(image_array.roi.size() == 2); REQUIRE(image_array.roi.contains("roi0")); REQUIRE(image_array.roi.contains("roi1")); CHECK(image_array.roi["roi0"].sum == message.roi["roi0"].sum); CHECK(image_array.roi["roi0"].sum_square == message.roi["roi0"].sum_square); CHECK(image_array.roi["roi0"].max_count == message.roi["roi0"].max_count); CHECK(image_array.roi["roi0"].pixels == message.roi["roi0"].pixels); CHECK(image_array.roi["roi0"].x_weighted == message.roi["roi0"].x_weighted); CHECK(image_array.roi["roi0"].y_weighted == message.roi["roi0"].y_weighted); CHECK(image_array.roi["roi1"].sum == message.roi["roi1"].sum); CHECK(image_array.roi["roi1"].sum_square == message.roi["roi1"].sum_square); CHECK(image_array.roi["roi1"].max_count == message.roi["roi1"].max_count); CHECK(image_array.roi["roi1"].pixels == message.roi["roi1"].pixels); CHECK(image_array.roi["roi1"].x_weighted == message.roi["roi1"].x_weighted); CHECK(image_array.roi["roi1"].y_weighted == message.roi["roi1"].y_weighted); } TEST_CASE("CBORSerialize_Image_Append", "[CBOR]") { std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); std::vector test(512 * 1024); for (int i = 0; i < test.size(); i++) test[i] = (i * 253 + 56) % 256; CompressedImage image(nullptr, 0, 1024, 512, CompressedImageMode::Uint8, CompressionAlgorithm::BSHUF_LZ4); DataMessage message{ .number = 480, .image = image }; REQUIRE_NOTHROW(serializer.SerializeImage(message)); memcpy(buffer.data() + serializer.GetImageAppendOffset(), test.data(), 512 * 1024); //REQUIRE_THROWS(serializer.AppendImage(16*1024*1024)); REQUIRE_NOTHROW(serializer.AppendImage(512 * 1024)); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::IMAGE); REQUIRE(deserialized->data_message); DataMessage &image_array = *deserialized->data_message; REQUIRE(image_array.image.GetCompressionAlgorithm() == CompressionAlgorithm::BSHUF_LZ4); REQUIRE(image_array.image.GetWidth() == 1024); REQUIRE(image_array.image.GetHeight() == 512); REQUIRE(image_array.image.GetMode() == CompressedImageMode::Uint8); REQUIRE(image_array.image.GetChannel() == "default"); REQUIRE(image_array.image.GetCompressedSize() == test.size()); REQUIRE(image_array.indexing_result == message.indexing_result); REQUIRE(image_array.number == 480); REQUIRE(memcmp(image_array.image.GetCompressed(), test.data(), test.size()) == 0); } TEST_CASE("CBORSerialize_Metadata", "[CBOR]") { MetadataMessage msgs; CompressedImage image(nullptr, 0, 123, 145, CompressedImageMode::Uint8, CompressionAlgorithm::BSHUF_LZ4); msgs.images.push_back(DataMessage{.number = 172, .image = image, .bkg_estimate = 45}); msgs.images.push_back(DataMessage{.number = 173, .image = image, .bkg_estimate = 48}); //msgs.push_back(DataMessage{.number = 174, .image = CompressedImage{.GetWidth() = 123, .GetHeight() = 145}, .bkg_estimate = 48}); std::vector buffer(8*1024*1024); CBORStream2Serializer serializer(buffer.data(), buffer.size()); REQUIRE_NOTHROW(serializer.SerializeMetadata(msgs)); REQUIRE(serializer.GetBufferSize() > 0); auto deserialized = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(deserialized); REQUIRE(deserialized->msg_type == CBORImageType::METADATA); REQUIRE(deserialized->metadata); REQUIRE(deserialized->metadata->images.size() == 2); CHECK(deserialized->metadata->images.at(0).number == 172); CHECK(deserialized->metadata->images.at(0).bkg_estimate == 45); CHECK(deserialized->metadata->images.at(0).image.GetWidth() == 0); CHECK(deserialized->metadata->images.at(1).number == 173); CHECK(deserialized->metadata->images.at(1).bkg_estimate == 48); CHECK(deserialized->metadata->images.at(1).image.GetWidth() == 0); } TEST_CASE("CBORSerialize_Image_LatticeType", "[CBOR][Lattice]") { std::vector buffer(1 << 20); CBORStream2Serializer serializer(buffer.data(), buffer.size()); // Prepare an image with lattice_type set std::vector pixels(16, 0); CompressedImage img(pixels, 4, 4); DataMessage in_msg{ .number = 1, .image = img }; // Case 1: Tetragonal, body-centered -> expect same after round trip in_msg.lattice_type = LatticeMessage{ .centering = 'I', .niggli_class = 25, .crystal_system = gemmi::CrystalSystem::Tetragonal }; REQUIRE_NOTHROW(serializer.SerializeImage(in_msg)); auto out = CBORStream2Deserialize(buffer.data(), serializer.GetBufferSize()); REQUIRE(out); REQUIRE(out->msg_type == CBORImageType::IMAGE); REQUIRE(out->data_message); // LatticeMessage present REQUIRE(out->data_message->lattice_type.has_value()); const auto lm = out->data_message->lattice_type.value(); CHECK(lm.centering == 'I'); CHECK(lm.niggli_class == 25); CHECK(lm.crystal_system == gemmi::CrystalSystem::Tetragonal); // Case 2: Absent lattice_type should remain absent CBORStream2Serializer serializer2(buffer.data(), buffer.size()); DataMessage in_msg2{ .number = 2, .image = img }; REQUIRE_NOTHROW(serializer2.SerializeImage(in_msg2)); auto out2 = CBORStream2Deserialize(buffer.data(), serializer2.GetBufferSize()); REQUIRE(out2); REQUIRE(out2->msg_type == CBORImageType::IMAGE); REQUIRE(out2->data_message); CHECK_FALSE(out2->data_message->lattice_type.has_value()); // Case 3: Various systems/centerings sanity round-trip struct Case { gemmi::CrystalSystem cs; char ctr; }; std::vector cases = { {gemmi::CrystalSystem::Triclinic, 'P'}, {gemmi::CrystalSystem::Monoclinic, 'C'}, {gemmi::CrystalSystem::Orthorhombic, 'F'}, {gemmi::CrystalSystem::Orthorhombic, 'A'}, {gemmi::CrystalSystem::Tetragonal, 'P'}, {gemmi::CrystalSystem::Trigonal, 'R'}, {gemmi::CrystalSystem::Hexagonal, 'P'}, {gemmi::CrystalSystem::Cubic, 'I'}, }; int i = 0; for (const auto& c : cases) { CBORStream2Serializer ser(buffer.data(), buffer.size()); DataMessage m{ .number = 3, .image = img }; m.lattice_type = LatticeMessage{ .centering = c.ctr, .niggli_class = i, .crystal_system = c.cs }; REQUIRE_NOTHROW(ser.SerializeImage(m)); auto d = CBORStream2Deserialize(buffer.data(), ser.GetBufferSize()); REQUIRE(d); REQUIRE(d->msg_type == CBORImageType::IMAGE); REQUIRE(d->data_message); REQUIRE(d->data_message->lattice_type.has_value()); auto l = d->data_message->lattice_type.value(); CHECK(l.centering == c.ctr); CHECK(l.niggli_class == i); CHECK(l.crystal_system == c.cs); i++; } }