// Copyright (2019-2023) Paul Scherrer Institute #include "JFJochBrokerParser.h" #include "../common/NetworkAddressConvert.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(); } 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(); } 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(); } 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(); } 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(); } 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(); } 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(); } 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(); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + " is compulsory"); } inline std::vector GET_STR_ARR(const nlohmann::json &j, const std::string& tag) { std::vector ret; if (CHECK_ARRAY(j, tag)) { try { for (const auto &iter: j[tag]) ret.push_back(iter.get()); } catch (std::exception &e) { throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, tag + ": " + e.what()); } } return ret; } inline std::vector GET_I64_ARR(const nlohmann::json &j, const std::string& tag) { std::vector ret; if (CHECK_ARRAY(j, tag)) { try { for (const auto &iter: j[tag]) ret.push_back(iter.get()); } 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 (j[tag].is_number()) return std::chrono::microseconds (std::lround(j[tag].get() * 1000.0 * 1000.0)); else if (j[tag].is_string()) { std::string::size_type pos; auto text = j[tag].get(); // Check if floating point pos = text.find_first_of('.'); if (pos != std::string::npos) throw JFJochException(JFJochExceptionCategory::WrongNumber, "Time must be provided as {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 {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 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")); 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 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.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("omega_axis")) { if (j["omega_axis"].is_array() && (j["omega_axis"].size() == 3)) experiment.DefaultOmegaAxis(Coord(j["omega_axis"][0].get(), j["omega_axis"][1].get(), j["omega_axis"][2].get())); else throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "omega_axis must be float array of 3"); } experiment.NeuralNetModelPath(GET_STR(j, "neural_net_model", "")); 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_us")) experiment.DetectorDelay(GET_TIME(j, "detector_trigger_delay_us")); experiment.FrameTime(GET_TIME(j, "frame_time_us"), GET_TIME(j, "count_time_us")); if (j.contains("preview_period_us")) experiment.PreviewPeriod(GET_TIME(j, "preview_period_us")); 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 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 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 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); }