From 1d04656dc8bbd660c3dd7abad5d19fe1e0d97559 Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Fri, 19 Jun 2026 09:28:25 +0200 Subject: [PATCH] api: serialize azimuthal ROI phi sector over OpenAPI and CBOR Expose the optional phi_min_deg / phi_max_deg azimuthal-sector bounds added to ROIAzimuthal through the wire formats. Both are optional, so older clients and streams (Q-only ROIs) remain valid. - jfjoch_api.yaml: optional phi_min_deg / phi_max_deg on roi_azimuthal; regenerated the cpp-pistache server model and the TS frontend client. - OpenAPIConvert: map phi both directions (set only when present). - CBOR start message: emit phi_min/phi_max only for a sector; on decode, absent phi means a full ring. - Documented the new optional CBOR fields; extended the CBOR round-trip test with a sector and a full-ring azimuthal ROI. Co-Authored-By: Claude Opus 4.8 --- broker/OpenAPIConvert.cpp | 15 +++++- broker/gen/model/Roi_azimuthal.cpp | 60 ++++++++++++++++++++- broker/gen/model/Roi_azimuthal.h | 22 +++++++- broker/jfjoch_api.yaml | 14 ++++- docs/CBOR.md | 2 +- frame_serialize/CBORStream2Deserializer.cpp | 3 ++ frame_serialize/CBORStream2Serializer.cpp | 5 ++ frontend/src/client/types.gen.ts | 12 ++++- frontend/src/client/zod.gen.ts | 6 ++- tests/CBORTest.cpp | 23 ++++++-- 10 files changed, 149 insertions(+), 13 deletions(-) diff --git a/broker/OpenAPIConvert.cpp b/broker/OpenAPIConvert.cpp index 87bf76e3..4dc07646 100644 --- a/broker/OpenAPIConvert.cpp +++ b/broker/OpenAPIConvert.cpp @@ -461,10 +461,17 @@ ROIDefinition Convert(const org::openapitools::server::model::Roi_definitions& i output.boxes.emplace_back(ROIBox(i.getName(), i.getMinXPxl(), i.getMaxXPxl(), i.getMinYPxl(), i.getMaxYPxl())); for (const auto &i: input.getCircle().getRois()) output.circles.emplace_back(ROICircle(i.getName(), i.getCenterXPxl(), i.getCenterYPxl(), i.getRadiusPxl())); - for (const auto &i: input.getAzim().getRois()) + for (const auto &i: input.getAzim().getRois()) { + std::optional phi_min, phi_max; + if (i.phiMinDegIsSet()) + phi_min = i.getPhiMinDeg(); + if (i.phiMaxDegIsSet()) + phi_max = i.getPhiMaxDeg(); output.azimuthal.emplace_back(ROIAzimuthal(i.getName(), (i.getQMaxRecipA() == 0.0) ? 0.0 : 2.0f * PI / i.getQMaxRecipA(), - (i.getQMinRecipA() == 0.0) ? 0.0 : 2.0f * PI / i.getQMinRecipA())); + (i.getQMinRecipA() == 0.0) ? 0.0 : 2.0f * PI / i.getQMinRecipA(), + phi_min, phi_max)); + } return output; } @@ -491,6 +498,10 @@ org::openapitools::server::model::Roi_azim_list Convert(const std::vector -/// ROI as Q-range (or resolution range) +/// ROI as Q-range (or resolution range), optionally restricted to an azimuthal-angle sector /// class Roi_azimuthal { @@ -73,6 +73,20 @@ public: /// float getQMaxRecipA() const; void setQMaxRecipA(float const value); + /// + /// Lower azimuthal-angle bound of the sector [degrees]. Optional: provide both phi_min_deg and phi_max_deg to restrict the ROI to an angular sector, or omit both for a full ring. A sector with phi_min_deg > phi_max_deg wraps across 0. + /// + float getPhiMinDeg() const; + void setPhiMinDeg(float const value); + bool phiMinDegIsSet() const; + void unsetPhi_min_deg(); + /// + /// Upper azimuthal-angle bound of the sector [degrees]. Optional; see phi_min_deg. + /// + float getPhiMaxDeg() const; + void setPhiMaxDeg(float const value); + bool phiMaxDegIsSet() const; + void unsetPhi_max_deg(); friend void to_json(nlohmann::json& j, const Roi_azimuthal& o); friend void from_json(const nlohmann::json& j, Roi_azimuthal& o); @@ -83,6 +97,10 @@ protected: float m_Q_max_recipA; + float m_Phi_min_deg; + bool m_Phi_min_degIsSet; + float m_Phi_max_deg; + bool m_Phi_max_degIsSet; }; diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 573288da..31a53a89 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -1682,7 +1682,7 @@ components: enum: ["WrongDAQState", "Other"] roi_azimuthal: type: object - description: "ROI as Q-range (or resolution range)" + description: "ROI as Q-range (or resolution range), optionally restricted to an azimuthal-angle sector" required: - name - q_min_recipA @@ -1702,6 +1702,18 @@ components: format: float minimum: 0.00001 description: "Maximum Q-range for the ROI" + phi_min_deg: + type: number + format: float + description: > + Lower azimuthal-angle bound of the sector [degrees]. Optional: provide both + phi_min_deg and phi_max_deg to restrict the ROI to an angular sector, or omit + both for a full ring. A sector with phi_min_deg > phi_max_deg wraps across 0. + phi_max_deg: + type: number + format: float + description: > + Upper azimuthal-angle bound of the sector [degrees]. Optional; see phi_min_deg. roi_circle: type: object description: "Circular ROI" diff --git a/docs/CBOR.md b/docs/CBOR.md index a82af4bc..cf25bc8c 100644 --- a/docs/CBOR.md +++ b/docs/CBOR.md @@ -96,7 +96,7 @@ There are minor differences at the moment: | - roi | Array(object) | ROI configurations; each element is one of: | | | | | type "box": xmin, xmax, ymin, ymax (numbers) | | | | | type "circle": r, x, y (numbers) | | -| | | type "azim": qmin, qmax (numbers) | | +| | | type "azim": qmin, qmax (numbers); optional phi_min, phi_max (numbers, deg) for an angular sector | | | - gain_file_names | Array(string) | Names of JUNGFRAU gain files used for the current detector | | | - write_master_file | bool | With multiple sockets, it selects which socket will provide master file | | | - write_images | bool | Write images in the HDF5 file (if false, will only write metadata) | | diff --git a/frame_serialize/CBORStream2Deserializer.cpp b/frame_serialize/CBORStream2Deserializer.cpp index 06241be0..4c01990c 100644 --- a/frame_serialize/CBORStream2Deserializer.cpp +++ b/frame_serialize/CBORStream2Deserializer.cpp @@ -1000,6 +1000,9 @@ namespace { cfg.type = ROIConfig::ROIType::Azim; cfg.azim.qmin = jr["qmin"]; cfg.azim.qmax = jr["qmax"]; + // Absent phi (older streams) means a full ring. + cfg.azim.phi_min = jr.value("phi_min", 0.0f); + cfg.azim.phi_max = jr.value("phi_max", 0.0f); message.rois.emplace_back(std::move(cfg)); } else continue; diff --git a/frame_serialize/CBORStream2Serializer.cpp b/frame_serialize/CBORStream2Serializer.cpp index dee8591f..3e361ef3 100644 --- a/frame_serialize/CBORStream2Serializer.cpp +++ b/frame_serialize/CBORStream2Serializer.cpp @@ -504,6 +504,11 @@ inline nlohmann::json CBOR_ENC_ROI_CONFIG(const std::vector &roi) { jr["type"] = "azim"; jr["qmin"] = r.azim.qmin; jr["qmax"] = r.azim.qmax; + // phi_min == phi_max means a full ring; only emit a sector. + if (r.azim.phi_min != r.azim.phi_max) { + jr["phi_min"] = r.azim.phi_min; + jr["phi_max"] = r.azim.phi_max; + } break; } j.push_back(jr); diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index e7b33b4f..a8b818e8 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -1073,7 +1073,7 @@ export type error_message = { }; /** - * ROI as Q-range (or resolution range) + * ROI as Q-range (or resolution range), optionally restricted to an azimuthal-angle sector */ export type roi_azimuthal = { /** @@ -1088,6 +1088,16 @@ export type roi_azimuthal = { * Maximum Q-range for the ROI */ q_max_recipA: number; + /** + * Lower azimuthal-angle bound of the sector [degrees]. Optional: provide both phi_min_deg and phi_max_deg to restrict the ROI to an angular sector, or omit both for a full ring. A sector with phi_min_deg > phi_max_deg wraps across 0. + * + */ + phi_min_deg?: number; + /** + * Upper azimuthal-angle bound of the sector [degrees]. Optional; see phi_min_deg. + * + */ + phi_max_deg?: number; }; /** diff --git a/frontend/src/client/zod.gen.ts b/frontend/src/client/zod.gen.ts index b3259a73..c6095998 100644 --- a/frontend/src/client/zod.gen.ts +++ b/frontend/src/client/zod.gen.ts @@ -424,12 +424,14 @@ export const zErrorMessage = z.object({ }); /** - * ROI as Q-range (or resolution range) + * ROI as Q-range (or resolution range), optionally restricted to an azimuthal-angle sector */ export const zRoiAzimuthal = z.object({ name: z.string().min(1), q_min_recipA: z.number().gte(0.00001), - q_max_recipA: z.number().gte(0.00001) + q_max_recipA: z.number().gte(0.00001), + phi_min_deg: z.number().optional(), + phi_max_deg: z.number().optional() }); /** diff --git a/tests/CBORTest.cpp b/tests/CBORTest.cpp index da3ecd21..35bbed6f 100644 --- a/tests/CBORTest.cpp +++ b/tests/CBORTest.cpp @@ -290,7 +290,19 @@ TEST_CASE("CBORSerialize_ROI", "[CBOR]") { .name = "roi3", .azim = ROIConfigAzim{ .qmin = 4.0, - .qmax = 5.0 + .qmax = 5.0, + .phi_min = 30.0f, + .phi_max = 90.0f + } + }, + ROIConfig{ + .type = ROIConfig::ROIType::Azim, + .name = "roi4", + .azim = ROIConfigAzim{ + .qmin = 1.0, + .qmax = 2.0, + .phi_min = 0.0f, + .phi_max = 0.0f } } } @@ -304,8 +316,8 @@ TEST_CASE("CBORSerialize_ROI", "[CBOR]") { REQUIRE(deserialized->start_message); StartMessage &output_message = *deserialized->start_message; - REQUIRE(output_message.rois.size() == 3); - for (int i = 0; i < 3; i++) { + REQUIRE(output_message.rois.size() == 4); + for (int i = 0; i < 4; i++) { CHECK(output_message.rois[i].name == message.rois[i].name); CHECK(output_message.rois[i].type == message.rois[i].type); } @@ -320,6 +332,11 @@ TEST_CASE("CBORSerialize_ROI", "[CBOR]") { CHECK(output_message.rois[2].azim.qmin == message.rois[2].azim.qmin); CHECK(output_message.rois[2].azim.qmax == message.rois[2].azim.qmax); + CHECK(output_message.rois[2].azim.phi_min == 30.0f); + CHECK(output_message.rois[2].azim.phi_max == 90.0f); + + // Full ring: phi_min == phi_max round-trips (no sector emitted) + CHECK(output_message.rois[3].azim.phi_min == output_message.rois[3].azim.phi_max); } TEST_CASE("CBORSerialize_Start_EmptyString", "[CBOR]") {