Skip to content

Commit 7e7be7f

Browse files
authored
merge: Merge pull request #11 from DigitalHolography/feat/sliding-average-vsh
fix: skip deterministic VSH warm-up frames
2 parents 06d2841 + 8de1870 commit 7e7be7f

9 files changed

Lines changed: 91 additions & 19 deletions

File tree

doc/mkdocs/docs/holovibes/tasks/asyncs/sliding_average.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ The tensor must be 32-bit floating point (`float32`) data already located in dev
1414

1515
An optional second input may provide a host `uint8` scalar with shape `(1)`. A zero value discards
1616
the associated frame without advancing the averaging window; a nonzero value accepts it. This is
17-
used to suppress invalid warm-up frames from centered correction pipelines.
17+
available for data-dependent filtering.
18+
19+
`discard_first` deterministically discards the requested number of initial accepted frames without
20+
advancing the averaging window. The VSH correction pipeline uses this to suppress its known
21+
`window_size - 1` alignment warm-up without adding a validity edge to the graph.
1822

1923
## Outputs
2024
After the warm-up period required to enqueue the first `window_size` frames, each pop exposes one tensor of shape `(1, H, W)` with the same dtype and memory location as the input.

src/holotask/include/holotask/asyncs/slide_avg.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace holotask::asyncs {
2828
struct SlidingAverageSettings {
2929
size_t target_capacity;
3030
size_t window_size;
31+
size_t discard_first = 0;
3132

3233
bool operator==(const SlidingAverageSettings &) const = default;
3334
};

src/holotask/src/asyncs/slide_avg.cu

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ namespace holotask::asyncs {
3636
template <typename T> using DevPtr = curaii::unique_device_ptr<T>;
3737

3838
void to_json(nlohmann::json &j, const SlidingAverageSettings &s) {
39-
j = nlohmann::json{{"target_capacity", s.target_capacity}, {"window_size", s.window_size}};
39+
j = nlohmann::json{{"target_capacity", s.target_capacity},
40+
{"window_size", s.window_size},
41+
{"discard_first", s.discard_first}};
4042
}
4143

4244
void from_json(const nlohmann::json &j, SlidingAverageSettings &s) {
4345
j.at("target_capacity").get_to(s.target_capacity);
4446
j.at("window_size").get_to(s.window_size);
47+
s.discard_first = j.value("discard_first", size_t{0});
4548
}
4649

4750
namespace {
@@ -85,6 +88,7 @@ private:
8588
size_t element_size_;
8689
DevPtr<std::byte> d_buffer_;
8790
DevPtr<float> d_running_avg_;
91+
size_t discarded_ = 0;
8892
alignas(CACHE_LINE_SIZE) std::atomic<size_t> avg_idx_;
8993
alignas(CACHE_LINE_SIZE) std::atomic<size_t> write_idx_;
9094
alignas(CACHE_LINE_SIZE) std::atomic<size_t> read_idx_;
@@ -178,8 +182,12 @@ void SlidingAverage::release_output(int index) {
178182
}
179183

180184
holoflow::core::OpResult SlidingAverage::try_push(holoflow::core::AsyncPushCtx &ctx) {
181-
if (ctx.inputs.size() == 2 &&
182-
*reinterpret_cast<const std::uint8_t *>(ctx.inputs[1].data()) == std::uint8_t{0}) {
185+
const bool invalid = ctx.inputs.size() == 2 && *reinterpret_cast<const std::uint8_t *>(
186+
ctx.inputs[1].data()) == std::uint8_t{0};
187+
if (invalid || discarded_ < settings_.discard_first) {
188+
if (!invalid) {
189+
++discarded_;
190+
}
183191
storage_access().owned_input_storage(0).ptr = nullptr;
184192
return holoflow::core::OpResult::Ok;
185193
}

src/holovibes/schemas/tasks/asyncs/sliding_average_settings.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
"type": "integer",
1515
"minimum": 1,
1616
"description": "Number of frames included in each running average. Must satisfy:\n- window_size > 0"
17+
},
18+
"discard_first": {
19+
"type": "integer",
20+
"minimum": 0,
21+
"default": 0,
22+
"description": "Number of initial accepted input frames to discard before filling the averaging window."
1723
}
1824
},
1925
"required": [
@@ -23,7 +29,8 @@
2329
"examples": [
2430
{
2531
"target_capacity": 32,
26-
"window_size": 8
32+
"window_size": 8,
33+
"discard_first": 0
2734
}
2835
]
2936
}

src/holovibes/src/pipeline/graph_builder.cc

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,8 @@ holoflow::core::GraphSpec GraphBuilder::build() {
133133
.target_capacity = std::max<size_t>(2, 2 * FH.shape.at(0)),
134134
.window_size = static_cast<size_t>(s_.pp_accumulation),
135135
});
136-
TDesc FH_current = timing_outputs.at(0);
137-
const TDesc FH_delayed = timing_outputs.at(1);
138-
std::optional<TDesc> valid;
136+
TDesc FH_current = timing_outputs.at(0);
137+
const TDesc FH_delayed = timing_outputs.at(1);
139138

140139
if (s_.autofocus_enabled) {
141140
if (s_.autofocus_nb_iter <= 0) {
@@ -146,7 +145,6 @@ holoflow::core::GraphSpec GraphBuilder::build() {
146145
"sliding Shack-Hartmann correction only supports one autofocus iteration");
147146
}
148147

149-
valid = timing_outputs.at(2);
150148
ShackHartmannIterationState shack_hartmann_iteration_state;
151149
for (int pass = 0; pass < s_.autofocus_nb_iter; ++pass) {
152150
const TDesc &delayed = pass == 0 ? FH_delayed : FH_current;
@@ -157,7 +155,7 @@ holoflow::core::GraphSpec GraphBuilder::build() {
157155

158156
TDesc FH_z = build_spatial_propagation(FH_current);
159157

160-
build_xy_view(FH_z, valid);
158+
build_xy_view(FH_z);
161159

162160
if (s_.view_3d_cuts) {
163161
build_3d_cuts(FH_z);
@@ -473,6 +471,7 @@ GraphBuilder::build_shack_hartmann(const TDesc &FH_current, const TDesc &FH_dela
473471
s_.autofocus_zernike_orders,
474472
s_.signal_plot_time_window_seconds,
475473
s_.signal_plot_sample_time_seconds(),
474+
static_cast<size_t>(s_.pp_accumulation - 1),
476475
});
477476
}
478477

@@ -602,7 +601,7 @@ GraphBuilder::TDesc GraphBuilder::build_freq_weights() {
602601
return freqs;
603602
}
604603

605-
void GraphBuilder::build_xy_view(const TDesc &FH_z, const std::optional<TDesc> &valid) {
604+
void GraphBuilder::build_xy_view(const TDesc &FH_z) {
606605
using Target = holotask::syncs::ConversionSettings::Target;
607606
using Strat = holotask::syncs::ConversionSettings::Strategy;
608607
auto Host = holotask::syncs::MemcpySettings::Target::Host;
@@ -649,13 +648,10 @@ void GraphBuilder::build_xy_view(const TDesc &FH_z, const std::optional<TDesc> &
649648
const holotask::asyncs::SlidingAverageSettings slide_settings{
650649
.target_capacity = static_cast<size_t>(std::max(1, s_.gpu_out_size)),
651650
.window_size = static_cast<size_t>(s_.pp_accumulation),
651+
.discard_first =
652+
s_.autofocus_enabled ? static_cast<size_t>(s_.pp_accumulation - 1) : size_t{0},
652653
};
653-
if (valid.has_value()) {
654-
auto stable_valid = memcpy(*valid, {holotask::syncs::MemcpySettings::Target::Host});
655-
result = slide_avg(result, stable_valid, slide_settings);
656-
} else {
657-
result = slide_avg(result, slide_settings);
658-
}
654+
result = slide_avg(result, slide_settings);
659655

660656
if (s_.pp_convolution) {
661657
throw std::logic_error{"Convolution is currently not supported"};

src/holovibes/src/pipeline/graph_builder.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private:
6767
ShackHartmannIterationState &iteration_state);
6868
TDesc build_spatial_propagation(const TDesc &FH);
6969
TDesc build_spatial_filter(const TDesc &FH_z);
70-
void build_xy_view(const TDesc &FH_z, const std::optional<TDesc> &valid);
70+
void build_xy_view(const TDesc &FH_z);
7171
void build_3d_cuts(const TDesc &FH_z);
7272
TDesc build_freq_weights();
7373

src/holovibes/src/tasks/sinks/display_signal_history.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ class DisplaySignalHistoryTask : public holoflow::core::ISyncTask {
130130
}
131131

132132
holoflow::core::OpResult execute(holoflow::core::SyncCtx &ctx) override {
133+
if (received_sample_index_++ < settings_.discard_first) {
134+
return holoflow::core::OpResult::Ok;
135+
}
136+
133137
auto &input = ctx.inputs[0];
134138
const auto &desc = input.desc;
135139
const auto count = settings_.indexes.size();
@@ -173,7 +177,8 @@ class DisplaySignalHistoryTask : public holoflow::core::ISyncTask {
173177
holoflow::core::TDesc idesc_;
174178
std::shared_ptr<SignalHistoryDispatcher> dispatcher_;
175179
cudaStream_t stream_;
176-
uint64_t valid_sample_index_ = 0;
180+
uint64_t received_sample_index_ = 0;
181+
uint64_t valid_sample_index_ = 0;
177182
};
178183

179184
} // namespace
@@ -183,13 +188,15 @@ void to_json(nlohmann::json &j, const DisplaySignalHistorySettings &settings) {
183188
{"indexes", settings.indexes},
184189
{"time_window_seconds", settings.time_window_seconds},
185190
{"sample_time_seconds", settings.sample_time_seconds},
191+
{"discard_first", settings.discard_first},
186192
};
187193
}
188194

189195
void from_json(const nlohmann::json &j, DisplaySignalHistorySettings &settings) {
190196
j.at("indexes").get_to(settings.indexes);
191197
j.at("time_window_seconds").get_to(settings.time_window_seconds);
192198
j.at("sample_time_seconds").get_to(settings.sample_time_seconds);
199+
settings.discard_first = j.value("discard_first", size_t{0});
193200
}
194201

195202
DisplaySignalHistoryFactory::DisplaySignalHistoryFactory(

src/holovibes/src/tasks/sinks/display_signal_history.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ struct DisplaySignalHistorySettings {
3838
// acquisition or pipeline time, not processing completion or GUI refresh time.
3939
double sample_time_seconds = 1.0 / 15.0;
4040

41+
// Known pipeline warm-up samples are discarded before the logical plot timeline starts.
42+
size_t discard_first = 0;
43+
4144
bool operator==(const DisplaySignalHistorySettings &) const = default;
4245
};
4346

test/holotask/sliding_average_test.cu

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,50 @@ TEST(SlidingAverageTest, DiscardsInvalidInputsBeforeFullWindowWarmup) {
246246
EXPECT_EQ(output_count, 2);
247247
}
248248

249+
TEST(SlidingAverageTest, DiscardsConfiguredInitialFramesWithoutValidityInput) {
250+
const TDesc image_desc({1, 1, 1}, DType::F32, MemLoc::Device);
251+
const std::array input_descs{image_desc};
252+
const holotask::asyncs::SlidingAverageSettings settings{
253+
.target_capacity = 4,
254+
.window_size = 3,
255+
.discard_first = 2,
256+
};
257+
holotask::asyncs::SlidingAverageFactory factory;
258+
const auto infer = factory.infer(input_descs, nlohmann::json(settings));
259+
260+
curaii::CudaStream producer_stream;
261+
curaii::CudaStream consumer_stream;
262+
auto task = factory.create(input_descs, nlohmann::json(settings),
263+
{producer_stream.get(), consumer_stream.get()});
264+
task->bind_logger(spdlog::default_logger());
265+
TestStorageAccess storage_access(infer.input_descs, infer.output_descs);
266+
task->bind_storage_access(&storage_access);
267+
268+
std::array output_views{TView{infer.output_descs[0], &storage_access.owned_output_storage(0)}};
269+
std::atomic<bool> cancelled{false};
270+
holoflow::core::AsyncPopCtx pop_ctx{output_views, &cancelled};
271+
const std::array values{100.0f, 200.0f, 3.0f, 5.0f, 7.0f};
272+
273+
for (size_t i = 0; i < values.size(); ++i) {
274+
auto acquired = task->acquire_input(0);
275+
ASSERT_TRUE(acquired.has_value());
276+
CUDA_CHECK(cudaMemcpy(acquired->data(), &values[i], sizeof(float), cudaMemcpyHostToDevice));
277+
std::array input_views{*acquired};
278+
holoflow::core::AsyncPushCtx push_ctx{input_views, &cancelled};
279+
ASSERT_EQ(task->try_push(push_ctx), OpResult::Ok);
280+
281+
const auto pop_result = task->try_pop(pop_ctx);
282+
if (i + 1 < values.size()) {
283+
EXPECT_EQ(pop_result, OpResult::NotReady);
284+
continue;
285+
}
286+
287+
ASSERT_EQ(pop_result, OpResult::Ok);
288+
float actual = 0.0f;
289+
CUDA_CHECK(cudaMemcpy(&actual, output_views[0].data(), sizeof(float), cudaMemcpyDeviceToHost));
290+
EXPECT_FLOAT_EQ(actual, 5.0f);
291+
task->release_output(0);
292+
}
293+
}
294+
249295
} // namespace

0 commit comments

Comments
 (0)