From 51451f167be6b55e4f68626e4d817fe36982ca0b Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Mon, 15 Jun 2026 16:43:48 +0200 Subject: [PATCH 1/2] Force CPU azimuthal integration in FPGA workflow Add AzimuthalIntegrationSettings::ForceCPUinFPGAWorkflow so the FPGA data-acquisition path can compute azimuthal integration on the CPU instead of the FPGA. This removes the FPGA bin-count limit (FPGA_INTEGRATION_BIN_COUNT) and adds per-bin standard deviation. - MXAnalysisAfterFPGA runs AzIntEngineCPU on the assembled image; the FPGA integration-map load and per-module read-back are skipped when forced. - JFJochReceiverFPGA rejects bin counts above the FPGA limit at acquisition start (unless CPU is forced) with an actionable error. - Wire force_cpu through the broker (both directions) and the frontend. Co-Authored-By: Claude Opus 4.8 --- acquisition_device/AcquisitionDevice.cpp | 20 +++-- broker/OpenAPIConvert.cpp | 2 + broker/gen/model/Azim_int_settings.cpp | 33 +++++++- broker/gen/model/Azim_int_settings.h | 9 +++ broker/jfjoch_api.yaml | 7 ++ broker/redoc-static.html | 13 ++-- common/AzimuthalIntegrationSettings.cpp | 9 +++ common/AzimuthalIntegrationSettings.h | 5 ++ docs/CHANGELOG.md | 5 ++ docs/python_client/docs/AzimIntSettings.md | 1 + frontend/src/components/AzIntSettings.tsx | 11 ++- .../src/openapi/models/azim_int_settings.ts | 7 ++ image_analysis/MXAnalysisAfterFPGA.cpp | 38 +++++++++- image_analysis/MXAnalysisAfterFPGA.h | 12 ++- receiver/JFJochReceiverFPGA.cpp | 27 ++++++- tests/JFJochReceiverIntegrationTest.cpp | 75 +++++++++++++++++++ 16 files changed, 255 insertions(+), 19 deletions(-) diff --git a/acquisition_device/AcquisitionDevice.cpp b/acquisition_device/AcquisitionDevice.cpp index 452338fa..b707c18e 100644 --- a/acquisition_device/AcquisitionDevice.cpp +++ b/acquisition_device/AcquisitionDevice.cpp @@ -204,23 +204,31 @@ void AcquisitionDevice::InitializeDataProcessing(const DiffractionExperiment &ex auto offset = experiment.GetFirstModuleOfDataStream(data_stream); size_t modules = experiment.GetModulesNum(data_stream); + // When azimuthal integration is forced onto the CPU, the FPGA must not bin pixels + // (the integration map can address more bins than the FPGA supports); the CPU path + // computes the profile from the assembled image instead. + const bool load_integration_map = !experiment.GetAzimuthalIntegrationSettings().IsForceCPUinFPGAWorkflow(); + if (experiment.IsGeometryTransformed()) { std::vector tmp1(RAW_MODULE_SIZE); std::vector tmp2(RAW_MODULE_SIZE); for (int m = 0; m < modules; m++) { - ConvertedToRawGeometry(experiment, offset + m, tmp1.data(), azint.Corrections().data()); - ConvertedToRawGeometry(experiment, offset + m, tmp2.data(), azint.GetPixelToBin().data()); - InitializeIntegrationMap(tmp2.data(), tmp1.data(), m); + if (load_integration_map) { + ConvertedToRawGeometry(experiment, offset + m, tmp1.data(), azint.Corrections().data()); + ConvertedToRawGeometry(experiment, offset + m, tmp2.data(), azint.GetPixelToBin().data()); + InitializeIntegrationMap(tmp2.data(), tmp1.data(), m); + } ConvertedToRawGeometry(experiment, offset + m, tmp1.data(), azint.Resolution().data()); InitializeSpotFinderResolutionMap(tmp1.data(), m); } } else { for (int m = 0; m < modules; m++) { - InitializeIntegrationMap(azint.GetPixelToBin().data() + (offset + m) * RAW_MODULE_SIZE, - azint.Corrections().data() + (offset + m) * RAW_MODULE_SIZE, - m); + if (load_integration_map) + InitializeIntegrationMap(azint.GetPixelToBin().data() + (offset + m) * RAW_MODULE_SIZE, + azint.Corrections().data() + (offset + m) * RAW_MODULE_SIZE, + m); InitializeSpotFinderResolutionMap(azint.Resolution().data() + (m + offset) * RAW_MODULE_SIZE, m); } diff --git a/broker/OpenAPIConvert.cpp b/broker/OpenAPIConvert.cpp index c601f594..4f465d5b 100644 --- a/broker/OpenAPIConvert.cpp +++ b/broker/OpenAPIConvert.cpp @@ -438,6 +438,7 @@ AzimuthalIntegrationSettings Convert(const org::openapitools::server::model::Azi ret.QSpacing_recipA(input.getQSpacing()); ret.QRange_recipA(input.getLowQRecipA(), input.getHighQRecipA()); ret.AzimuthalBinCount(input.getAzimuthalBins()); + ret.ForceCPUinFPGAWorkflow(input.isForceCpu()); return ret; } @@ -449,6 +450,7 @@ org::openapitools::server::model::Azim_int_settings Convert(const AzimuthalInteg ret.setLowQRecipA(settings.GetLowQ_recipA()); ret.setQSpacing(settings.GetQSpacing_recipA()); ret.setAzimuthalBins(settings.GetAzimuthalBinCount()); + ret.setForceCpu(settings.IsForceCPUinFPGAWorkflow()); return ret; } diff --git a/broker/gen/model/Azim_int_settings.cpp b/broker/gen/model/Azim_int_settings.cpp index b105e272..9bd3ef35 100644 --- a/broker/gen/model/Azim_int_settings.cpp +++ b/broker/gen/model/Azim_int_settings.cpp @@ -28,6 +28,8 @@ Azim_int_settings::Azim_int_settings() m_Q_spacing = 0.0f; m_Azimuthal_bins = 1L; m_Azimuthal_binsIsSet = false; + m_Force_cpu = false; + m_Force_cpuIsSet = false; } @@ -69,7 +71,7 @@ bool Azim_int_settings::validate(std::stringstream& msg, const std::string& path } } - + return success; } @@ -94,7 +96,10 @@ bool Azim_int_settings::operator==(const Azim_int_settings& rhs) const && - ((!azimuthalBinsIsSet() && !rhs.azimuthalBinsIsSet()) || (azimuthalBinsIsSet() && rhs.azimuthalBinsIsSet() && getAzimuthalBins() == rhs.getAzimuthalBins())) + ((!azimuthalBinsIsSet() && !rhs.azimuthalBinsIsSet()) || (azimuthalBinsIsSet() && rhs.azimuthalBinsIsSet() && getAzimuthalBins() == rhs.getAzimuthalBins())) && + + + ((!forceCpuIsSet() && !rhs.forceCpuIsSet()) || (forceCpuIsSet() && rhs.forceCpuIsSet() && isForceCpu() == rhs.isForceCpu())) ; } @@ -114,6 +119,8 @@ void to_json(nlohmann::json& j, const Azim_int_settings& o) j["q_spacing"] = o.m_Q_spacing; if(o.azimuthalBinsIsSet()) j["azimuthal_bins"] = o.m_Azimuthal_bins; + if(o.forceCpuIsSet()) + j["force_cpu"] = o.m_Force_cpu; } @@ -129,6 +136,11 @@ void from_json(const nlohmann::json& j, Azim_int_settings& o) j.at("azimuthal_bins").get_to(o.m_Azimuthal_bins); o.m_Azimuthal_binsIsSet = true; } + if(j.find("force_cpu") != j.end()) + { + j.at("force_cpu").get_to(o.m_Force_cpu); + o.m_Force_cpuIsSet = true; + } } @@ -189,6 +201,23 @@ void Azim_int_settings::unsetAzimuthal_bins() { m_Azimuthal_binsIsSet = false; } +bool Azim_int_settings::isForceCpu() const +{ + return m_Force_cpu; +} +void Azim_int_settings::setForceCpu(bool const value) +{ + m_Force_cpu = value; + m_Force_cpuIsSet = true; +} +bool Azim_int_settings::forceCpuIsSet() const +{ + return m_Force_cpuIsSet; +} +void Azim_int_settings::unsetForce_cpu() +{ + m_Force_cpuIsSet = false; +} } // namespace org::openapitools::server::model diff --git a/broker/gen/model/Azim_int_settings.h b/broker/gen/model/Azim_int_settings.h index 4ce22f34..2066eef4 100644 --- a/broker/gen/model/Azim_int_settings.h +++ b/broker/gen/model/Azim_int_settings.h @@ -89,6 +89,13 @@ public: void setAzimuthalBins(int64_t const value); bool azimuthalBinsIsSet() const; void unsetAzimuthal_bins(); + /// + /// Force CPU processing of azimuthal integration in the FPGA data acquisition workflow. This allows to extend number of azimuthal integration bins, as well as to calculate standard deviation of the azimuthal integration results. + /// + bool isForceCpu() const; + void setForceCpu(bool const value); + bool forceCpuIsSet() const; + void unsetForce_cpu(); friend void to_json(nlohmann::json& j, const Azim_int_settings& o); friend void from_json(const nlohmann::json& j, Azim_int_settings& o); @@ -105,6 +112,8 @@ protected: int64_t m_Azimuthal_bins; bool m_Azimuthal_binsIsSet; + bool m_Force_cpu; + bool m_Force_cpuIsSet; }; diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 3796f0f2..3b5b8058 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -1079,6 +1079,13 @@ components: maximum: 256 default: 1 description: Numer of azimuthal (phi) bins; 1 = standard 1D azimuthal integration + force_cpu: + type: boolean + default: false + description: | + Force CPU processing of azimuthal integration in the FPGA data acquisition workflow. + This allows to extend number of azimuthal integration bins, as well as to calculate standard deviation + of the azimuthal integration results. detector_list_element: type: object required: diff --git a/broker/redoc-static.html b/broker/redoc-static.html index 6ae6d5be..a998f7c8 100644 --- a/broker/redoc-static.html +++ b/broker/redoc-static.html @@ -702,15 +702,18 @@ This option should be turned OFF for small molecule datasets or for crystals wit
http://localhost:5232/config/spot_finding

Response samples

Content type
application/json
{
  • "enable": true,
  • "indexing": true,
  • "signal_to_noise_threshold": 0.1,
  • "photon_count_threshold": 0,
  • "min_pix_per_spot": 1,
  • "max_pix_per_spot": 1,
  • "high_resolution_limit": 0.1,
  • "low_resolution_limit": 0.1,
  • "high_resolution_limit_for_spot_count_low_res": 2,
  • "quick_integration": false,
  • "ice_ring_width_q_recipA": 0.02,
  • "high_res_gap_Q_recipA": 1.5
}

Configure azimuthal integration

Can be done when detector is Inactive or Idle

Request Body schema: application/json
polarization_corr
required
boolean
Default: true

Apply polarization correction for azimuthal integration (polarization factor must be configured in dataset settings)

solid_angle_corr
required
boolean
Default: true

Apply solid angle correction for azimuthal integration

-
high_q_recipA
required
number <float>
low_q_recipA
required
number <float>
q_spacing
required
number <float>
azimuthal_bins
integer <int64> [ 1 .. 256 ]
Default: 1

Numer of azimuthal (phi) bins; 1 = standard 1D azimuthal integration

+
high_q_recipA
required
number <float>
low_q_recipA
required
number <float>
q_spacing
required
number <float>
azimuthal_bins
integer <int64> [ 1 .. 256 ]
Default: 1

Numer of azimuthal (phi) bins; 1 = standard 1D azimuthal integration

+
force_cpu
boolean
Default: false

Force CPU processing of azimuthal integration in the FPGA data acquisition workflow. +This allows to extend number of azimuthal integration bins, as well as to calculate standard deviation +of the azimuthal integration results.

Responses

Request samples

Content type
application/json
{
  • "polarization_corr": true,
  • "solid_angle_corr": true,
  • "high_q_recipA": 0.1,
  • "low_q_recipA": 0.1,
  • "q_spacing": 0.1,
  • "azimuthal_bins": 1
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get azimuthal integration configuration

Can be done anytime

+
http://localhost:5232/config/azim_int

Request samples

Content type
application/json
{
  • "polarization_corr": true,
  • "solid_angle_corr": true,
  • "high_q_recipA": 0.1,
  • "low_q_recipA": 0.1,
  • "q_spacing": 0.1,
  • "azimuthal_bins": 1,
  • "force_cpu": false
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get azimuthal integration configuration

Can be done anytime

Responses

Response samples

Content type
application/json
{
  • "polarization_corr": true,
  • "solid_angle_corr": true,
  • "high_q_recipA": 0.1,
  • "low_q_recipA": 0.1,
  • "q_spacing": 0.1,
  • "azimuthal_bins": 1
}

Load binary image for internal FPGA generator

Load image for internal FPGA generator. This can only happen in Idle state of the detector. +

http://localhost:5232/config/azim_int

Response samples

Content type
application/json
{
  • "polarization_corr": true,
  • "solid_angle_corr": true,
  • "high_q_recipA": 0.1,
  • "low_q_recipA": 0.1,
  • "q_spacing": 0.1,
  • "azimuthal_bins": 1,
  • "force_cpu": false
}

Load binary image for internal FPGA generator

Load image for internal FPGA generator. This can only happen in Idle state of the detector. Requires binary blob with 16-bit integer numbers of size of detector in raw/converted coordinates (depending on detector settings).

query Parameters
id
integer <int64> [ 0 .. 127 ]

Image id to upload

@@ -812,7 +815,7 @@ This can only be done when detector is Idle, Error or

Request samples

Content type
application/json
{
  • "box": {
    },
  • "circle": {
    },
  • "azim": {
    }
}

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get general statistics

Responses

Response samples

Content type
application/json
{
  • "detector": {
    },
  • "detector_list": {
    },
  • "detector_settings": {
    },
  • "image_format_settings": {
    },
  • "instrument_metadata": {
    },
  • "file_writer_settings": {
    },
  • "data_processing_settings": {
    },
  • "measurement": {
    },
  • "broker": {
    },
  • "fpga": [
    ],
  • "calibration": [
    ],
  • "zeromq_preview": {
    },
  • "zeromq_metadata": {
    },
  • "dark_mask": {
    },
  • "pixel_mask": {
    },
  • "roi": {
    },
  • "az_int": {
    },
  • "buffer": {
    },
  • "indexing": {
    },
  • "image_pusher": {
    }
}

Get data collection statistics

Results of the last data collection

+
http://localhost:5232/statistics

Response samples

Content type
application/json
{
  • "detector": {
    },
  • "detector_list": {
    },
  • "detector_settings": {
    },
  • "image_format_settings": {
    },
  • "instrument_metadata": {
    },
  • "file_writer_settings": {
    },
  • "data_processing_settings": {
    },
  • "measurement": {
    },
  • "broker": {
    },
  • "fpga": [
    ],
  • "calibration": [
    ],
  • "zeromq_preview": {
    },
  • "zeromq_metadata": {
    },
  • "dark_mask": {
    },
  • "pixel_mask": {
    },
  • "roi": {
    },
  • "az_int": {
    },
  • "buffer": {
    },
  • "indexing": {
    },
  • "image_pusher": {
    }
}

Get data collection statistics

Results of the last data collection

Responses