From f74e71f7288a2fd0b1110d413bd3caaaae98b374 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 May 2026 00:04:06 +0200 Subject: [PATCH] Implementing averaging window for density map --- .../lay_plugin/DensityMapDialog.ui | 90 +++++++-- .../lay_plugin/layDensityMapDialog.cc | 177 +++++++++++++++++- .../lay_plugin/layDensityMapDialog.h | 12 ++ 3 files changed, 265 insertions(+), 14 deletions(-) diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui index ae4712e76..ded9b81ce 100644 --- a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -7,7 +7,7 @@ 0 0 657 - 425 + 480 @@ -64,6 +64,16 @@ + + + + + 0 + 0 + + + + @@ -71,6 +81,33 @@ + + + + + 0 + 0 + + + + 1 + + + + + + + Averaging window size + + + + + + + Threads + + + @@ -84,24 +121,51 @@ - - + + + + µm (a multiple of the pixel size or empty) + + + + + + + Boundary mode + + + + + 0 0 - - 1 - - - - - - - Threads + + QComboBox::AdjustToContents + + + Periodic (repeat region seamlessly) + + + + + Use zero density outside + + + + + Use 100% density outside + + + + + Use average density outside + + @@ -473,7 +537,7 @@ Qt::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc index 151706176..b6e280797 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -58,9 +58,19 @@ static const std::vector region_modes = { region_mode_by_rulers }; +static const std::string boundary_mode_periodic ("boundary-mode-periodic"); +static const std::string boundary_mode_zero ("boundary-mode-zero"); +static const std::string boundary_mode_one ("boundary-mode-one"); +static const std::string boundary_mode_average ("boundary-mode-average"); + +// items in boundary_mode_cb +static const std::vector boundary_modes = { boundary_mode_periodic, boundary_mode_zero, boundary_mode_one, boundary_mode_average }; + static const std::string cfg_density_map_region_mode ("density-map-region-mode"); static const std::string cfg_density_map_layer_mode ("density-map-layer-mode"); static const std::string cfg_density_map_pixel_size ("density-map-pixel-size"); +static const std::string cfg_density_map_window_size ("density-map-window-size"); +static const std::string cfg_density_map_boundary_mode ("density-map-boundary-mode"); static const std::string cfg_density_map_threads ("density-map-threads"); static const std::string cfg_density_map_source_layer ("density-map-source-layer"); static const std::string cfg_density_map_box_layer ("density-map-box-layer"); @@ -78,6 +88,8 @@ public: options.push_back (std::make_pair (cfg_density_map_layer_mode, layer_mode_specific)); options.push_back (std::make_pair (cfg_density_map_region_mode, region_mode_global_bbox)); options.push_back (std::make_pair (cfg_density_map_pixel_size, "100")); + options.push_back (std::make_pair (cfg_density_map_window_size, "0")); + options.push_back (std::make_pair (cfg_density_map_boundary_mode, boundary_mode_periodic)); options.push_back (std::make_pair (cfg_density_map_threads, "1")); } @@ -269,6 +281,32 @@ DensityMapDialog::configure (const std::string &name, const std::string &value) } return true; + } else if (name == cfg_density_map_window_size) { + + double ws = 0.0; + try { + tl::from_string (value, ws); + if (ws > 0) { + le_window_size->setText (tl::to_qstring (tl::to_string (ws))); + } else { + le_window_size->setText (QString ()); + } + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_boundary_mode) { + + int mode = 0; + for (size_t i = 0; i < boundary_modes.size (); ++i) { + if (boundary_modes[i] == value) { + mode = int (i); + } + } + + cb_boundary_mode->setCurrentIndex (mode); + return true; + } else if (name == cfg_density_map_threads) { int thr = 1; @@ -335,9 +373,23 @@ DensityMapDialog::make_density_map () tl::from_string_ext (tl::to_string (le_pixel_size->text ()), par.pixel_size); if (par.pixel_size < 1e-6) { - throw tl::Exception (tl::to_string (QObject::tr ("Pixel size must be positive and not zero"))); + throw tl::Exception (tl::to_string (QObject::tr ("The pixel size must be positive and not zero"))); } + par.window_size = 0.0; + if (! le_window_size->text ().simplified ().isEmpty ()) { + tl::from_string_ext (tl::to_string (le_window_size->text ()), par.window_size); + if (par.window_size < 1e-6) { + throw tl::Exception (tl::to_string (QObject::tr ("The window size must be positive and not zero or empty"))); + } + } + + int boundary_mode = cb_boundary_mode->currentIndex (); + if (boundary_mode < 0 || boundary_mode >= int (boundary_modes.size ())) { + return; + } + par.boundary_mode = boundary_modes [boundary_mode]; + int region_mode = region_cb->currentIndex (); if (region_mode < 0 || region_mode >= int (region_modes.size ())) { return; @@ -461,8 +513,10 @@ DensityMapDialog::make_density_map () // Commit the parameters dispatcher ()->config_set (cfg_density_map_layer_mode, lm); dispatcher ()->config_set (cfg_density_map_region_mode, rm); + dispatcher ()->config_set (cfg_density_map_boundary_mode, par.boundary_mode); dispatcher ()->config_set (cfg_density_map_threads, tl::to_string (par.threads)); dispatcher ()->config_set (cfg_density_map_pixel_size, tl::to_string (par.pixel_size)); + dispatcher ()->config_set (cfg_density_map_window_size, tl::to_string (par.window_size)); if (lm == layer_mode_specific) { dispatcher ()->config_set (cfg_density_map_source_layer, source_layer.to_string ()); @@ -584,6 +638,127 @@ DensityMapDialog::compute_density_map (const DensityMapParameters &par) // Execute the tiling processor tp.execute (tl::to_string (tr ("Computing density map"))); + + // Do the averaging if requested + + unsigned int nw = (unsigned int) (std::max (0.0, std::min (1000.0, floor (par.window_size / par.pixel_size + 0.5)))); + + if (nw > 1) { + average_window (*img_object, par.boundary_mode, nw); + } +} + +inline int safe_mod (int a, int b) +{ + if (a < 0) { + return b - (-a % b); + } else { + return a % b; + } +} + +void +DensityMapDialog::average_window (img::Object &img_object, const std::string boundary_mode, int nw) +{ + bool periodic = (boundary_mode == boundary_mode_periodic); + + int nx = img_object.width (); + int ny = img_object.height (); + + // compute the outside value if not periodic + double outside = 0.0; + if (boundary_mode == boundary_mode_one) { + + outside = 1.0; + + } else if (boundary_mode == boundary_mode_average) { + + double outside = 0.0; + const float *d = img_object.float_data (); + for (size_t i = size_t (nx) * size_t (ny); i > 0; --i) { + outside += *d++; + } + outside /= double (nx) * double (ny); + + } + + std::vector vavg_data; + vavg_data.resize (size_t (nx) * size_t (ny), 0.0); + + // vertical sum + + for (int y = 0; y < ny; ++y) { + + int wh = nw / 2; + double fb = (nw % 2 == 0) ? 0.5 : 1.0; + + for (int dy = -wh; dy <= wh; ++dy) { + + // top and bottom row count half in case of even nw + double f = (dy == -wh || dy == wh) ? fb : 1.0; + + std::vector::iterator d = vavg_data.begin () + y * nx; + if (periodic || (y + dy >= 0 && y + dy < ny)) { + const float *s = img_object.float_data () + safe_mod (y + dy, ny) * nx; + for (int ix = 0; ix < nx; ++ix) { + *d++ += f * *s++; + } + } else { + for (int ix = 0; ix < nx; ++ix) { + *d++ += f * outside; + } + } + + } + + } + + // horizontal sum + + outside *= nw; // because we do normalization later + + // TODO: transposing the image would make things more efficient + + std::vector havg_data; + havg_data.resize (size_t (nx) * size_t (ny), 0.0); + + for (int x = 0; x < nx; ++x) { + + int wh = nw / 2; + double fb = (nw % 2 == 0) ? 0.5 : 1.0; + + for (int dx = -wh; dx <= wh; ++dx) { + + // top and bottom row count half in case of even nw + double f = (dx == -wh || dx == wh) ? fb : 1.0; + + std::vector::iterator d = havg_data.begin () + x; + + if (periodic || (x + dx >= 0 && x + dx < nx)) { + std::vector::const_iterator s = vavg_data.begin () + safe_mod (x + dx, nx); + for (int iy = 0; iy < ny; ++iy) { + *d += f * *s; + d += nx; + s += nx; + } + } else { + for (int iy = 0; iy < ny; ++iy) { + *d += f * outside; + d += nx; + } + } + + } + + } + + // take the average + double s = 1.0 / (double (nw) * double (nw)); + for (auto i = havg_data.begin (); i != havg_data.end (); ++i) { + *i *= s; + } + + img_object.set_data (nx, ny, havg_data); } } diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h index b46f19539..bd99bbbe8 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -30,6 +30,11 @@ #include "layBrowser.h" #include "layMarker.h" +namespace img +{ + class Object; +} + namespace lay { @@ -65,6 +70,12 @@ private: // The pixel size double pixel_size; + // The window size or zero for "no window" + double window_size; + + // The boundary mode + std::string boundary_mode; + // The number of threads to use int threads; }; @@ -77,6 +88,7 @@ private: void make_density_map (); void compute_density_map (const DensityMapParameters &par); + void average_window (img::Object &img_object, const std::string boundary_mode, int nw); };