Compare commits

..

42 Commits

Author SHA1 Message Date
7044f7b085 Logger: handle situation when git sha1 is empty 2024-05-14 22:22:15 +02:00
165fe1a432 FPGA: build script is safe for situations when git is absent 2024-05-14 22:21:58 +02:00
ad2bc91582 Remove .gitmodules file 2024-05-14 21:53:43 +02:00
c1e9cc5d3c Update spdlog (working on better integration) 2024-05-14 21:51:17 +02:00
eb0e5daf8f catch2: download with CMake + update to recent Catch2 2024-05-14 21:31:34 +02:00
9f1b6a6e96 fast-feedback-indexer: download with CMake 2024-05-14 20:57:29 +02:00
81db3af275 slsDetectorPackage: download with CMake 2024-05-14 20:54:42 +02:00
8cd5787848 bitshuffle_hperf: put directly into the repository 2024-05-14 20:50:12 +02:00
ebd3628eab bitshuffle_hperf: put directly into the repository 2024-05-14 20:48:15 +02:00
e02f8692f9 zstd: built via cmake 2024-05-14 20:42:34 +02:00
f27126fa1f pistache: built via cmake 2024-05-14 20:36:50 +02:00
d2c5b9c038 tinycbor: internal to repository 2024-05-14 20:30:19 +02:00
ff191c8490 tinycbor: remove submodule 2024-05-14 20:28:43 +02:00
ced40a7170 PreviewImage: Add resolution ring (experimental) 2024-05-14 18:53:11 +02:00
37e825a018 FPGA: Spot finder masks module edges, improve testing in this regard 2024-05-14 18:01:40 +02:00
36257d7009 CI: Create release as part of CI - should be working now 2024-05-14 17:39:58 +02:00
786b28ed78 CI: Create release as part of CI (another try) 2024-05-14 17:21:22 +02:00
bd86809bdd CI: Create release as part of CI (another try) 2024-05-14 17:05:39 +02:00
af366acb08 CI: Create release as part of CI (another try) 2024-05-14 16:40:42 +02:00
4fe2904b47 CI: Create release as part of CI (try again) 2024-05-14 15:12:39 +02:00
c97749c16a CI: Create release as part of CI 2024-05-14 14:54:47 +02:00
bd43b09b4c Revert "CI: More serious test of package registry"
This reverts commit dcd6088afe.
2024-05-14 14:45:26 +02:00
dcd6088afe CI: More serious test of package registry 2024-05-14 14:39:26 +02:00
b4c26b036f CI: Try release pipeline with runner tag 2024-05-14 14:29:02 +02:00
b2a7dadef7 CI: Try release pipeline 2024-05-14 14:21:00 +02:00
5808d2c5bc VERSION: Add package version to git 2024-05-14 14:05:14 +02:00
c71728e16f Dockerfile: add comment for MAX IV 2024-05-14 13:49:42 +02:00
10b8398bd0 CI: test 2024-05-14 13:29:55 +02:00
9c1796a9e6 CMake: Remove ZeroMQ CMake module 2024-05-07 15:27:34 +02:00
3cbf675120 CI: Building HLS is NOT requirement to build firmware (it will fail anyway 2024-05-07 15:12:58 +02:00
1aa1da92ff CI: Fix FPGA .mcs name and FPGA build is optional if not required 2024-05-07 15:11:53 +02:00
07a1659e94 CI: Fix FPGA .mcs name 2024-05-07 15:09:53 +02:00
bc1285acc6 CI: FPGA HLS is built in parallel with 4 threads 2024-05-07 14:45:35 +02:00
c330a0ed81 CI: FPGA images are called jfjoch_fpga_{FLOW}.mcs 2024-05-07 14:43:30 +02:00
cdba3000d2 README.md: Linkt to broker/writer subdirectories 2024-05-07 14:34:24 +02:00
db47da3b6d README.md: Remove part on .mcs naming 2024-05-07 14:33:22 +02:00
d3420d7f0c CI: Fix driver artifact 2024-05-07 14:13:58 +02:00
975442f452 README.md: Remove CompressionBenchmark 2024-05-07 14:13:28 +02:00
4c87554233 nextgendcu/broker/README.md: Updates 2024-05-07 14:12:15 +02:00
b5da66baf6 CompressionBenchmark: Remove 2024-05-07 14:11:21 +02:00
69d34e22f7 CI: Update 2024-05-07 14:05:51 +02:00
89cf520e52 FPGAIntegrationTest: Truncate number of test images 2024-05-07 14:00:15 +02:00
39 changed files with 565 additions and 31 deletions

3
.gitignore vendored
View File

@@ -1,3 +0,0 @@
cmake-build-debug/
cmake-build-release/
build*/

View File

@@ -7,6 +7,7 @@ stages:
build:x86:gcc:
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
CC: gcc
CXX: g++
tags:
@@ -23,6 +24,7 @@ build:x86:gcc:
build:x86:gcc_writer:
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
CC: gcc
CXX: g++
tags:
@@ -39,6 +41,7 @@ build:x86:gcc_writer:
build:x86:driver:
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
CC: gcc
CXX: g++
tags:
@@ -57,6 +60,8 @@ build:x86:driver:
build:x86:vitis_hls:
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
tags:
- x86
needs: []
@@ -79,6 +84,8 @@ build:x86:vitis_hls:
build:x86:frontend:
stage: build
variables:
GIT_SUBMODULE_STRATEGY: recursive
tags:
- x86
needs: []
@@ -97,6 +104,7 @@ test:x86:gcc:
stage: test
timeout: 90m
variables:
GIT_SUBMODULE_STRATEGY: recursive
CTEST_OUTPUT_ON_FAILURE: 1
CC: gcc
CXX: g++
@@ -110,9 +118,9 @@ test:x86:gcc:
- mkdir -p build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release ..
- make -j48 jfjoch_test HDF5DatasetWriteTest
- make -j48 CatchTest HDF5DatasetWriteTest
- cd tests
- ./jfjoch_test -r junit -o report.xml
- ./CatchTest -r junit -o report.xml
- cd ../tools
- ./HDF5DatasetWriteTest ../../tests/test_data/compression_benchmark.h5
artifacts:
@@ -124,6 +132,7 @@ test:x86:crystfel:
stage: test
timeout: 90m
variables:
GIT_SUBMODULE_STRATEGY: recursive
CTEST_OUTPUT_ON_FAILURE: 1
CC: gcc
CXX: g++
@@ -147,6 +156,7 @@ test:x86:xds_durin:
stage: test
timeout: 90m
variables:
GIT_SUBMODULE_STRATEGY: recursive
CTEST_OUTPUT_ON_FAILURE: 1
CC: gcc
CXX: g++
@@ -170,6 +180,7 @@ test:x86:xds_neggia:
stage: test
timeout: 90m
variables:
GIT_SUBMODULE_STRATEGY: recursive
CTEST_OUTPUT_ON_FAILURE: 1
CC: gcc
CXX: g++
@@ -193,6 +204,7 @@ test:x86:xia2.ssx:
stage: test
timeout: 90m
variables:
GIT_SUBMODULE_STRATEGY: recursive
CTEST_OUTPUT_ON_FAILURE: 1
CC: gcc
CXX: g++
@@ -218,6 +230,7 @@ synthesis:vivado_pcie_100g:
stage: synthesis
dependencies: []
variables:
GIT_SUBMODULE_STRATEGY: recursive
CC: gcc
CXX: g++
rules:
@@ -230,6 +243,8 @@ synthesis:vivado_pcie_100g:
- fpga/scripts/*
- fpga/xdc/*
- fpga/pcie_driver/jfjoch_fpga.h
- when: manual
allow_failure: true
tags:
- vivado
artifacts:
@@ -251,6 +266,7 @@ synthesis:vivado_pcie_8x10g:
stage: synthesis
dependencies: []
variables:
GIT_SUBMODULE_STRATEGY: recursive
CC: gcc
CXX: g++
rules:
@@ -263,6 +279,8 @@ synthesis:vivado_pcie_8x10g:
- fpga/xdc/*
- fpga/pcie_driver/jfjoch_fpga.h
allow_failure: true
- when: manual
allow_failure: true
tags:
- vivado
artifacts:
@@ -282,9 +300,7 @@ synthesis:vivado_pcie_8x10g:
release:
stage: release
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
when: manual
tags:
- x86
dependencies:

View File

@@ -31,6 +31,13 @@ SET(JFJOCH_WRITER_ONLY OFF CACHE BOOL "Compile HDF5 writer only")
INCLUDE_DIRECTORIES(include)
INCLUDE(CheckIncludeFile)
#This is to supress error in TORCH
IF ((NOT EXISTS /usr/bin/python) AND (EXISTS /usr/bin/python3))
SET(PYTHON_EXECUTABLE /usr/bin/python3)
ENDIF()
FIND_PACKAGE(Torch HINTS /opt/libtorch/share/cmake/Torch/)
FIND_LIBRARY(NUMA_LIBRARY NAMES numa DOC "NUMA Library")
CHECK_INCLUDE_FILE(numaif.h HAS_NUMAIF)
CHECK_INCLUDE_FILE(numa.h HAS_NUMA_H)
@@ -83,7 +90,8 @@ ELSE()
ADD_SUBDIRECTORY(tests)
ADD_SUBDIRECTORY(tools)
ADD_SUBDIRECTORY(preview)
SET(jfjoch_executables jfjoch_broker jfjoch_writer jfjoch_test CompressionBenchmark HDF5DatasetWriteTest jfjoch_udp_simulator sls_detector_put sls_detector_get)
ADD_SUBDIRECTORY(resonet)
SET(jfjoch_executables jfjoch_broker jfjoch_writer CatchTest CompressionBenchmark HDF5DatasetWriteTest jfjoch_udp_simulator sls_detector_put sls_detector_get)
ENDIF()
ADD_CUSTOM_COMMAND(OUTPUT frontend_ui/build/index.html

View File

@@ -39,9 +39,10 @@ Required:
* JPEG library (turbo-jpeg is also OK)
Optional:
* CUDA compiler version 11 or newer - required for MX fast feedback indexer
* CUDA compiler version 11 or newer - required for MX indexing and ML resolution estimation
* NUMA library - to pin threads to nodes/CPUs
* Node.js - to make frontend
* libtorch - for resolution estimation using model from Stanford - see below
Automatically downloaded by CMake:
* SLS Detector Package - see [github.com/slsdetectorgroup/slsDetectorPackage](https://github.com/slsdetectorgroup/slsDetectorPackage)
@@ -75,6 +76,7 @@ For license check LICENSE file in respective directory
Use the following commands:
```
git submodule update --init --recursive
mkdir build
cd build
cmake ..
@@ -86,6 +88,7 @@ In most use cases it is better to have a separate machine, with access to distri
Such machine needs only a HDF5 writer service with fewer dependencies. For compilation use the following commands:
```
git submodule update --init --recursive
mkdir build
cd build
cmake -DJFJOCH_WRITER_ONLY=ON ..
@@ -109,6 +112,7 @@ Frontend is written in TypeScript. For details see [frontend_ui/](frontend_ui) d
Jungfraujoch Cmake scripts have an option to start frontend build with the following command:
```
git submodule update --init --recursive
mkdir build
cd build
cmake ..
@@ -118,7 +122,7 @@ Contrary to standard CMake way, frontend will be built in "source" `frontend_ui/
## Tests
Automated test routine is then accessible as `tests/jfjoch_test`. There are also benchmark routines:
Automated test routine is then accessible as `tests/CatchTest`. There are also benchmark routines:
* `HDF5DatasetWriteTest` to measure HDF5 dataset writing speed (single threaded)
* `jfjoch_spot_finding_test` to apply spot finding and indexing routines in Jungfraujoch to an example dataset - this is equivalent to FPGA spot finding algorithm, but NOT performance equivalent as it is particularly not-efficient
@@ -126,3 +130,16 @@ Automated test routine is then accessible as `tests/jfjoch_test`. There are also
In addition, tests are executed to verify that datasets written by Jungfraujoch are readable with XDS Durin plugin, XDS Neggia plygin and CrystFEL.
Input files for these programs are placed in `xds_durin`, `xds_neggia` and `crystfel` folders. See `.gitlab-ci.yml` for details.
## Resolution estimation
Resolution estimation can be done with a recent deep learning model by D. Mendez et al.
(see [Acta Cryst D, 80, 26-43](https://doi.org/10.1107/S2059798323010586)), adapted to Jungfraujoch.
Model used in the original paper is located in the [resonet/](resonet) directory, after converting to TorchScript format.
To use the feature it is necessary to install [libtorch](https://pytorch.org/) library, preferably in `/opt/libtorch` location. The C++11 ABI version needs to be chosen.
For RHEL 8 systems, please download older version 2.1.0, as version 2.2.0 requires newer `glibc` library than available with the operating system.
Version 2.1.0 can be downloaded with the following command:
```
wget https://download.pytorch.org/libtorch/cu121/libtorch-cxx11-abi-shared-with-deps-2.1.0%2Bcu121.zip
```

View File

@@ -1 +1 @@
1.0.0-rc.1
1.0.0-test5

View File

@@ -651,6 +651,10 @@ void JFJochBrokerHttp::preview_image_tiff_get(Pistache::Http::ResponseWriter &re
response.send(Pistache::Http::Code::Not_Found);
}
void JFJochBrokerHttp::plot_resolution_estimate_histogram_get(Pistache::Http::ResponseWriter &response) {
GenericPlot(PlotType::ResEstimation, 0, response);
}
void JFJochBrokerHttp::config_internal_generator_image_put(const Pistache::Rest::Request &request,
Pistache::Http::ResponseWriter &response) {
int64_t image_number = 0;

View File

@@ -75,6 +75,8 @@ class JFJochBrokerHttp : public org::openapitools::server::api::DefaultApi {
void plot_rad_int_per_file_get(Pistache::Http::ResponseWriter &response) override;
void plot_resolution_estimate_histogram_get(Pistache::Http::ResponseWriter &response) override;
void statistics_calibration_get(Pistache::Http::ResponseWriter &response) override;
void statistics_data_collection_get(Pistache::Http::ResponseWriter &response) override;

View File

@@ -319,6 +319,7 @@ void ParseFacilityConfiguration(const nlohmann::json &input, const std::string&
"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"))

View File

@@ -25,6 +25,7 @@ To run the test:
### Compile Jungfraujoch with frontend
```
git submodule update --init --recursive
mkdir build
cd build
cmake ..

View File

@@ -56,6 +56,7 @@ void DefaultApi::setupRoutes() {
Routes::Get(*router, base + "/plot/rad_int_per_file", Routes::bind(&DefaultApi::plot_rad_int_per_file_get_handler, this));
Routes::Get(*router, base + "/plot/receiver_delay", Routes::bind(&DefaultApi::plot_receiver_delay_get_handler, this));
Routes::Get(*router, base + "/plot/receiver_free_send_buffers", Routes::bind(&DefaultApi::plot_receiver_free_send_buffers_get_handler, this));
Routes::Get(*router, base + "/plot/resolution_estimate_histogram", Routes::bind(&DefaultApi::plot_resolution_estimate_histogram_get_handler, this));
Routes::Get(*router, base + "/plot/roi_max_count", Routes::bind(&DefaultApi::plot_roi_max_count_get_handler, this));
Routes::Get(*router, base + "/plot/roi_sum", Routes::bind(&DefaultApi::plot_roi_sum_get_handler, this));
Routes::Get(*router, base + "/plot/roi_valid_pixels", Routes::bind(&DefaultApi::plot_roi_valid_pixels_get_handler, this));
@@ -671,6 +672,26 @@ void DefaultApi::plot_receiver_free_send_buffers_get_handler(const Pistache::Res
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
}
}
void DefaultApi::plot_resolution_estimate_histogram_get_handler(const Pistache::Rest::Request &, Pistache::Http::ResponseWriter response) {
try {
try {
this->plot_resolution_estimate_histogram_get(response);
} catch (Pistache::Http::HttpError &e) {
response.send(static_cast<Pistache::Http::Code>(e.code()), e.what());
return;
} catch (std::exception &e) {
const std::pair<Pistache::Http::Code, std::string> errorInfo = this->handleOperationException(e);
response.send(errorInfo.first, errorInfo.second);
return;
}
} catch (std::exception &e) {
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
}
}
void DefaultApi::plot_roi_max_count_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response) {
try {

View File

@@ -80,6 +80,7 @@ private:
void plot_rad_int_per_file_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_receiver_delay_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_receiver_free_send_buffers_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_resolution_estimate_histogram_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_roi_max_count_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_roi_sum_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void plot_roi_valid_pixels_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
@@ -292,6 +293,13 @@ private:
/// <param name="binning">Binning of frames for the plot (0 &#x3D; default binning) (optional, default to 0)</param>
virtual void plot_receiver_free_send_buffers_get(const std::optional<int32_t> &binning, Pistache::Http::ResponseWriter &response) = 0;
/// <summary>
/// Generate resolution estimate histogram
/// </summary>
/// <remarks>
/// Generate histogram of crystal resolutions from 1.0 to 5.0 A based on ML model
/// </remarks>
virtual void plot_resolution_estimate_histogram_get(Pistache::Http::ResponseWriter &response) = 0;
/// <summary>
/// Generate plot of ROI max count
/// </summary>
/// <remarks>

View File

@@ -1445,6 +1445,17 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/plots'
/plot/resolution_estimate_histogram:
get:
summary: Generate resolution estimate histogram
description: Generate histogram of crystal resolutions from 1.0 to 5.0 A based on ML model
responses:
"200":
description: Everything OK
content:
application/json:
schema:
$ref: '#/components/schemas/plots'
/plot/rad_int:
get:
summary: Generate radial integration profile

File diff suppressed because one or more lines are too long

View File

@@ -1064,6 +1064,15 @@ const DetectorSetup &DiffractionExperiment::GetDetectorSetup() const {
return detector;
}
DiffractionExperiment &DiffractionExperiment::NeuralNetModelPath(const std::string &input) {
neural_net_model_path = input;
return *this;
}
std::string DiffractionExperiment::GetNeuralNetModelPath() const {
return neural_net_model_path;
}
DiffractionExperiment &DiffractionExperiment::PulsedSource(bool input) {
pulsed_source = input;
return *this;

View File

@@ -89,6 +89,8 @@ class DiffractionExperiment {
uint64_t series_id;
std::string neural_net_model_path;
bool rad_int_solid_angle_corr;
bool rad_int_polarization_corr;
float rad_int_polarization_factor{};
@@ -142,6 +144,7 @@ public:
DiffractionExperiment& FixedGainG1(bool input);
DiffractionExperiment& IncrementSeriesID();
DiffractionExperiment& ConversionOnFPGA(bool input);
DiffractionExperiment& NeuralNetModelPath(const std::string& input);
DiffractionExperiment& PulsedSource(bool input);
DiffractionExperiment& ImagesPerTrigger(int64_t input);
@@ -279,6 +282,7 @@ public:
bool IsConversionOnFPGA() const;
const DetectorSetup& GetDetectorSetup() const;
std::string GetNeuralNetModelPath() const;
bool IsPulsedSource() const;

View File

@@ -9,7 +9,7 @@
enum class PlotType {BkgEstimate, RadInt, RadIntPerTimePoint, SpotCount, IndexingRate, IndexingRatePerTimePoint,
ErrorPixels, ImageCollectionEfficiency, ReceiverDelay, ReceiverFreeSendBuf, StrongPixels,
ROISum, ROIMaxCount, ROIPixels};
ROISum, ROIMaxCount, ROIPixels, ResEstimation};
struct PlotRequest {
PlotType type;

View File

@@ -75,6 +75,8 @@ struct DataMessage {
std::optional<uint64_t> receiver_free_send_buf;
std::optional<uint64_t> storage_cell;
std::optional<float> resolution_estimation;
std::optional<uint64_t> xfel_pulse_id;
std::optional<uint64_t> xfel_event_code;

View File

@@ -16,7 +16,9 @@ export enum PlotType {
STRONG_PIXELS,
ROI_SUM,
ROI_MAX_COUNT,
RECEIVER_FREE_SEND_BUFS
RES_ESTIMATION,
RECEIVER_FREE_SEND_BUFS,
BEAM_CENTER_DRIFT
}
type MyProps = {
@@ -143,6 +145,13 @@ class DataProcessingPlot extends Component<MyProps, MyState> {
this.setState({connection_error: true});
});
break;
case PlotType.RES_ESTIMATION:
DefaultService.getPlotResolutionEstimateHistogram()
.then(data => this.setState({plots: data, connection_error: false}))
.catch(error => {
this.setState({connection_error: true});
});
break;
case PlotType.RAD_INT_PER_FILE:
DefaultService.getPlotRadIntPerFile()
.then(data => this.setState({plots: data, connection_error: false}))

View File

@@ -69,6 +69,9 @@ class DataProcessingPlots extends Component<MyProps, MyState> {
case "11":
this.setState({type: PlotType.ROI_SUM, xlabel: "Image number", ylabel: "Photon count"});
break;
case "12":
this.setState({type: PlotType.RES_ESTIMATION, xlabel: "Resolution [&#8491;]", ylabel: "Number of crystals"});
break;
case "13":
this.setState({type: PlotType.RECEIVER_FREE_SEND_BUFS, xlabel: "Image number", ylabel: "Number of buffers"});
break;
@@ -97,6 +100,7 @@ class DataProcessingPlots extends Component<MyProps, MyState> {
<MenuItem value={3}>Azimuthal integration profile</MenuItem>
<MenuItem value={11}>ROI area sum</MenuItem>
<MenuItem value={6}>ROI area max count</MenuItem>
<MenuItem value={12}>Crystal resolution histogram</MenuItem>
<MenuItem value={4}>Indexing rate (per time point)</MenuItem>
<MenuItem value={5}>Azimuthal integration profile (per time point)</MenuItem>
<MenuItem value={8}>Error and saturated pixels</MenuItem>

View File

@@ -724,6 +724,19 @@ export class DefaultService {
});
}
/**
* Generate resolution estimate histogram
* Generate histogram of crystal resolutions from 1.0 to 5.0 A based on ML model
* @returns plots Everything OK
* @throws ApiError
*/
public static getPlotResolutionEstimateHistogram(): CancelablePromise<plots> {
return __request(OpenAPI, {
method: 'GET',
url: '/plot/resolution_estimate_histogram',
});
}
/**
* Generate radial integration profile
* Generate average radial integration profile

View File

@@ -21,8 +21,8 @@ IF (CMAKE_CUDA_COMPILER)
FetchContent_Declare(
fast-indexer
GIT_REPOSITORY https://github.com/fleon-psi/fast-feedback-indexer/
GIT_TAG 651cbc9
GIT_REPOSITORY https://github.com/paulscherrerinstitute/fast-feedback-indexer/
GIT_TAG 2d6fa7511f240eeb4918dd7647edd0642e987317
)
FetchContent_MakeAvailable(fast-indexer)

View File

@@ -11,7 +11,7 @@ ADD_LIBRARY(JFJochReceiver STATIC
LossyFilter.cpp
LossyFilter.h)
TARGET_LINK_LIBRARIES(JFJochReceiver ImagePusher JFJochImageAnalysis JFJochAcquisitionDevice JFJochCommon JFJochHLSSimulation JFJochPreview)
TARGET_LINK_LIBRARIES(JFJochReceiver ImagePusher JFJochImageAnalysis JFJochAcquisitionDevice JFJochCommon JFJochHLSSimulation JFJochPreview JFJochResonet)
ADD_EXECUTABLE(jfjoch_action_test jfjoch_action_test.cpp)
TARGET_LINK_LIBRARIES(jfjoch_action_test JFJochReceiver)

View File

@@ -7,6 +7,7 @@
#include "../image_analysis/MXAnalyzer.h"
#include "../common/DiffractionGeometry.h"
#include "ImageMetadata.h"
#include "../resonet/NeuralNetResPredictor.h"
#include "../common/time_utc.h"
#include "../common/PixelMask.h"
@@ -265,6 +266,7 @@ void JFJochReceiver::FrameTransformationThread() {
Cancel(e);
}
NeuralNetResPredictor neural_net_predictor(experiment.GetNeuralNetModelPath());
FrameTransformation transformation(experiment);
MXAnalyzer analyzer(experiment);
@@ -337,6 +339,7 @@ void JFJochReceiver::FrameTransformationThread() {
message.receiver_free_send_buf = send_buf_ctrl.GetAvailBufLocations();
message.az_int_profile = az_int_profile_image.GetResult();
message.bkg_estimate = az_int_profile_image.GetMeanValueOfBins(az_int_min_bin, az_int_max_bin);
message.resolution_estimation = neural_net_predictor.Inference(experiment, transformation.GetImage());
plots.Add(message, image_number % experiment.GetTimePointNumber(), az_int_profile_image);

View File

@@ -6,7 +6,7 @@ JFJochReceiverPlots::JFJochReceiverPlots(const DiffractionExperiment &experiment
const AzimuthalIntegrationMapping &mapping)
: indexing_solution_per_time_point(experiment.GetTimePointNumber()),
default_binning(experiment.GetDefaultPlotBinning()),
az_int_profile(mapping) {
az_int_profile(mapping), resolution_estimation(50, 1.0, 5.0) {
az_int_profile.SetTitle("dataset");
for (int i = 0; i < experiment.GetTimePointNumber(); i++) {
az_int_profile_per_time_point.emplace_back(mapping);
@@ -36,6 +36,9 @@ void JFJochReceiverPlots::Add(const DataMessage &msg, uint64_t file_number, cons
indexing_solution.AddElement(msg.number, msg.indexing_result);
indexing_solution_per_time_point.Add(file_number, msg.indexing_result);
if (msg.resolution_estimation)
resolution_estimation.Add(msg.resolution_estimation.value());
az_int_profile += profile;
az_int_profile_per_time_point.at(file_number) += profile;
@@ -118,6 +121,8 @@ MultiLinePlot JFJochReceiverPlots::GetPlots(const PlotRequest &request) {
ret.emplace_back(tmp.at(0));
}
return ret;
case PlotType::ResEstimation:
return resolution_estimation.GetPlot();
case PlotType::RadIntPerTimePoint:
tmp = az_int_profile.GetPlot();
ret.emplace_back(tmp.at(0));

View File

@@ -31,6 +31,7 @@ class JFJochReceiverPlots {
std::map<std::string, std::unique_ptr<StatusVector<int64_t>>> roi_sum;
std::map<std::string, std::unique_ptr<StatusVector<int64_t>>> roi_max_count;
std::map<std::string, std::unique_ptr<StatusVector<uint64_t>>> roi_pixels;
FloatHistogram resolution_estimation;
AzimuthalIntegrationProfile az_int_profile;
std::vector<AzimuthalIntegrationProfile> az_int_profile_per_time_point;

View File

@@ -19,6 +19,7 @@ void print_usage(Logger &logger) {
logger.Info(" -i<num> number of images");
logger.Info(" -N<num> number of image processing threads");
logger.Info(" -P<txt> NUMA Policy (none|n2g2|n8g4|n8g4_hbm), none is default");
logger.Info(" -D<path> use resonet deep learning model for resolution estimation - path to TorchScript");
logger.Info(" -B<num> size of send buffer in MiB (default 2048)");
}
@@ -36,6 +37,7 @@ int main(int argc, char **argv) {
std::string numa_policy_name;
bool raw_data = false;
bool force_32bit = false;
std::string resonet_path;
DetectorType detector_type = DetectorType::JUNGFRAU;
bool hls_simulation = false;
size_t send_buffer_size_MiB = 2048;
@@ -46,7 +48,7 @@ int main(int argc, char **argv) {
}
int opt;
while ((opt = getopt(argc, argv, "s:i:m:N:P:vRIS:EHB:")) != -1) {
while ((opt = getopt(argc, argv, "s:i:m:N:P:vRIS:D:EHB:")) != -1) {
switch (opt) {
case 'i':
nimages = atol(optarg);
@@ -75,6 +77,9 @@ int main(int argc, char **argv) {
case 'I':
force_32bit = true;
break;
case 'D':
resonet_path = std::string(optarg);
break;
case 'E':
detector_type = DetectorType::EIGER;
break;
@@ -109,6 +114,8 @@ int main(int argc, char **argv) {
if (force_32bit)
x.FPGAOutputMode(FPGAPixelOutput::Int32);
if (!resonet_path.empty())
x.NeuralNetModelPath(resonet_path);
logger.Info("Data streams {} Total modules {} Total images {} Threads {}", nstreams, nmodules, nimages, nthreads);

10
resonet/CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
ADD_LIBRARY(JFJochResonet STATIC NeuralNetResPredictor.cpp NeuralNetResPredictor.h)
TARGET_LINK_LIBRARIES(JFJochResonet JFJochCommon)
IF (${TORCH_FOUND})
TARGET_COMPILE_DEFINITIONS(JFJochResonet PUBLIC -DJFJOCH_USE_TORCH)
TARGET_LINK_LIBRARIES(JFJochResonet ${TORCH_LIBRARIES})
TARGET_INCLUDE_DIRECTORIES(JFJochResonet PUBLIC ${TORCH_INCLUDE_DIRS})
ADD_EXECUTABLE(resonet_test resonet_test.cpp)
TARGET_LINK_LIBRARIES(resonet_test JFJochResonet JFJochWriter)
ENDIF()

28
resonet/LICENSE.resonet Normal file
View File

@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2023, Macromolecular Crystallography at SSRL
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,101 @@
// Copyright (2019-2024) Paul Scherrer Institute
#include "NeuralNetResPredictor.h"
#include <cmath>
#include "../common/JFJochException.h"
NeuralNetResPredictor::NeuralNetResPredictor(const std::string& model_path)
: model_input(512*512),
enable(!model_path.empty())
#ifdef JFJOCH_USE_TORCH
, device(torch::kCUDA)
#endif
{
#ifdef JFJOCH_USE_TORCH
if (enable) {
module = torch::jit::load(model_path);
module.to(device);
}
#else
enable = false;
#endif
}
template<class T>
void NeuralNetResPredictor::PrepareInternal(const DiffractionExperiment& experiment, const T* image) {
size_t pool_factor = GetMaxPoolFactor(experiment);
size_t xpixel = experiment.GetXPixelsNum();
size_t ypixel = experiment.GetYPixelsNum();
size_t start_x = std::lround(experiment.GetBeamX_pxl());
size_t start_y = std::lround(experiment.GetBeamY_pxl());
for (size_t y = 0; y < 512; y++) {
size_t y0 = y * pool_factor + start_y;
size_t min_yp = std::min(y0 + pool_factor, ypixel);
for (size_t x = 0; x < 512; x++) {
float val = 0.0;
size_t x0 = x * pool_factor + start_x;
size_t min_xp = std::min(x0 + pool_factor, ypixel);
for (size_t yp = y0; yp < min_yp; yp++) {
for (size_t xp = x0; xp < min_xp; xp++) {
int16_t pxl = image[yp * xpixel + xp];
if (val < pxl)
val = pxl;
}
}
float max_pool = floorf(sqrtf(val));
model_input[512 * y + x] = max_pool;
}
}
}
void NeuralNetResPredictor::Prepare(const DiffractionExperiment& experiment, const int16_t *image) {
PrepareInternal(experiment, image);
}
void NeuralNetResPredictor::Prepare(const DiffractionExperiment& experiment, const int32_t *image) {
PrepareInternal(experiment, image);
}
size_t NeuralNetResPredictor::GetMaxPoolFactor(const DiffractionExperiment& experiment) const {
float max_direction = std::max(experiment.GetXPixelsNum(), experiment.GetYPixelsNum()) / 2.0;
size_t pool_factor = std::lround(max_direction / 512.0f);
if (pool_factor <= 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector size is too small");
if (pool_factor > 8)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector size is too large");
return pool_factor;
}
std::optional<float> NeuralNetResPredictor::Inference(const DiffractionExperiment& experiment, const void *image) {
if (!enable)
return {};
#ifdef JFJOCH_USE_TORCH
if (experiment.GetPixelDepth() == 2)
Prepare(experiment, (int16_t *) image);
else
Prepare(experiment, (int32_t *) image);
auto options = torch::TensorOptions().dtype(at::kFloat);
auto model_input_tensor = torch::from_blob(model_input.data(), {1,1,512,512}, options).to(device);
std::vector<torch::jit::IValue> pixels;
pixels.emplace_back(std::move(model_input_tensor));
auto output = module.forward(pixels).toTensor();
auto tensor_output = output[0].item<float>();
float two_theta = atanf(((2.0f * experiment.GetPixelSize_mm() / experiment.GetDetectorDistance_mm()) * tensor_output));
float stheta = sinf(two_theta * 0.5f);
float resolution = experiment.GetWavelength_A() / (2.0f * stheta);
return resolution;
#else
return {};
#endif
}
const std::vector<float> &NeuralNetResPredictor::GetModelInput() const {
return model_input;
}

View File

@@ -0,0 +1,36 @@
// Copyright (2019-2024) Paul Scherrer Institute
#ifndef JUNGFRAUJOCH_NEURALNETRESPREDICTOR_H
#define JUNGFRAUJOCH_NEURALNETRESPREDICTOR_H
#ifdef JFJOCH_USE_TORCH
#include <torch/script.h>
#endif
#include "../common/DiffractionExperiment.h"
// Based on model described in:
// Mendez, D., Holton, J. M., Lyubimov, A. Y., Hollatz, S., Mathews, I. I., Cichosz, A., Martirosyan, V.,
// Zeng, T., Stofer, R., Liu, R., Song, J., McPhillips, S., Soltis, M. & Cohen, A. E. (2024).
// Acta Cryst. D80, 26-43.
class NeuralNetResPredictor {
std::vector<float> model_input;
bool enable;
#ifdef JFJOCH_USE_TORCH
torch::Device device;
torch::jit::script::Module module;
#endif
template<class T>
void PrepareInternal(const DiffractionExperiment& experiment, const T* image);
public:
explicit NeuralNetResPredictor(const std::string& model_path);
void Prepare(const DiffractionExperiment& experiment, const int16_t* image);
void Prepare(const DiffractionExperiment& experiment, const int32_t* image);
std::optional<float> Inference(const DiffractionExperiment& experiment, const void* image);
size_t GetMaxPoolFactor(const DiffractionExperiment& experiment) const;
const std::vector<float> &GetModelInput() const;
};
#endif //JUNGFRAUJOCH_NEURALNETRESPREDICTOR_H

51
resonet/resonet_test.cpp Normal file
View File

@@ -0,0 +1,51 @@
// Copyright (2019-2024) Paul Scherrer Institute
#include <iostream>
#include "NeuralNetResPredictor.h"
#include "../writer/HDF5Objects.h"
int main(int argc, char **argv) {
if (argc != 2) {
std::cerr << "Usage ./resonet_test <.pt file with traced model>" << std::endl;
exit(EXIT_FAILURE);
}
RegisterHDF5Filter();
DiffractionExperiment experiment(DetectorGeometry(8, 2, 8, 36));
experiment.DetectorDistance_mm(75).PhotonEnergy_keV(12.4).BeamY_pxl(1136).BeamX_pxl(1090);
NeuralNetResPredictor predictor(argv[1]);
HDF5ReadOnlyFile data("../../tests/test_data/compression_benchmark.h5");
HDF5DataSet dataset(data, "/entry/data/data");
HDF5DataSpace file_space(dataset);
std::vector<int16_t> image_conv (file_space.GetDimensions()[1] * file_space.GetDimensions()[2]);
std::vector<hsize_t> start = {4,0,0};
std::vector<hsize_t> file_size = {1, file_space.GetDimensions()[1], file_space.GetDimensions()[2]};
dataset.ReadVector(image_conv, start, file_size);
std::cout << "Max pooling " << predictor.GetMaxPoolFactor(experiment) << std::endl;
float x = 0;
size_t niterations = 25;
std::optional<float> result;
auto start_time = std::chrono::system_clock::now();
for (int i = 0; i < niterations; i++) {
result = predictor.Inference(experiment, image_conv.data());
if (result)
x += result.value();
}
auto end_time = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
int64_t frequency_Hz = (niterations * 1e6) / (double) (elapsed.count());
std::cout << ((double) elapsed.count()) / (1e3 * niterations) << " ms" << std::endl;
std::cout << frequency_Hz << " Hz" << std::endl;
if (result)
std::cout << "Resolution " << result.value() << std::endl;
}

Binary file not shown.

View File

@@ -1,4 +1,4 @@
ADD_EXECUTABLE(jfjoch_test
ADD_EXECUTABLE(CatchTest
DiffractionExperimentTest.cpp
RawToConvertedGeometryTest.cpp
../common/RawToConvertedGeometry.h
@@ -30,6 +30,7 @@ ADD_EXECUTABLE(jfjoch_test
TIFFTest.cpp
JFJochReceiverProcessingTest.cpp
JPEGTest.cpp
NeuralNetResPredictorTest.cpp
HistogramTest.cpp
FPGAEigerReorderTest.cpp
ROIMapTest.cpp
@@ -39,5 +40,7 @@ ADD_EXECUTABLE(jfjoch_test
RegressionTest.cpp
)
target_link_libraries(jfjoch_test Catch2WithMain JFJochBroker JFJochReceiver JFJochWriter JFJochImageAnalysis JFJochCommon JFJochHLSSimulation JFJochPreview)
target_include_directories(jfjoch_test PRIVATE .)
target_link_libraries(CatchTest Catch2WithMain JFJochBroker JFJochReceiver JFJochWriter JFJochImageAnalysis JFJochCommon JFJochHLSSimulation JFJochPreview JFJochResonet)
target_include_directories(CatchTest PRIVATE .)
INSTALL(TARGETS CatchTest RUNTIME)

View File

@@ -188,6 +188,73 @@ TEST_CASE("GenerateResolutionMap") {
experiment.GetYPixelsNum(), 4);
}
TEST_CASE("JFJochIntegrationTest_ZMQ_lysozyme_resolution", "[JFJochReceiver]") {
Logger logger("JFJochIntegrationTest_ZMQ_lysozyme_resolution");
RegisterHDF5Filter();
const uint16_t nthreads = 4;
DiffractionExperiment experiment(DetectorGeometry(8,2,8,36));
experiment.ImagesPerTrigger(5).NumTriggers(1).UseInternalPacketGenerator(true)
.FilePrefix("lyso_test_resolution").ConversionOnFPGA(false)
.DetectorDistance_mm(75).BeamY_pxl(1136).BeamX_pxl(1090).PhotonEnergy_keV(12.4)
.SetUnitCell(UnitCell{.a = 36.9, .b = 78.95, .c = 78.95, .alpha =90, .beta = 90, .gamma = 90})
.NeuralNetModelPath("../../resonet/traced_resnet_model.pt");
// Load example image
HDF5ReadOnlyFile data("../../tests/test_data/compression_benchmark.h5");
HDF5DataSet dataset(data, "/entry/data/data");
HDF5DataSpace file_space(dataset);
REQUIRE(file_space.GetDimensions()[2] == experiment.GetXPixelsNum());
REQUIRE(file_space.GetDimensions()[1] == experiment.GetYPixelsNum());
std::vector<int16_t> image_conv (file_space.GetDimensions()[1] * file_space.GetDimensions()[2]);
std::vector<hsize_t> start = {4,0,0};
std::vector<hsize_t> file_size = {1, file_space.GetDimensions()[1], file_space.GetDimensions()[2]};
dataset.ReadVector(image_conv, start, file_size);
std::vector<int16_t> image_raw_geom(experiment.GetModulesNum() * RAW_MODULE_SIZE);
ConvertedToRawGeometry(experiment, image_raw_geom.data(), image_conv.data());
logger.Info("Loaded image");
// Setup acquisition device
AcquisitionDeviceGroup aq_devices;
std::unique_ptr<HLSSimulatedDevice> test = std::make_unique<HLSSimulatedDevice>(0, 64);
for (int m = 0; m < experiment.GetModulesNum(); m++)
test->SetInternalGeneratorFrame((uint16_t *) image_raw_geom.data() + m * RAW_MODULE_SIZE, m);
aq_devices.Add(std::move(test));
ZMQContext context;
ZMQStream2Pusher pusher(context, "ipc://*");
StreamWriter writer(context, logger, pusher.GetAddress());
auto writer_future = std::async(std::launch::async, &StreamWriter::Run, &writer);
context.NumThreads(4);
JFJochReceiverService service(aq_devices, logger, pusher);
service.NumThreads(nthreads);
SpotFindingSettings settings = DiffractionExperiment::DefaultDataProcessingSettings();
settings.signal_to_noise_threshold = 2.5;
settings.photon_count_threshold = 5;
settings.min_pix_per_spot = 1;
settings.max_pix_per_spot = 200;
service.SetSpotFindingSettings(settings);
service.Start(experiment, nullptr);
auto receiver_out = service.Stop();
CHECK(receiver_out.efficiency == 1.0);
CHECK(receiver_out.status.indexing_rate == 1.0);
CHECK(receiver_out.status.images_sent == experiment.GetImageNum());
CHECK(!receiver_out.status.cancelled);
REQUIRE_NOTHROW(writer_future.get());
}
TEST_CASE("JFJochIntegrationTest_ZMQ_ROI", "[JFJochReceiver]") {
Logger logger("JFJochIntegrationTest_ZMQ_ROI");

View File

@@ -0,0 +1,51 @@
// Copyright (2019-2024) Paul Scherrer Institute
#include <catch2/catch_all.hpp>
#include "../writer/HDF5Objects.h"
#include "../resonet/NeuralNetResPredictor.h"
TEST_CASE("NeuralNetResPredictor_Prepare", "[LinearAlgebra][Coord]") {
DiffractionExperiment experiment(DetectorGeometry(8, 2, 8, 36));
experiment.DetectorDistance_mm(75).PhotonEnergy_keV(12.4).BeamX_pxl(1000).BeamY_pxl(1000);
std::vector<int16_t> v(experiment.GetPixelsNum(),0);
v[1000 * experiment.GetXPixelsNum() + 1000] = 100;
v[1000 * experiment.GetXPixelsNum() + 1001] = 20;
v[1001 * experiment.GetXPixelsNum() + 1000] = 30;
v[1001 * experiment.GetXPixelsNum() + 1001] = INT16_MIN;
v[1050 * experiment.GetXPixelsNum() + 1050] = 52;
v[2000 * experiment.GetXPixelsNum() + 1500] = 160;
NeuralNetResPredictor predictor("../../resonet/traced_resnet_model.pt");
REQUIRE(predictor.GetMaxPoolFactor(experiment) == 2);
predictor.Prepare(experiment, v.data());
auto nn_input = predictor.GetModelInput();
REQUIRE(nn_input[0] == 10);
REQUIRE(nn_input[25 * 512 + 25] == 7);
REQUIRE(nn_input[500 * 512 + 250] == 12);
}
TEST_CASE("NeuralNetResPredictor_Inference", "[LinearAlgebra][Coord]") {
DiffractionExperiment experiment(DetectorGeometry(8, 2, 8, 36));
experiment.DetectorDistance_mm(75).PhotonEnergy_keV(12.4).BeamY_pxl(1136).BeamX_pxl(1090);
NeuralNetResPredictor predictor("../../resonet/traced_resnet_model.pt");
HDF5ReadOnlyFile data("../../tests/test_data/compression_benchmark.h5");
HDF5DataSet dataset(data, "/entry/data/data");
HDF5DataSpace file_space(dataset);
std::vector<int16_t> image_conv (file_space.GetDimensions()[1] * file_space.GetDimensions()[2]);
std::vector<hsize_t> start = {4,0,0};
std::vector<hsize_t> file_size = {1, file_space.GetDimensions()[1], file_space.GetDimensions()[2]};
dataset.ReadVector(image_conv, start, file_size);
auto res = predictor.Inference(experiment, image_conv.data());
REQUIRE(res);
REQUIRE(res.value() < 1.5);
REQUIRE(res.value() > 1.4);
}

View File

@@ -21,7 +21,8 @@ ADD_LIBRARY(JFJochWriter STATIC
HDF5DataFilePluginAzInt.h
HDF5DataFilePluginJUNGFRAU.cpp
HDF5DataFilePluginJUNGFRAU.h
)
HDF5DataFilePluginResEstimation.cpp
HDF5DataFilePluginResEstimation.h)
TARGET_LINK_LIBRARIES(JFJochWriter JFJochHDF5Wrappers JFJochCommon CBORStream2FrameSerialize)

View File

@@ -8,6 +8,7 @@
#include "HDF5DataFilePluginMX.h"
#include "HDF5DataFilePluginXFEL.h"
#include "HDF5DataFilePluginJUNGFRAU.h"
#include "HDF5DataFilePluginResEstimation.h"
#include "../include/spdlog/fmt/fmt.h"
HDF5DataFile::HDF5DataFile(const std::string &in_filename,
@@ -26,6 +27,7 @@ HDF5DataFile::HDF5DataFile(const std::string &in_filename,
plugins.emplace_back(std::make_unique<HDF5DataFilePluginJUNGFRAU>());
plugins.emplace_back(std::make_unique<HDF5DataFilePluginAzInt>(in_rad_int_bin_to_q));
plugins.emplace_back(std::make_unique<HDF5DataFilePluginXFEL>());
plugins.emplace_back(std::make_unique<HDF5DataFilePluginResEstimation>());
plugins.emplace_back(std::make_unique<HDF5DataFilePluginMX>(in_max_spots));
}

View File

@@ -0,0 +1,20 @@
// Copyright (2019-2024) Paul Scherrer Institute
#include "HDF5DataFilePluginResEstimation.h"
void HDF5DataFilePluginResEstimation::OpenFile(HDF5File &data_file, const DataMessage &msg) {}
void HDF5DataFilePluginResEstimation::Write(const DataMessage &msg, uint64_t image_number) {
if (!msg.resolution_estimation)
return;
if (image_number >= res_estimation.size())
res_estimation.resize(image_number + 1);
res_estimation[image_number] = msg.resolution_estimation.value();
}
void HDF5DataFilePluginResEstimation::WriteFinal(HDF5File &data_file) {
if (!res_estimation.empty())
data_file.SaveVector("/entry/res_estimation", res_estimation);
}

View File

@@ -0,0 +1,17 @@
// Copyright (2019-2024) Paul Scherrer Institute
#ifndef JUNGFRAUJOCH_HDF5DATAFILEPLUGINRESESTIMATION_H
#define JUNGFRAUJOCH_HDF5DATAFILEPLUGINRESESTIMATION_H
#include "HDF5DataFilePlugin.h"
class HDF5DataFilePluginResEstimation : public HDF5DataFilePlugin {
std::vector<float> res_estimation;
public:
void OpenFile(HDF5File &data_file, const DataMessage &msg) override;
void Write(const DataMessage &msg, uint64_t image_number) override;
void WriteFinal(HDF5File &data_file) override;
};
#endif //JUNGFRAUJOCH_HDF5DATAFILEPLUGINRESESTIMATION_H