976 lines
40 KiB
C++
976 lines
40 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "CBORStream2Deserializer.h"
|
|
#include "CborErr.h"
|
|
#include "CborUtil.h"
|
|
#include <nlohmann/json.hpp>
|
|
|
|
namespace {
|
|
std::string GetCBORString(CborValue &value) {
|
|
if (!cbor_value_is_text_string(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "String expected");
|
|
|
|
std::vector<char> s(16384);
|
|
size_t len = s.size() - 1;
|
|
cborErr(cbor_value_copy_text_string(&value, s.data(), &len, nullptr));
|
|
s[len + 1] = 0;
|
|
cborErr(cbor_value_advance(&value));
|
|
return {s.data()};
|
|
}
|
|
|
|
int64_t GetCBORInt(CborValue &value) {
|
|
if (!cbor_value_is_integer(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Integer expected");
|
|
int64_t tmp;
|
|
cborErr(cbor_value_get_int64(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
}
|
|
|
|
|
|
uint64_t GetCBORUInt(CborValue &value) {
|
|
if (!cbor_value_is_unsigned_integer(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Unsigned integer expected");
|
|
uint64_t tmp;
|
|
cborErr(cbor_value_get_uint64(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
}
|
|
|
|
float GetCBORFloat(CborValue &value) {
|
|
if (cbor_value_is_double(&value)) {
|
|
double tmp;
|
|
cborErr(cbor_value_get_double(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return static_cast<float>(tmp);
|
|
} else if (cbor_value_is_float(&value)) {
|
|
float tmp;
|
|
cborErr(cbor_value_get_float(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
} else if (cbor_value_is_half_float(&value)) {
|
|
float tmp;
|
|
cborErr(cbor_value_get_half_float_as_float(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
}
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Float expected");
|
|
}
|
|
|
|
bool GetCBORBool(CborValue &value) {
|
|
if (!cbor_value_is_boolean(&value)) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Bool expected " + std::to_string(cbor_value_get_type(&value)));
|
|
}
|
|
bool tmp;
|
|
cborErr(cbor_value_get_boolean(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
}
|
|
|
|
CborTag GetCBORTag(CborValue &value) {
|
|
if (!cbor_value_is_tag(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Tag expected");
|
|
CborTag tmp;
|
|
cborErr(cbor_value_get_tag(&value, &tmp));
|
|
cborErr(cbor_value_advance(&value));
|
|
return tmp;
|
|
}
|
|
|
|
std::string GetCBORDateTime(CborValue &value) {
|
|
auto tag = GetCBORTag(value);
|
|
if (tag == CborDateTimeStringTag)
|
|
return GetCBORString(value);
|
|
else if (tag == CborUnixTime_tTag) {
|
|
time_t t = GetCBORUInt(value);
|
|
char buf1[255];
|
|
strftime(buf1, sizeof(buf1), "%FT%T", gmtime(&t));
|
|
return std::string(buf1) + "Z";
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Time/date tag error");
|
|
}
|
|
|
|
uint64_t GetCBORArrayLen(CborValue &value) {
|
|
if (!cbor_value_is_array(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Array expected");
|
|
|
|
size_t array_len;
|
|
cborErr(cbor_value_get_array_length(&value, &array_len));
|
|
return array_len;
|
|
}
|
|
|
|
uint64_t GetCBORMapLen(CborValue &value) {
|
|
if (!cbor_value_is_map(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Map expected");
|
|
size_t map_len;
|
|
cborErr(cbor_value_get_map_length(&value, &map_len));
|
|
return map_len;
|
|
}
|
|
|
|
std::pair<uint64_t, uint64_t> GetRational(CborValue &value) {
|
|
std::pair<uint64_t, uint64_t> ret;
|
|
if (GetCBORArrayLen(value) != 2)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Rational number expects array of 2 elements");
|
|
CborValue array_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
ret.first = GetCBORUInt(array_value);
|
|
ret.second = GetCBORUInt(array_value);
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
|
|
return ret;
|
|
}
|
|
|
|
UnitCell ProcessUnitCellElement(CborValue &value) {
|
|
UnitCell unit_cell{};
|
|
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
|
|
if (key == "a")
|
|
unit_cell.a = GetCBORFloat(map_value);
|
|
else if (key == "b")
|
|
unit_cell.b = GetCBORFloat(map_value);
|
|
else if (key == "c")
|
|
unit_cell.c = GetCBORFloat(map_value);
|
|
else if (key == "alpha")
|
|
unit_cell.alpha = GetCBORFloat(map_value);
|
|
else if (key == "beta")
|
|
unit_cell.beta = GetCBORFloat(map_value);
|
|
else if (key == "gamma")
|
|
unit_cell.gamma = GetCBORFloat(map_value);
|
|
else
|
|
cbor_value_advance(&map_value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
|
|
return unit_cell;
|
|
}
|
|
|
|
std::pair<uint64_t, uint64_t> GetCBORDimension2DArray(CborValue &value) {
|
|
std::pair<uint64_t, uint64_t> ret;
|
|
|
|
if (GetCBORArrayLen(value) != 2)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Only 2D arrays allowed");
|
|
|
|
CborValue array_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
|
|
ret.second = GetCBORUInt(array_value);
|
|
ret.first = GetCBORUInt(array_value);
|
|
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::pair<const uint8_t *, size_t> GetCBORByteString(CborValue &value) {
|
|
if (!cbor_value_is_byte_string(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Bool expected");
|
|
|
|
size_t len;
|
|
cborErr(cbor_value_get_string_length(&value, &len));
|
|
|
|
// Code below from: https://github.com/dectris/documentation/blob/main/stream_v2/examples/stream2.c
|
|
|
|
const uint8_t *ptr = cbor_value_get_next_byte(&value);
|
|
uint8_t val = *ptr;
|
|
|
|
if (val < 0x40)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Something wrong with byte string tag");
|
|
|
|
val -= 0x40;
|
|
if (val < 24)
|
|
ptr += 1;
|
|
else if (val == 24)
|
|
ptr += 1 + 1;
|
|
else if (val == 25)
|
|
ptr += 1 + 2;
|
|
else if (val == 26)
|
|
ptr += 1 + 4;
|
|
else if (val == 27)
|
|
ptr += 1 + 8;
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Something wrong with byte string tag");
|
|
|
|
cborErr(cbor_value_advance(&value));
|
|
return {ptr, len};
|
|
}
|
|
|
|
void GetCBORFloatArray(CborValue &value, std::vector<float> &v) {
|
|
if (GetCBORTag(value) != TagFloatLE)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Incorrect array type tag");
|
|
|
|
auto [ptr, len] = GetCBORByteString(value);
|
|
|
|
if (len % sizeof(float))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Size mismatch");
|
|
|
|
v.resize(len / sizeof(float));
|
|
memcpy(v.data(), ptr, len);
|
|
}
|
|
|
|
void GetCBORStringArray(CborValue &value, std::vector<std::string> &v) {
|
|
if (!cbor_value_is_array(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Array expected");
|
|
CborValue array_value;
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
while (!cbor_value_at_end(&array_value)) {
|
|
v.emplace_back(GetCBORString(array_value));
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
}
|
|
|
|
void GetCBORUInt64Array(CborValue &value, std::vector<uint64_t> &v) {
|
|
if (GetCBORTag(value) != TagUnsignedInt64BitLE)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Incorrect array type tag");
|
|
|
|
auto [ptr, len] = GetCBORByteString(value);
|
|
|
|
if (len % sizeof(uint64_t))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Size mismatch");
|
|
|
|
v.resize(len / sizeof(uint64_t));
|
|
memcpy(v.data(), ptr, len);
|
|
}
|
|
|
|
void GetCBORTypedArray(CompressedImage &v, CborValue &value) {
|
|
CborTag tag = GetCBORTag(value);
|
|
|
|
switch (tag) {
|
|
case TagFloatLE:
|
|
v.pixel_is_signed = true;
|
|
v.pixel_depth_bytes = sizeof(float);
|
|
v.pixel_is_float = true;
|
|
break;
|
|
case TagSignedInt8Bit:
|
|
v.pixel_is_signed = true;
|
|
v.pixel_depth_bytes = 1;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
case TagUnsignedInt8Bit:
|
|
v.pixel_is_signed = false;
|
|
v.pixel_depth_bytes = 1;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
case TagSignedInt16BitLE:
|
|
v.pixel_is_signed = true;
|
|
v.pixel_depth_bytes = 2;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
case TagUnsignedInt16BitLE:
|
|
v.pixel_is_signed = false;
|
|
v.pixel_depth_bytes = 2;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
case TagSignedInt32BitLE:
|
|
v.pixel_is_signed = true;
|
|
v.pixel_depth_bytes = 4;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
case TagUnsignedInt32BitLE:
|
|
v.pixel_is_signed = false;
|
|
v.pixel_depth_bytes = 4;
|
|
v.pixel_is_float = false;
|
|
break;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Only int/uint arrays of 1, 2, 4 bytes per element allowed");
|
|
}
|
|
|
|
if (cbor_value_is_tag(&value)) {
|
|
if (GetCBORTag(value) != TagDECTRISCompression)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Unsupported tag");
|
|
|
|
if (GetCBORArrayLen(value) != 3)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Expected 3 element array");
|
|
|
|
CborValue array_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
|
|
auto algorithm_text = GetCBORString(array_value);
|
|
if (algorithm_text == "bslz4")
|
|
v.algorithm = CompressionAlgorithm::BSHUF_LZ4;
|
|
else if (algorithm_text == "bszstd")
|
|
v.algorithm = CompressionAlgorithm::BSHUF_ZSTD;
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Unsupported compression algorithm");
|
|
|
|
v.pixel_depth_bytes = GetCBORUInt(array_value);
|
|
auto ret = GetCBORByteString(array_value);
|
|
v.data = ret.first;
|
|
v.size = ret.second;
|
|
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
|
|
} else if (cbor_value_is_byte_string(&value)) {
|
|
v.algorithm = CompressionAlgorithm::NO_COMPRESSION;
|
|
auto ret = GetCBORByteString(value);
|
|
v.data = ret.first;
|
|
v.size = ret.second;
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Byte string or compressed array expected");
|
|
}
|
|
|
|
void GetCBORMultidimTypedArray(CompressedImage &v, CborValue &value) {
|
|
|
|
if (GetCBORTag(value) != TagMultiDimArray)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Multidim array expected");
|
|
|
|
if (GetCBORArrayLen(value) != 2)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "2 element array expected");
|
|
|
|
CborValue array_value;
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
|
|
auto [x, y] = GetCBORDimension2DArray(array_value);
|
|
v.xpixel = x;
|
|
v.ypixel = y;
|
|
|
|
GetCBORTypedArray(v, array_value);
|
|
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
}
|
|
|
|
void CheckMagicNumber(CborValue &v) {
|
|
if (GetCBORUInt(v) != user_data_magic_number)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Inconsistency between Jungfraujoch server and writer");
|
|
}
|
|
|
|
void ProcessImageData(CborValue &value, CompressedImage &image) {
|
|
if (!cbor_value_is_map(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Map expected");
|
|
|
|
if (GetCBORMapLen(value) != 1)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Single channel images only supported at the moment");
|
|
|
|
CborValue map_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
|
|
image.channel = GetCBORString(map_value);
|
|
GetCBORMultidimTypedArray(image, map_value);
|
|
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
// Data message
|
|
SpotToSave GetSpot(CborValue &value) {
|
|
SpotToSave s{};
|
|
CborValue map_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
if (key == "x")
|
|
s.x = GetCBORFloat(map_value);
|
|
else if (key == "y")
|
|
s.y = GetCBORFloat(map_value);
|
|
else if (key == "I")
|
|
s.intensity = GetCBORFloat(map_value);
|
|
else if (key == "indexed")
|
|
s.indexed = GetCBORBool(map_value);
|
|
else
|
|
cbor_value_advance(&map_value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
return s;
|
|
}
|
|
|
|
void GetCBORSpots(DataMessage &message, CborValue &value) {
|
|
size_t array_len = GetCBORArrayLen(value);
|
|
CborValue array_value;
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
for (int i = 0; i < array_len; i++)
|
|
message.spots.push_back(GetSpot(array_value));
|
|
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
}
|
|
|
|
CBORImageType DecodeType(CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Message empty");
|
|
|
|
if (GetCBORString(value) != "type")
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "First CBOR entry must by type");
|
|
|
|
if (cbor_value_at_end(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Message empty");
|
|
|
|
auto type_str = GetCBORString(value);
|
|
if (type_str == "start")
|
|
return CBORImageType::START;
|
|
if (type_str == "end")
|
|
return CBORImageType::END;
|
|
if (type_str == "image")
|
|
return CBORImageType::IMAGE;
|
|
if (type_str == "calibration")
|
|
return CBORImageType::CALIBRATION;
|
|
if (type_str == "metadata")
|
|
return CBORImageType::METADATA;
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Unknown message type");
|
|
}
|
|
|
|
// Data message
|
|
ROIMessage ProcessROIElement(CborValue &value) {
|
|
ROIMessage msg{};
|
|
|
|
// value
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
|
|
if (key == "sum")
|
|
msg.sum = GetCBORInt(map_value);
|
|
else if (key == "sum_square")
|
|
msg.sum_square = GetCBORUInt(map_value);
|
|
else if (key == "max_count")
|
|
msg.max_count = GetCBORInt(map_value);
|
|
else if (key == "pixels")
|
|
msg.pixels = GetCBORUInt(map_value);
|
|
else
|
|
cbor_value_advance(&map_value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
return msg;
|
|
}
|
|
|
|
void ProcessROIElementMap(DataMessage &message, CborValue &value) {
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
// key
|
|
const std::string roi_name = GetCBORString(map_value);
|
|
// value
|
|
message.roi[roi_name] = ProcessROIElement(map_value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
|
|
bool ProcessDataMessageElement(DataMessage &message, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
|
|
const auto key = GetCBORString(value);
|
|
try {
|
|
if (key == "image_id")
|
|
message.number = GetCBORInt(value);
|
|
else if (key == "original_image_id")
|
|
message.original_number = GetCBORInt(value);
|
|
else if (key == "data")
|
|
ProcessImageData(value, message.image);
|
|
else if (key == "series_unique_id")
|
|
message.run_name = GetCBORString(value);
|
|
else if (key == "series_id")
|
|
message.run_number = GetCBORUInt(value);
|
|
else if (key == "real_time") {
|
|
auto r = GetRational(value);
|
|
message.exptime = r.first;
|
|
message.exptime_base = r.second;
|
|
} else if (key == "start_time") {
|
|
auto r = GetRational(value);
|
|
message.timestamp = r.first;
|
|
message.timestamp_base = r.second;
|
|
} else if (key == "user_data") {
|
|
const std::string s = GetCBORString(value);
|
|
try {
|
|
message.user_data = nlohmann::json::parse(s);
|
|
} catch (...) {
|
|
message.user_data = s;
|
|
}
|
|
} else if (key == "spots")
|
|
GetCBORSpots(message, value);
|
|
else if (key == "spot_count_in_rings")
|
|
message.spot_count_in_rings = GetCBORUInt(value);
|
|
else if (key == "az_int_profile")
|
|
GetCBORFloatArray(value, message.az_int_profile);
|
|
else if (key == "indexing_result")
|
|
message.indexing_result = GetCBORBool(value);
|
|
else if (key == "indexing_lattice")
|
|
GetCBORFloatArray(value, message.indexing_lattice);
|
|
else if (key == "indexing_unit_cell")
|
|
message.indexing_unit_cell = ProcessUnitCellElement(value);
|
|
else if (key == "jf_info")
|
|
message.jf_info = GetCBORUInt(value) & UINT32_MAX;
|
|
else if (key == "receiver_aq_dev_delay")
|
|
message.receiver_aq_dev_delay = GetCBORInt(value);
|
|
else if (key == "receiver_free_send_buf")
|
|
message.receiver_free_send_buf = GetCBORInt(value);
|
|
else if (key == "storage_cell")
|
|
message.storage_cell = GetCBORUInt(value) & UINT32_MAX;
|
|
else if (key == "xfel_pulse_id")
|
|
message.xfel_pulse_id = GetCBORUInt(value);
|
|
else if (key == "xfel_event_code")
|
|
message.xfel_event_code = GetCBORUInt(value);
|
|
else if (key == "saturated_pixel_count")
|
|
message.saturated_pixel_count = GetCBORUInt(value);
|
|
else if (key == "error_pixel_count")
|
|
message.error_pixel_count = GetCBORUInt(value);
|
|
else if (key == "strong_pixel_count")
|
|
message.strong_pixel_count = GetCBORUInt(value);
|
|
else if (key == "min_viable_pixel_value")
|
|
message.min_viable_pixel_value = GetCBORInt(value);
|
|
else if (key == "max_viable_pixel_value")
|
|
message.max_viable_pixel_value = GetCBORInt(value);
|
|
else if (key == "data_collection_efficiency")
|
|
message.image_collection_efficiency = GetCBORFloat(value);
|
|
else if (key == "packets_expected")
|
|
message.packets_expected = GetCBORUInt(value);
|
|
else if (key == "packets_received")
|
|
message.packets_received = GetCBORUInt(value);
|
|
else if (key == "bkg_estimate")
|
|
message.bkg_estimate = GetCBORFloat(value);
|
|
else if (key == "adu_histogram")
|
|
GetCBORUInt64Array(value, message.adu_histogram);
|
|
else if (key == "roi_integrals")
|
|
ProcessROIElementMap(message, value);
|
|
else {
|
|
if (cbor_value_is_tag(&value))
|
|
cbor_value_advance(&value);
|
|
cbor_value_advance(&value);
|
|
}
|
|
return true;
|
|
} catch (const JFJochException &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Error processing image message, key " + key + ":" + e.what());
|
|
}
|
|
}
|
|
|
|
bool ProcessMetadataImagesElement(MetadataMessage &metadata, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
if (!cbor_value_is_map(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "images@metadata must be map");
|
|
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
|
|
DataMessage message{};
|
|
while (ProcessDataMessageElement(message, map_value)) {}
|
|
metadata.images.push_back(std::move(message));
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ProcessMetadataMessageElement(MetadataMessage &metadata, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
|
|
const auto key = GetCBORString(value);
|
|
try {
|
|
if (key == "series_unique_id")
|
|
metadata.run_name = GetCBORString(value);
|
|
else if (key == "series_id")
|
|
metadata.run_number = GetCBORUInt(value);
|
|
else if (key == "images") {
|
|
if (!cbor_value_is_array(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "images@metadata must be array");
|
|
|
|
CborValue array_value;
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
while (ProcessMetadataImagesElement(metadata, array_value)) {}
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
} else {
|
|
if (cbor_value_is_tag(&value))
|
|
cbor_value_advance(&value);
|
|
cbor_value_advance(&value);
|
|
}
|
|
return true;
|
|
} catch (const JFJochException &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Error processing image message, key " + key + ":" + e.what());
|
|
}
|
|
}
|
|
|
|
// Start message
|
|
void ProcessAxis(CborValue &value, float v[3]) {
|
|
if (GetCBORArrayLen(value) != 3)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Array with 3 floats expected");
|
|
CborValue array_value;
|
|
cborErr(cbor_value_enter_container(&value, &array_value));
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
v[i] = GetCBORFloat(array_value);
|
|
cborErr(cbor_value_leave_container(&value, &array_value));
|
|
}
|
|
|
|
GoniometerAxis ProcessGoniometerOmega(std::string &name, CborValue &value) {
|
|
GoniometerAxis ret{};
|
|
ret.name = name;
|
|
|
|
CborValue map_value;
|
|
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
if (key == "increment")
|
|
ret.increment = GetCBORFloat(map_value);
|
|
else if (key == "start")
|
|
ret.start = GetCBORFloat(map_value);
|
|
else
|
|
cbor_value_advance(&value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ProcessGoniometerMap(StartMessage &message, CborValue &value) {
|
|
CborValue map_value;
|
|
|
|
if (GetCBORMapLen(value) > 1)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Max one rotation angle allowed");
|
|
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
GoniometerAxis axis;
|
|
auto key = GetCBORString(map_value);
|
|
message.goniometer = ProcessGoniometerOmega(key, map_value);
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
void ProcessPixelMaskElement(StartMessage &message, CborValue &value) {
|
|
// For calibration and mask - image might be accessed later on - so it is saved in internal storage of the object
|
|
// It allows the original start message to be lost
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
CompressedImage image;
|
|
image.channel = key;
|
|
GetCBORMultidimTypedArray(image, map_value);
|
|
message.pixel_mask.emplace_back(std::move(image));
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
void ProcessStartUserData(StartMessage &message, CborValue &value) {
|
|
try {
|
|
const std::string s = GetCBORString(value);
|
|
auto j = nlohmann::json::parse(s);
|
|
if (!j.is_object())
|
|
return;
|
|
if (j.contains("file_prefix"))
|
|
message.file_prefix = j["file_prefix"];
|
|
if (j.contains("images_per_file"))
|
|
message.images_per_file = j["images_per_file"];
|
|
if (j.contains("user"))
|
|
message.user_data = j["user"];
|
|
if (j.contains("sample_name"))
|
|
message.sample_name = j["sample_name"];
|
|
if (j.contains("source_name"))
|
|
message.source_name = j["source_name"];
|
|
if (j.contains("source_type"))
|
|
message.source_type = j["source_type"];
|
|
if (j.contains("instrument_name"))
|
|
message.instrument_name = j["instrument_name"];
|
|
if (j.contains("total_flux"))
|
|
message.total_flux = j["total_flux"];
|
|
if (j.contains("attenuator_transmission"))
|
|
message.attenuator_transmission = j["attenuator_transmission"];
|
|
if (j.contains("rotation_axis") && j["rotation_axis"].is_array() &&
|
|
(j["rotation_axis"].size() == 3)) {
|
|
for (int i = 0; i < 3; i++)
|
|
message.rotation_axis[i] = j["rotation_axis"][i];
|
|
}
|
|
if (j.contains("space_group_number"))
|
|
message.space_group_number = j["space_group_number"];
|
|
if (j.contains("gain_file_names"))
|
|
message.gain_file_names = j["gain_file_names"];
|
|
if (j.contains("roi_names"))
|
|
message.roi_names = j["roi_names"];
|
|
if (j.contains("write_master_file"))
|
|
message.write_master_file = j["write_master_file"];
|
|
if (j.contains("data_reduction_factor_serialmx"))
|
|
message.data_reduction_factor_serialmx = j["data_reduction_factor_serialmx"];
|
|
if (j.contains("experiment_group"))
|
|
message.experiment_group = j["experiment_group"];
|
|
if (j.contains("jfjoch_release"))
|
|
message.jfjoch_release = j["jfjoch_release"];
|
|
if (j.contains("socket_number"))
|
|
message.socket_number = j["socket_number"];
|
|
if (j.contains("writer_notification_zmq_addr"))
|
|
message.writer_notification_zmq_addr = j["writer_notification_zmq_addr"];
|
|
if (j.contains("bit_depth_readout"))
|
|
message.bit_depth_readout = j["bit_depth_readout"];
|
|
if (j.contains("summation_mode"))
|
|
message.summation_mode = j["summation_mode"];
|
|
if (j.contains("overwrite"))
|
|
message.overwrite = j["overwrite"];
|
|
if (j.contains("xfel_pulse_id"))
|
|
message.overwrite = j["xfel_pulse_id"];
|
|
if (j.contains("hdf5_format_version"))
|
|
message.hdf5_format_version = j["hdf5_format_version"];
|
|
} catch (const std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Cannot parse user_data as valid JSON " + std::string(e.what()));
|
|
}
|
|
}
|
|
|
|
bool ProcessStartMessageElement(StartMessage &message, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
const auto key = GetCBORString(value);
|
|
try {
|
|
if (key == "magic_number")
|
|
CheckMagicNumber(value);
|
|
else if (key == "beam_center_x")
|
|
message.beam_center_x = GetCBORFloat(value);
|
|
else if (key == "beam_center_y")
|
|
message.beam_center_y = GetCBORFloat(value);
|
|
else if (key == "detector_distance")
|
|
message.detector_distance = GetCBORFloat(value);
|
|
else if (key == "number_of_images")
|
|
message.number_of_images = GetCBORUInt(value);
|
|
else if (key == "countrate_correction_enabled")
|
|
message.countrate_correction_enabled = GetCBORBool(value);
|
|
else if (key == "flatfield_enabled")
|
|
message.flatfield_enabled = GetCBORBool(value);
|
|
else if (key == "image_size_x")
|
|
message.image_size_x = GetCBORUInt(value);
|
|
else if (key == "image_size_y")
|
|
message.image_size_y = GetCBORUInt(value);
|
|
else if (key == "incident_energy")
|
|
message.incident_energy = GetCBORFloat(value);
|
|
else if (key == "incident_wavelength")
|
|
message.incident_wavelength = GetCBORFloat(value);
|
|
else if (key == "frame_time")
|
|
message.frame_time = GetCBORFloat(value);
|
|
else if (key == "count_time")
|
|
message.count_time = GetCBORFloat(value);
|
|
else if (key == "saturation_value")
|
|
message.saturation_value = GetCBORInt(value);
|
|
else if (key == "error_value")
|
|
message.error_value = GetCBORInt(value);
|
|
else if (key == "pixel_size_x")
|
|
message.pixel_size_x = GetCBORFloat(value);
|
|
else if (key == "pixel_size_y")
|
|
message.pixel_size_y = GetCBORFloat(value);
|
|
else if (key == "sensor_thickness")
|
|
message.sensor_thickness = GetCBORFloat(value);
|
|
else if (key == "sensor_material")
|
|
message.sensor_material = GetCBORString(value);
|
|
else if (key == "detector_description")
|
|
message.detector_description = GetCBORString(value);
|
|
else if (key == "detector_serial_number")
|
|
message.detector_serial_number = GetCBORString(value);
|
|
else if (key == "series_unique_id")
|
|
message.run_name = GetCBORString(value);
|
|
else if (key == "series_id")
|
|
message.run_number = GetCBORUInt(value);
|
|
else if (key == "pixel_mask")
|
|
ProcessPixelMaskElement(message, value);
|
|
else if (key == "channels")
|
|
GetCBORStringArray(value, message.channels);
|
|
else if (key == "detector_translation")
|
|
ProcessAxis(value, message.detector_translation);
|
|
else if (key == "goniometer")
|
|
ProcessGoniometerMap(message, value);
|
|
else if (key == "pixel_mask_enabled")
|
|
message.pixel_mask_enabled = GetCBORBool(value);
|
|
else if (key == "jungfrau_conversion_enabled")
|
|
message.jungfrau_conversion_enabled = GetCBORBool(value);
|
|
else if (key == "geometry_transformation_enabled")
|
|
message.geometry_transformation_enabled = GetCBORBool(value);
|
|
else if (key == "jungfrau_conversion_factor")
|
|
message.jungfrau_conversion_factor = GetCBORFloat(value);
|
|
else if (key == "arm_date")
|
|
message.arm_date = GetCBORDateTime(value);
|
|
else if (key == "user_data")
|
|
ProcessStartUserData(message, value);
|
|
else if (key == "unit_cell")
|
|
message.unit_cell = ProcessUnitCellElement(value);
|
|
else if (key == "max_spot_count")
|
|
message.max_spot_count = GetCBORUInt(value);
|
|
else if (key == "image_dtype") {
|
|
auto val = GetCBORString(value);
|
|
if (val == "uint8") {
|
|
message.pixel_signed = false;
|
|
message.bit_depth_image = 8;
|
|
} else if (val == "int8") {
|
|
message.pixel_signed = true;
|
|
message.bit_depth_image = 8;
|
|
} else if (val == "uint16") {
|
|
message.pixel_signed = false;
|
|
message.bit_depth_image = 16;
|
|
} else if (val == "int16") {
|
|
message.pixel_signed = true;
|
|
message.bit_depth_image = 16;
|
|
} else if (val == "uint32") {
|
|
message.pixel_signed = false;
|
|
message.bit_depth_image = 32;
|
|
} else if (val == "int32") {
|
|
message.pixel_signed = true;
|
|
message.bit_depth_image = 32;
|
|
}
|
|
} else if (key == "az_int_bin_number")
|
|
message.az_int_bin_number = GetCBORInt(value);
|
|
else if (key == "az_int_bin_to_q")
|
|
GetCBORFloatArray(value, message.az_int_bin_to_q);
|
|
else if (key == "summation")
|
|
message.summation = GetCBORUInt(value);
|
|
else if (key == "storage_cell_number")
|
|
message.storage_cell_number = GetCBORUInt(value);
|
|
else if (key == "storage_cell_delay")
|
|
message.storage_cell_delay_ns = GetRational(value).first;
|
|
else {
|
|
if (cbor_value_is_tag(&value))
|
|
cbor_value_advance(&value);
|
|
cbor_value_advance(&value);
|
|
}
|
|
return true;
|
|
} catch (const JFJochException &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Error processing start message, key " + key + ":" + e.what());
|
|
}
|
|
}
|
|
|
|
// Calibration
|
|
bool ProcessCalibration(CompressedImage &message, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
else {
|
|
auto key = GetCBORString(value);
|
|
try {
|
|
if (key == "data") {
|
|
ProcessImageData(value, message);
|
|
} else {
|
|
if (cbor_value_is_tag(&value))
|
|
cbor_value_advance(&value);
|
|
cbor_value_advance(&value);
|
|
}
|
|
return true;
|
|
} catch (const JFJochException &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Error processing calibration message, key " + key + ":" + e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// EndMessage
|
|
void ProcessRadIntResultElement(EndMessage &message, CborValue &value) {
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
std::vector<float> val;
|
|
GetCBORFloatArray(map_value, val);
|
|
message.az_int_result[key] = val;
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
void ProcessADUHistogramElement(EndMessage &message, CborValue &value) {
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
while (!cbor_value_at_end(&map_value)) {
|
|
auto key = GetCBORString(map_value);
|
|
std::vector<uint64_t> val;
|
|
GetCBORUInt64Array(map_value, val);
|
|
message.adu_histogram[key] = val;
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
}
|
|
|
|
bool ProcessEndMessageElement(EndMessage &message, CborValue &value) {
|
|
if (cbor_value_at_end(&value))
|
|
return false;
|
|
|
|
auto key = GetCBORString(value);
|
|
try {
|
|
if (key == "end_date")
|
|
message.end_date = GetCBORString(value);
|
|
else if (key == "series_unique_id")
|
|
message.run_name = GetCBORString(value);
|
|
else if (key == "series_id")
|
|
message.run_number = GetCBORUInt(value);
|
|
else if (key == "max_image_number")
|
|
message.max_image_number = GetCBORUInt(value);
|
|
else if (key == "images_collected")
|
|
message.images_collected_count = GetCBORUInt(value);
|
|
else if (key == "images_sent_to_write")
|
|
message.images_sent_to_write_count = GetCBORUInt(value);
|
|
else if (key == "max_receiver_delay")
|
|
message.max_receiver_delay = GetCBORUInt(value);
|
|
else if (key == "data_collection_efficiency")
|
|
message.efficiency = GetCBORFloat(value);
|
|
else if (key == "az_int_result")
|
|
ProcessRadIntResultElement(message, value);
|
|
else if (key == "adu_histogram")
|
|
ProcessADUHistogramElement(message, value);
|
|
else if (key == "adu_histogram_bin_width")
|
|
message.adu_histogram_bin_width = GetCBORUInt(value);
|
|
else if (key == "bkg_estimate")
|
|
message.bkg_estimate = GetCBORFloat(value);
|
|
else if (key == "indexing_rate")
|
|
message.indexing_rate = GetCBORFloat(value);
|
|
else
|
|
cbor_value_advance(&value);
|
|
return true;
|
|
} catch (const JFJochException &e) {
|
|
throw JFJochException(JFJochExceptionCategory::CBORError,
|
|
"Error processing end message, key " + key + ":" + e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<CBORStream2DeserializerOutput> CBORStream2Deserialize(const uint8_t *msg, size_t msg_size) {
|
|
CborParser parser;
|
|
CborValue value;
|
|
|
|
cborErr(cbor_parser_init(msg, msg_size, 0, &parser, &value));
|
|
|
|
if (GetCBORTag(value) != CborSignatureTag)
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "CBOR must start with dedicated tag");
|
|
|
|
if (!cbor_value_is_map(&value))
|
|
throw JFJochException(JFJochExceptionCategory::CBORError, "Serialized frame must be map in top level");
|
|
|
|
auto ret = std::make_shared<CBORStream2DeserializerOutput>();
|
|
|
|
CborValue map_value;
|
|
cborErr(cbor_value_enter_container(&value, &map_value));
|
|
ret->msg_type = DecodeType(map_value);
|
|
switch (ret->msg_type) {
|
|
case CBORImageType::IMAGE:
|
|
ret->data_message = DataMessage();
|
|
while (ProcessDataMessageElement(*ret->data_message, map_value)) {}
|
|
break;
|
|
case CBORImageType::CALIBRATION: {
|
|
ret->calibration = CompressedImage();
|
|
while (ProcessCalibration(*ret->calibration, map_value)) {}
|
|
break;
|
|
}
|
|
case CBORImageType::START: {
|
|
ret->start_message = StartMessage();
|
|
while (ProcessStartMessageElement(*ret->start_message, map_value)) {}
|
|
break;
|
|
}
|
|
case CBORImageType::END:
|
|
ret->end_message = EndMessage();
|
|
while (ProcessEndMessageElement(*ret->end_message, map_value)) {}
|
|
break;
|
|
case CBORImageType::METADATA:
|
|
ret->metadata = MetadataMessage();
|
|
while (ProcessMetadataMessageElement(*ret->metadata, map_value)) {}
|
|
break;
|
|
case CBORImageType::NONE:
|
|
break;
|
|
}
|
|
cborErr(cbor_value_leave_container(&value, &map_value));
|
|
return ret;
|
|
}
|