428 lines
18 KiB
C++
428 lines
18 KiB
C++
// Copyright (2019-2023) Paul Scherrer Institute
|
|
|
|
#include "JFJochBrokerParser.h"
|
|
#include "../common/NetworkAddressConvert.h"
|
|
#include "../frame_serialize/ZMQStream2Pusher.h"
|
|
#include "../frame_serialize/DumpCBORToFilePusher.h"
|
|
|
|
inline bool CHECK_ARRAY(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_array())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be array");
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
inline bool CHECK_OBJECT(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_object())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be object");
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
inline int64_t GET_I64(const nlohmann::json &j, const std::string& tag, int64_t def) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_number_integer())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer");
|
|
try {
|
|
return j[tag].get<int64_t>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
return def;
|
|
}
|
|
|
|
inline int32_t GET_I32(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_number_integer())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer");
|
|
try {
|
|
return j[tag].get<int32_t>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory");
|
|
}
|
|
|
|
inline int32_t GET_I32(const nlohmann::json &j, const std::string& tag, int32_t def) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_number_integer())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer");
|
|
try {
|
|
return j[tag].get<int32_t>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
return def;
|
|
}
|
|
|
|
inline float GET_FLOAT(const nlohmann::json &j, const std::string& tag, float def) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_number())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be float");
|
|
try {
|
|
return j[tag].get<float>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
return def;
|
|
}
|
|
|
|
inline bool GET_BOOL(const nlohmann::json &j, const std::string& tag, bool def) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_boolean())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer");
|
|
try {
|
|
return j[tag].get<bool>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
return def;
|
|
}
|
|
|
|
inline std::string GET_STR(const nlohmann::json &j, const std::string& tag, const std::string& def) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_string())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be string");
|
|
try {
|
|
return j[tag].get<std::string>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
return def;
|
|
}
|
|
|
|
inline std::string GET_STR(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_string())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be string");
|
|
try {
|
|
return j[tag].get<std::string>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory");
|
|
}
|
|
|
|
inline int64_t GET_I64(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_number_integer())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be integer");
|
|
try {
|
|
return j[tag].get<int64_t>();
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory");
|
|
}
|
|
|
|
inline std::vector<std::string> GET_STR_ARR(const nlohmann::json &j, const std::string& tag) {
|
|
std::vector<std::string> ret;
|
|
|
|
if (CHECK_ARRAY(j, tag)) {
|
|
try {
|
|
for (const auto &iter: j[tag])
|
|
ret.push_back(iter.get<std::string>());
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
inline std::vector<std::int64_t> GET_I64_ARR(const nlohmann::json &j, const std::string& tag) {
|
|
std::vector<std::int64_t> ret;
|
|
|
|
if (CHECK_ARRAY(j, tag)) {
|
|
try {
|
|
for (const auto &iter: j[tag])
|
|
ret.push_back(iter.get<int64_t>());
|
|
} catch (std::exception &e) {
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
DetectorModuleGeometry::Direction GET_DIRECTION(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
if (!j[tag].is_string())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be string");
|
|
if (j[tag] == "Xp")
|
|
return DetectorModuleGeometry::Direction::Xpos;
|
|
if (j[tag] == "Xn")
|
|
return DetectorModuleGeometry::Direction::Xneg;
|
|
if (j[tag] == "Yp")
|
|
return DetectorModuleGeometry::Direction::Ypos;
|
|
if (j[tag] == "Yn")
|
|
return DetectorModuleGeometry::Direction::Yneg;
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Allowed values for direction: Xp, Xn, Yp, Yn");
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory");
|
|
}
|
|
|
|
inline int64_t TimeToUs(const std::string &unit) {
|
|
if ((unit == "us") || unit.empty()) return 1L;
|
|
else if (unit == "ms") return 1000L;
|
|
else if ((unit == "s") || (unit == "sec")) return 1000*1000L;
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::WrongUnit,
|
|
"Only us, ms and s (or sec) are viable units for time");
|
|
}
|
|
|
|
inline std::chrono::microseconds GET_TIME(const nlohmann::json &j, const std::string& tag) {
|
|
if (j.contains(tag)) {
|
|
// If no units provided for time, this is always microsecond
|
|
if (j[tag].is_number())
|
|
return std::chrono::microseconds (std::lround(j[tag].get<double>() * 1000.0 * 1000.0));
|
|
else if (j[tag].is_string()) {
|
|
std::string::size_type pos;
|
|
auto text = j[tag].get<std::string>();
|
|
|
|
// Check if floating point
|
|
pos = text.find_first_of('.');
|
|
if (pos != std::string::npos)
|
|
throw JFJochException(JFJochExceptionCategory::WrongNumber,
|
|
"Time must be provided as <integer> {s|ms|us|Hz} - no floating point allowed");
|
|
|
|
// Convert integer part
|
|
int64_t val = std::stol(text, &pos);
|
|
if (pos == 0)
|
|
throw JFJochException(JFJochExceptionCategory::WrongNumber,
|
|
"Time must be provided as <integer> {s|ms|us|Hz}");
|
|
|
|
pos = text.find_first_not_of(' ', pos);
|
|
if (pos != std::string::npos) {
|
|
if (text.substr(pos) == "Hz")
|
|
val = 1000*1000L / val;
|
|
else
|
|
val *= TimeToUs(text.substr(pos));
|
|
}
|
|
return std::chrono::microseconds(val);
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " must be number in seconds or string with time units s|ms|us");
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory");
|
|
}
|
|
|
|
DetectorGeometry ParseStandardDetectorGeometry(const nlohmann::json &j) {
|
|
int32_t nmodules = GET_I64(j, "nmodules");
|
|
int32_t gap_x = GET_I64(j, "gap_x", 8);
|
|
int32_t gap_y = GET_I64(j, "gap_y", 36);
|
|
int32_t h_stacking = GET_I64(j, "horizontal_stacking", 2);
|
|
bool mirror_y = GET_BOOL(j, "mirror_y", true);
|
|
return {nmodules, h_stacking, gap_x, gap_y, mirror_y};
|
|
}
|
|
|
|
DetectorGeometry ParseCustomDetectorGeometry(const nlohmann::json &j, bool mirror_y) {
|
|
std::vector<DetectorModuleGeometry> modules;
|
|
for (const auto &iter: j) {
|
|
if (!iter.is_object())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"custom_geometry element must be JSON object");
|
|
int64_t x0 = GET_I64(iter, "x0");
|
|
int64_t y0 = GET_I64(iter, "y0");
|
|
auto fast = GET_DIRECTION(iter, "fast_axis");
|
|
auto slow = GET_DIRECTION(iter, "slow_axis");
|
|
modules.emplace_back(x0, y0, fast, slow);
|
|
}
|
|
return {modules, mirror_y};
|
|
}
|
|
|
|
DetectorGeometry ParseDetectorGeometry(const nlohmann::json &j) {
|
|
if (CHECK_OBJECT(j, "standard_geometry"))
|
|
return ParseStandardDetectorGeometry(j["standard_geometry"]);
|
|
else if (CHECK_ARRAY(j, "custom_geometry")) {
|
|
return ParseCustomDetectorGeometry(j["custom_geometry"], GET_BOOL(j, "custom_geometry_mirror_y", false));
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"It is compulsory to have either standard_detector or custom_detector declared.");
|
|
}
|
|
|
|
DetectorSetup ParseDetectorSetup(const nlohmann::json &j) {
|
|
if (!j.is_object())
|
|
throw JFJochException(JFJochExceptionCategory::JSON, "Expecting JSON object for detector setup");
|
|
|
|
DetectorGeometry geom = ParseDetectorGeometry(j);
|
|
|
|
std::string description = GET_STR(j, "description");
|
|
auto module_hostname = GET_STR_ARR(j, "module_hostname");
|
|
|
|
DetectorType detector_type;
|
|
const std::string detector_type_str = GET_STR(j, "type", "jungfrau");
|
|
if (detector_type_str == "jungfrau")
|
|
detector_type = DetectorType::JUNGFRAU;
|
|
else if (detector_type_str == "eiger")
|
|
detector_type = DetectorType::EIGER;
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::JSON, "type must be \"jungfrau\" or \"eiger\" ");
|
|
DetectorSetup setup(geom, detector_type, description, module_hostname);
|
|
if (detector_type == DetectorType::JUNGFRAU) {
|
|
auto gain_files = GET_STR_ARR(j, "gain_files");
|
|
if (!gain_files.empty())
|
|
setup.LoadGain(gain_files);
|
|
} else if (detector_type == DetectorType::EIGER) {
|
|
auto trim_files = GET_STR_ARR(j, "trim_files");
|
|
if (!trim_files.empty())
|
|
setup.SetTrimFiles(trim_files);
|
|
}
|
|
|
|
if (j.contains("high_voltage"))
|
|
setup.HighVoltage(GET_I32(j,"high_voltage"));
|
|
|
|
setup.UDPInterfaceCount(GET_I64(j, "udp_interface_count", 2))
|
|
.SensorThickness_um(GET_FLOAT(j, "sensor_thickness_um", 320.0f))
|
|
.PixelSize_um(GET_FLOAT(j, "pixel_size_um", 75.0f))
|
|
.SensorMaterial(GET_STR(j, "sensor_material", "Si"))
|
|
.SerialNumber(GET_STR(j, "serial_number",""));
|
|
|
|
if (j.contains("tx_delay"))
|
|
setup.TxDelay(GET_I64_ARR(j, "tx_delay"));
|
|
|
|
return setup;
|
|
}
|
|
|
|
void ParseDetectorSetup(const nlohmann::json &j, const std::string& tag, JFJochBrokerHttp& broker) {
|
|
if (CHECK_ARRAY(j, tag)) {
|
|
for (const auto &iter : j[tag])
|
|
broker.AddDetectorSetup(ParseDetectorSetup(iter));
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::JSON, "Detector setup not found");
|
|
}
|
|
|
|
void ParseImagePusher(const nlohmann::json &input, std::unique_ptr<ImagePusher> &image_pusher) {
|
|
std::string pusher_type = ParseString(input, "stream_type", "zmq");
|
|
if (pusher_type == "zmq") {
|
|
int32_t zmq_send_watermark = ParseInt32(input, "zmq_send_watermark", 100);
|
|
int32_t zmq_send_buffer_size = ParseInt32(input, "zmq_send_buffer_size", -1);
|
|
|
|
auto tmp = std::make_unique<ZMQStream2Pusher>(ParseStringArray(input, "zmq_image_addr"),
|
|
zmq_send_watermark,
|
|
zmq_send_buffer_size);
|
|
|
|
std::string preview_addr = ParseString(input, "zmq_preview_addr", "");
|
|
if (!preview_addr.empty())
|
|
tmp->PreviewSocket(preview_addr);
|
|
|
|
if (input.contains("zmq_preview_period"))
|
|
tmp->PreviewCounterPeriod(GET_TIME(input, "zmq_preview_period"));
|
|
|
|
std::string writer_notification_addr = ParseString(input, "zmq_writer_notification_addr", "");
|
|
if (!writer_notification_addr.empty())
|
|
tmp->WriterNotificationSocket(writer_notification_addr);
|
|
|
|
image_pusher = std::move(tmp);
|
|
} else if (pusher_type == "dump_cbor") {
|
|
image_pusher = std::make_unique<DumpCBORToFilePusher>();
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"stream_type allowed: zmq (default), dump_cbor");
|
|
}
|
|
|
|
void ParseFacilityConfiguration(const nlohmann::json &input, const std::string& tag, DiffractionExperiment &experiment) {
|
|
if (CHECK_OBJECT(input, tag)) {
|
|
auto j = input[tag];
|
|
experiment.SourceName(GET_STR(j, "source_name"));
|
|
experiment.SourceNameShort(GET_STR(j, "source_name_short"));
|
|
experiment.SourceType(GET_STR(j, "source_type", ""));
|
|
|
|
experiment.InstrumentName(GET_STR(j, "instrument_name"));
|
|
experiment.InstrumentNameShort(GET_STR(j, "instrument_name_short"));
|
|
|
|
experiment.PulsedSource(GET_BOOL(j, "pulsed_source", false));
|
|
|
|
if (j.contains("rotation_axis")) {
|
|
if (j["rotation_axis"].is_array() && (j["rotation_axis"].size() == 3))
|
|
experiment.DefaultRotationAxis(Coord(j["rotation_axis"][0].get<float>(),
|
|
j["rotation_axis"][1].get<float>(),
|
|
j["rotation_axis"][2].get<float>()));
|
|
else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"rotation_axis must be float array of 3");
|
|
}
|
|
|
|
if (j.contains("pedestal_g0_frames"))
|
|
experiment.PedestalG0Frames(GET_I64(j, "pedestal_g0_frames"));
|
|
if (j.contains("pedestal_g1_frames"))
|
|
experiment.PedestalG1Frames(GET_I64(j, "pedestal_g1_frames"));
|
|
if (j.contains("pedestal_g2_frames"))
|
|
experiment.PedestalG2Frames(GET_I64(j, "pedestal_g2_frames"));
|
|
if (j.contains("detector_trigger_delay"))
|
|
experiment.DetectorDelay(GET_TIME(j, "detector_trigger_delay"));
|
|
|
|
experiment.FrameTime(GET_TIME(j, "frame_time"), GET_TIME(j, "count_time"));
|
|
|
|
experiment.UseInternalPacketGenerator(GET_BOOL(j, "internal_frame_generator", false));
|
|
if (experiment.IsUsingInternalPacketGen())
|
|
experiment.ConversionOnFPGA(false);
|
|
|
|
experiment.IPv4BaseAddr(GET_STR(j, "detector_ipv4"));
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::JSON, "Default configuration not found");
|
|
}
|
|
|
|
void ParseAcquisitionDeviceGroup(const nlohmann::json &input, const std::string& tag,
|
|
AcquisitionDeviceGroup &aq_devices) {
|
|
if (CHECK_OBJECT(input, tag)) {
|
|
const auto &j = input[tag];
|
|
|
|
std::string dev_type = GET_STR(j, "type");
|
|
|
|
if (dev_type == "pcie") {
|
|
std::vector<std::string> devices = GET_STR_ARR(j, "devices");
|
|
if (devices.empty())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"At least one PCIe device must be provided");
|
|
for (auto &d: devices)
|
|
aq_devices.AddPCIeDevice(d);
|
|
} else if (dev_type == "hls") {
|
|
int64_t buffer_size = GET_I64(j, "buffer_size", 1024);
|
|
|
|
if ((buffer_size < 16) || (buffer_size > 65536))
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Buffer size must be in range 16-65536");
|
|
|
|
aq_devices.AddHLSDevice(buffer_size);
|
|
} else
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Device type unknown");
|
|
|
|
aq_devices.SetDefaultDataSource(AcquisitionDeviceSource::NETWORK);
|
|
|
|
std::vector<std::string> ipv4_addr = GET_STR_ARR(j, "ipv4_addr");
|
|
if (ipv4_addr.size() != aq_devices.size())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Mismatch between number of devices and number of IPv4 addresses");
|
|
for (int i = 0; i < ipv4_addr.size(); i++)
|
|
aq_devices[i].SetIPv4Address(IPv4AddressFromStr(ipv4_addr[i]));
|
|
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> ParseStringArray(const nlohmann::json &input, const std::string& tag) {
|
|
return GET_STR_ARR(input, tag);
|
|
}
|
|
|
|
std::string ParseString(const nlohmann::json &input, const std::string& tag, const std::string &def) {
|
|
return GET_STR(input, tag, def);
|
|
}
|
|
|
|
int64_t ParseInt64(const nlohmann::json &input, const std::string& tag, int64_t def) {
|
|
return GET_I64(input, tag, def);
|
|
}
|
|
|
|
int32_t ParseInt32(const nlohmann::json &input, const std::string& tag, int32_t def) {
|
|
return GET_I32(input, tag, def);
|
|
}
|