From bf6a5ee490a828271d54e8e3a93efaa79f3f6a6a Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Jul 2024 16:54:24 +0100 Subject: [PATCH 1/8] Removes incorrect \param message --- morph/HexGrid.h | 1 - 1 file changed, 1 deletion(-) diff --git a/morph/HexGrid.h b/morph/HexGrid.h index 4dc14699..535ab89b 100644 --- a/morph/HexGrid.h +++ b/morph/HexGrid.h @@ -1448,7 +1448,6 @@ namespace morph { * \param image_pixelwidth (input) The number of pixels that the image is wide * \param image_scale (input) The size that the image should be resampled to (same units as HexGrid) * \param image_offset (input) An offset in HexGrid units to shift the image wrt to the HexGrid's origin - * \param sigma (input) The sigma for the 2D resampling Gaussian * * \return A new data vvec containing the resampled (and renormalised) hex pixel values */ From c939ca8fa44ca2160c65716cc826ead0bfefd44b Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Jul 2024 16:54:47 +0100 Subject: [PATCH 2/8] Actually plot on a rectangle --- examples/hexgrid_image_rect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hexgrid_image_rect.cpp b/examples/hexgrid_image_rect.cpp index 0d6a58ce..b00dc2d6 100644 --- a/examples/hexgrid_image_rect.cpp +++ b/examples/hexgrid_image_rect.cpp @@ -21,7 +21,7 @@ int main() morph::Visual v(1600, 1000, "Demo of HexGrid::resampleImage"); morph::HexGrid hg(0.01f, 3.0f, 0.0f); - hg.setCircularBoundary (1.2f); + hg.setRectangularBoundary (2.0f, 0.5f); // Load a rectangular image with the help of morph::loadpng(). std::string fn = "../examples/bike256_65.png"; From 10aff69c1ce07cd2098e21d1489e411a43592a47 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 18 Jul 2024 16:55:26 +0100 Subject: [PATCH 3/8] WIP. Haven't figured the scaling out --- examples/CMakeLists.txt | 6 +++ examples/grid_resampled_image.cpp | 90 +++++++++++++++++++++++++++++++ morph/Grid.h | 59 ++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 examples/grid_resampled_image.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 42e14cef..56e06972 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -155,6 +155,12 @@ if(ARMADILLO_FOUND) target_link_libraries(grid_image GLEW::GLEW) endif() + add_executable(grid_resampled_image grid_resampled_image.cpp) + target_link_libraries(grid_resampled_image OpenGL::GL glfw Freetype::Freetype) + if(USE_GLEW) + target_link_libraries(grid_resampled_image GLEW::GLEW) + endif() + add_executable(grid_layout grid_layout.cpp) add_executable(hsvwheel hsvwheel.cpp) diff --git a/examples/grid_resampled_image.cpp b/examples/grid_resampled_image.cpp new file mode 100644 index 00000000..a2154526 --- /dev/null +++ b/examples/grid_resampled_image.cpp @@ -0,0 +1,90 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + morph::Visual v(1600, 1000, "Demo of Grid showing a resampled image"); + v.setSceneTrans (morph::vec({-2.60233f, 1.32237f, -6.5f})); + + morph::vec dx = { 0.02f, 0.02f }; + morph::vec dx2 = { 0.04f, 0.04f }; + morph::vec nul = { 0.0f, 0.0f }; + // Top left to bottom right order matches image loaded by loadpng and avoids the need for a + // vec flip arg to morph::loadpng. + morph::Grid g1(256U, 65U, dx, nul, morph::GridDomainWrap::Horizontal); + + // Resample onto a lower res grid + morph::Grid g2(128U, 32U, dx2, nul, morph::GridDomainWrap::Horizontal); + + // Load an image + std::string fn = "../examples/bike256_65.png"; + morph::vvec image_data_bltr; + morph::vec dims = morph::loadpng (fn, image_data_bltr, morph::vec{false,true}); + std::cout << "Image dims: " << dims << std::endl; + + if (dims[0] != 256) { + std::cerr << "Wrong image width!\n"; + return -1; + } + + // Resample + // This controls how large the photo will be on the HexGrid + morph::vec image_scale = {2.0f, 2.0f}; + // You can shift the photo with an offset if necessary + morph::vec image_offset = {0.0f, 0.0f}; + + morph::vvec img_resampled = g2.resample_image (image_data_bltr, dims[0], image_scale, image_offset); + + // Resample to HexGrid too, for good measure + morph::HexGrid hg(0.01f, 3.0f, 0.0f); + hg.setRectangularBoundary (2.0f, 0.5f); + // Here's the HexGrid method that will resample the square pixel grid onto the hex grid + morph::vvec hex_image_data = hg.resampleImage (image_data_bltr, dims[0], image_scale, image_offset); + + // Visualise original with a GridVisual + auto gv1 = std::make_unique>(&g1, morph::vec({0,0,0})); + v.bindmodel (gv1); + gv1->gridVisMode = morph::GridVisMode::RectInterp; + gv1->setScalarData (&image_data_bltr); + gv1->cm.setType (morph::ColourMapType::GreyscaleInv); // inverse greyscale is good for a monochrome image + gv1->zScale.setParams (0, 0); // As it's an image, we don't want relief, so set the zScale to have a zero gradient + gv1->addLabel ("Original", {0, 0.2, 0}, morph::TextFeatures(0.1f)); + gv1->finalize(); + v.addVisualModel (gv1); + + // Visualize resampled + auto gv2 = std::make_unique>(&g2, morph::vec({0,-2,0})); + v.bindmodel (gv2); + gv2->gridVisMode = morph::GridVisMode::RectInterp; + gv2->setScalarData (&img_resampled); + gv2->cm.setType (morph::ColourMapType::GreyscaleInv); + gv2->zScale.setParams (0, 0); + gv2->addLabel ("Resampled", {0, 0.2, 0}, morph::TextFeatures(0.1f)); + gv2->finalize(); + v.addVisualModel (gv2); + + auto hgv = std::make_unique>(&hg, morph::vec({1,-4,0})); + v.bindmodel (hgv); + hgv->setScalarData (&hex_image_data); + hgv->cm.setType (morph::ColourMapType::GreyscaleInv); + hgv->zScale.setParams (0, 0); + hgv->addLabel ("Resampled to hex", {-1, 0.5, 0}, morph::TextFeatures(0.1f)); + hgv->finalize(); + v.addVisualModel (hgv); + + v.keepOpen(); + + return 0; +} diff --git a/morph/Grid.h b/morph/Grid.h index b4b492ac..c2af5426 100644 --- a/morph/Grid.h +++ b/morph/Grid.h @@ -587,6 +587,65 @@ namespace morph { return index < n ? index / h : std::numeric_limits::max(); } + /*! + * Resampling function (monochrome). + * + * \param image_data (input) The monochrome image as a vvec of floats. + * \param image_pixelwidth (input) The number of pixels that the image is wide + * \param image_scale (input) The size that the image should be resampled to (same units as Grid) + * \param image_offset (input) An offset in Grid units to shift the image wrt to the Grid's origin + * \param sigma (input) The sigma for the 2D resampling Gaussian + * + * \return A new data vvec containing the resampled (and renormalised) hex pixel values + */ + morph::vvec resample_image (const morph::vvec& image_data, + const unsigned int image_pixelwidth, + const morph::vec& image_scale, + const morph::vec& image_offset) const + { + unsigned int csz = image_data.size(); + morph::vec image_pixelsz = {image_pixelwidth, csz / image_pixelwidth}; + std::cout << "image_pixelsz = " << image_pixelsz << " csz: " << csz << std::endl; + // Distance per pixel in the image. This defines the Gaussian width (sigma) for the + // resample. Assume that the unscaled image pixels are square. Use the image width to + // set the distance per pixel (hence divide by image_scale by image_pixelsz[*0*]). + morph::vec dist_per_pix = image_scale / (image_pixelsz[0]-1u); + std::cout << "dist_per_pix = " << dist_per_pix << std::endl; + // This is an offset to centre the image wrt to the Grid + morph::vec input_centering_offset = dist_per_pix * image_pixelsz * morph::vec{1, 1}; + std::cout << "input_centering_offset = " << input_centering_offset << std::endl; + // Parameters for the Gaussian computation + morph::vec params = 1.0f / (2.0f * dist_per_pix * dist_per_pix); + morph::vec threesig = 3.0f * dist_per_pix; + + morph::vvec expr_resampled(this->w * this->h, 0.0f); +#pragma omp parallel for // parallel on this outer loop gives best result (5.8 s vs 7 s) + for (typename std::vector::size_type xi = 0u; xi < this->v_x.size(); ++xi) { + float expr = 0.0f; + for (unsigned int i = 0; i < csz; ++i) { + // Get x/y pixel coords: + morph::vec idx = {(i % image_pixelsz[0]), (image_pixelsz[1] - (i / image_pixelsz[0]))}; + // Get the coordinates of the input pixel at index idx (in Grid units): + morph::vec posn = (dist_per_pix * idx) + input_centering_offset + image_offset; + // Distance from input pixel to output pixel: + float _v_x = this->v_x[xi] - posn[0]; + float _v_y = this->v_y[xi] - posn[1]; + if (xi == 0) { + std::cout << "Pixel " << i << " has idx : " << idx << " and position " + << posn << " v_x/y: (" << _v_x << "," << _v_y << ")\n"; + } + // Compute contributions to each Grid pixel, using 2D (elliptical) Gaussian + if (_v_x < threesig[0] && _v_y < threesig[1]) { // Testing for distance gives slight speedup + expr += std::exp ( - ( (params[0] * _v_x * _v_x) + (params[1] * _v_y * _v_y) ) ) * image_data[i]; + } + } + expr_resampled[xi] = expr; + } + + expr_resampled /= expr_resampled.max(); // renormalise result + return expr_resampled; + } + //! Two vector structures that contains the coords for this grid. v_x is a vector of the x coordinates morph::vvec v_x; //! v_y is a vector of the y coordinates From 9c74b3db3cd7496aa036d36e29874ddaeb6c4c62 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 19 Jul 2024 11:45:51 +0100 Subject: [PATCH 4/8] Changed default flipping of loaded image data. --- morph/loadpng.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/morph/loadpng.h b/morph/loadpng.h index 0efc9df1..c2dbe614 100644 --- a/morph/loadpng.h +++ b/morph/loadpng.h @@ -29,10 +29,13 @@ namespace morph { * * If flip[1] is true, then flip the order of the rows to do an up/down flip of the * image during loading. + * + * Note: The default for flip is {false, true}, which means that by default, + * image_data will be filled in a bottom-left to top-right order. */ template static morph::vec loadpng (const std::string& filename, morph::vvec& image_data, - const morph::vec flip = {false, false}) + const morph::vec flip = {false, true}) { std::vector png; unsigned int w = 0; @@ -114,7 +117,7 @@ namespace morph { template static morph::vec loadpng (const std::string& filename, morph::vvec>& image_data, - const morph::vec flip = {false, false}) + const morph::vec flip = {false, true}) { std::vector png; unsigned int w = 0; @@ -196,7 +199,7 @@ namespace morph { // Load a colour PNG and return a vector of type T with elements ordered as RGBRGBRGB... template static morph::vec loadpng_rgb (const std::string& filename, morph::vvec& image_data, - const morph::vec flip = {false, false}) + const morph::vec flip = {false, true}) { std::vector png; unsigned int w = 0; @@ -261,7 +264,7 @@ namespace morph { // Load a colour PNG and return a vector of type T with elements ordered as RGBARGBARGBA... template static morph::vec loadpng_rgba (const std::string& filename, morph::vvec& image_data, - const morph::vec flip = {false, false}) + const morph::vec flip = {false, true}) { std::vector png; unsigned int w = 0; @@ -329,7 +332,7 @@ namespace morph { // Load a colour PNG and return a vector of type T with elements ordered as RGBARGBARGBA... template static morph::vec loadpng_rgba (const std::string& filename, morph::vec& image_data, - const morph::vec flip = {false, false}) + const morph::vec flip = {false, true}) { std::vector png; unsigned int w = 0; From bda0ec3493ac8bedd4739079af222006f4c88eba Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 19 Jul 2024 11:46:38 +0100 Subject: [PATCH 5/8] HexGrid resampling now assumes input image in bottom left to top right order --- morph/HexGrid.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/morph/HexGrid.h b/morph/HexGrid.h index 535ab89b..3c2822cd 100644 --- a/morph/HexGrid.h +++ b/morph/HexGrid.h @@ -1444,7 +1444,10 @@ namespace morph { /*! * Resampling function (monochrome). * - * \param image_data (input) The monochrome image as a vvec of floats. + * \param image_data (input) The monochrome image as a vvec of floats. The + * image is interpreted as running from bottom left to top right. Thus, the very + * first float in the vvec is at x=0, y=0. + * * \param image_pixelwidth (input) The number of pixels that the image is wide * \param image_scale (input) The size that the image should be resampled to (same units as HexGrid) * \param image_offset (input) An offset in HexGrid units to shift the image wrt to the HexGrid's origin @@ -1462,7 +1465,7 @@ namespace morph { // Distance per pixel in the image. This defines the Gaussian width (sigma) for the // resample. Assume that the unscaled image pixels are square. Use the image width to // set the distance per pixel (hence divide by image_scale by image_pixelsz[*0*]). - morph::vec dist_per_pix = image_scale / (image_pixelsz[0]-1u); + morph::vec dist_per_pix = image_scale / (image_pixelsz[0] - 1u); // This is an offset to centre the image wrt to the HexGrid morph::vec input_centering_offset = dist_per_pix * image_pixelsz * 0.5f; // Parameters for the Gaussian computation @@ -1473,10 +1476,9 @@ namespace morph { #pragma omp parallel for // parallel on this outer loop gives best result (5.8 s vs 7 s) for (typename std::vector::size_type xi = 0u; xi < this->d_x.size(); ++xi) { float expr = 0.0f; -//#pragma omp parallel for reduction(+:expr) for (unsigned int i = 0; i < csz; ++i) { // Get x/y pixel coords: - morph::vec idx = {(i % image_pixelsz[0]), (image_pixelsz[1] - (i / image_pixelsz[0]))}; + morph::vec idx = {(i % image_pixelsz[0]), (i / image_pixelsz[0])}; // Get the coordinates of the pixel at index i (in HexGrid units): morph::vec posn = (dist_per_pix * idx) - input_centering_offset + image_offset; // Distance from input pixel to output hex: From e1bb1fcbc86a7ecf8ce4fd7a38fb3771d2037500 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 19 Jul 2024 11:47:08 +0100 Subject: [PATCH 6/8] A fix for Grid::width()/height(). Improved resampling. --- examples/grid_image.cpp | 4 +-- examples/grid_resampled_image.cpp | 52 ++++++++++++++++++------------- examples/gridct_image.cpp | 4 +-- morph/Grid.h | 48 +++++++++++++++++----------- morph/Gridct.h | 8 ++--- tests/testGrid.cpp | 18 +++++++++++ 6 files changed, 86 insertions(+), 48 deletions(-) diff --git a/examples/grid_image.cpp b/examples/grid_image.cpp index ad5c7abd..005e2550 100644 --- a/examples/grid_image.cpp +++ b/examples/grid_image.cpp @@ -34,9 +34,9 @@ int main() // Load an image std::string fn = "../examples/bike256_65.png"; morph::vvec image_data_tlbr; - morph::vec dims = morph::loadpng (fn, image_data_tlbr); + morph::vec dims = morph::loadpng (fn, image_data_tlbr, morph::vec{false,false}); morph::vvec image_data_bltr; - dims = morph::loadpng (fn, image_data_bltr, morph::vec({false,true})); + dims = morph::loadpng (fn, image_data_bltr); std::cout << "Image dims: " << dims << std::endl; // Now visualise with a GridVisual diff --git a/examples/grid_resampled_image.cpp b/examples/grid_resampled_image.cpp index a2154526..83abf10b 100644 --- a/examples/grid_resampled_image.cpp +++ b/examples/grid_resampled_image.cpp @@ -15,23 +15,26 @@ int main() { - morph::Visual v(1600, 1000, "Demo of Grid showing a resampled image"); - v.setSceneTrans (morph::vec({-2.60233f, 1.32237f, -6.5f})); + morph::Visual v(1400, 1300, "Demo of Grid showing a resampled image"); + v.setSceneTrans (morph::vec({-2.60691f, 1.39885f, -11.1f})); morph::vec dx = { 0.02f, 0.02f }; morph::vec dx2 = { 0.04f, 0.04f }; - morph::vec nul = { 0.0f, 0.0f }; // Top left to bottom right order matches image loaded by loadpng and avoids the need for a // vec flip arg to morph::loadpng. - morph::Grid g1(256U, 65U, dx, nul, morph::GridDomainWrap::Horizontal); + morph::Grid g1(256U, 65U, dx); + std::cout << "g1 extents: (xmin,xmax,ymin,ymax): " << g1.extents() << std::endl; // Resample onto a lower res grid - morph::Grid g2(128U, 32U, dx2, nul, morph::GridDomainWrap::Horizontal); + morph::Grid g2(128U, 32U, dx2); + std::cout << "g2 extents: (xmin,xmax,ymin,ymax): " << g2.extents() << std::endl; // Load an image std::string fn = "../examples/bike256_65.png"; - morph::vvec image_data_bltr; - morph::vec dims = morph::loadpng (fn, image_data_bltr, morph::vec{false,true}); + + morph::vvec image_data; // image loaded BLTR by default + morph::vec dims = morph::loadpng (fn, image_data); + std::cout << "Image dims: " << dims << std::endl; if (dims[0] != 256) { @@ -40,47 +43,54 @@ int main() } // Resample - // This controls how large the photo will be on the HexGrid - morph::vec image_scale = {2.0f, 2.0f}; - // You can shift the photo with an offset if necessary + // This allows you to expand or contract the image on the Grid. + morph::vec image_scale = {1.0f, 1.0f}; + // You can shift the image with an offset if necessary. Use the units of the target Grid/HexGrid morph::vec image_offset = {0.0f, 0.0f}; - morph::vvec img_resampled = g2.resample_image (image_data_bltr, dims[0], image_scale, image_offset); + morph::vvec img_resampled = g2.resample_image (image_data, dims[0], image_scale, image_offset); // Resample to HexGrid too, for good measure - morph::HexGrid hg(0.01f, 3.0f, 0.0f); - hg.setRectangularBoundary (2.0f, 0.5f); - // Here's the HexGrid method that will resample the square pixel grid onto the hex grid - morph::vvec hex_image_data = hg.resampleImage (image_data_bltr, dims[0], image_scale, image_offset); + morph::vec g2_dx = g2.get_dx(); + morph::HexGrid hg(g2_dx[0], g2.width()*2.0f, 0.0f); + hg.setRectangularBoundary (g2.width(), g2.height()); // Make rectangular boundary same size as g2 + + // Here's the HexGrid method that will resample the square pixel grid onto the hex + // grid Because HexGrids are not naturally rectangular, a scaling of {1,1} does not + // expand the image to fit the HexGrid. Instead, we scale by the width of g2, which + // scales the interpreted image (which is interpreted as having width 1.0) to the + // correct dimensions. + morph::vec hex_image_scale = { g2.width(), g2.width() }; + morph::vvec hex_image_data = hg.resampleImage (image_data, dims[0], hex_image_scale, image_offset); // Visualise original with a GridVisual auto gv1 = std::make_unique>(&g1, morph::vec({0,0,0})); v.bindmodel (gv1); gv1->gridVisMode = morph::GridVisMode::RectInterp; - gv1->setScalarData (&image_data_bltr); + gv1->setScalarData (&image_data); gv1->cm.setType (morph::ColourMapType::GreyscaleInv); // inverse greyscale is good for a monochrome image gv1->zScale.setParams (0, 0); // As it's an image, we don't want relief, so set the zScale to have a zero gradient - gv1->addLabel ("Original", {0, 0.2, 0}, morph::TextFeatures(0.1f)); + gv1->addLabel ("Original", {0, -0.2, 0}, morph::TextFeatures(0.1f)); gv1->finalize(); v.addVisualModel (gv1); // Visualize resampled - auto gv2 = std::make_unique>(&g2, morph::vec({0,-2,0})); + auto gv2 = std::make_unique>(&g2, morph::vec{ 0.0f, -2.0f, 0.0f }); v.bindmodel (gv2); gv2->gridVisMode = morph::GridVisMode::RectInterp; gv2->setScalarData (&img_resampled); gv2->cm.setType (morph::ColourMapType::GreyscaleInv); gv2->zScale.setParams (0, 0); - gv2->addLabel ("Resampled", {0, 0.2, 0}, morph::TextFeatures(0.1f)); + gv2->addLabel ("Resampled to coarser Grid", {0, -0.2, 0}, morph::TextFeatures(0.1f)); gv2->finalize(); v.addVisualModel (gv2); - auto hgv = std::make_unique>(&hg, morph::vec({1,-4,0})); + auto hgv = std::make_unique>(&hg, morph::vec{ g2.width() / 2.0f, g2.height() / 2.0f - 4.0f , 0.0f }); v.bindmodel (hgv); hgv->setScalarData (&hex_image_data); hgv->cm.setType (morph::ColourMapType::GreyscaleInv); hgv->zScale.setParams (0, 0); - hgv->addLabel ("Resampled to hex", {-1, 0.5, 0}, morph::TextFeatures(0.1f)); + hgv->addLabel ("Resampled to HexGrid", {-g2.width() / 2.0f, -0.2f - g2.height() / 2.0f, 0}, morph::TextFeatures(0.1f)); hgv->finalize(); v.addVisualModel (hgv); diff --git a/examples/gridct_image.cpp b/examples/gridct_image.cpp index ca27d9de..7519e8e6 100644 --- a/examples/gridct_image.cpp +++ b/examples/gridct_image.cpp @@ -31,9 +31,9 @@ int main() // Load an image std::string fn = "../examples/bike256_65.png"; morph::vvec image_data_tlbr; - morph::vec dims = morph::loadpng (fn, image_data_tlbr); + morph::vec dims = morph::loadpng (fn, image_data_tlbr, morph::vec{false,false}); morph::vvec image_data_bltr; - dims = morph::loadpng (fn, image_data_bltr, morph::vec({false,true})); + dims = morph::loadpng (fn, image_data_bltr); std::cout << "Image dims: " << dims << std::endl; // Now visualise with a GridVisual diff --git a/morph/Grid.h b/morph/Grid.h index c2af5426..0196f03c 100644 --- a/morph/Grid.h +++ b/morph/Grid.h @@ -501,18 +501,18 @@ namespace morph { * left-most pixel to the right edge of the right-most pixel? It could be either, so I * provide width() and width_of_pixels() as well as height() and height_of_pixels(). */ - C width() const { return dx[I{0}] * w; } + C width() const { return dx[I{0}] * (w - I{1}); } //! Return the width of the grid if drawn as pixels - C width_of_pixels() const { return dx[I{0}] * w + dx[I{0}]; } + C width_of_pixels() const { return dx[I{0}] * w; } //! Return the distance from the centre of the bottom row to the centre of the top row - C height() const { return dx[I{1}] * h; } + C height() const { return dx[I{1}] * (h - I{1}); } C area() const { return this->width() * this->height(); } //! Return the height of the grid if drawn as pixels - C height_of_pixels() const { return dx[I{1}] * h + dx[I{1}]; } + C height_of_pixels() const { return dx[I{1}] * h; } //! Return the area of the grid, if drawn as pixels C area_of_pixels() const { return this->width_of_pixels() * this->height_of_pixels(); } @@ -590,7 +590,12 @@ namespace morph { /*! * Resampling function (monochrome). * - * \param image_data (input) The monochrome image as a vvec of floats. + * \param image_data (input) The monochrome image as a vvec of floats. The image + * is interpreted as running from bottom left to top right (matching the default + * value of Grid::order). Thus, the very first float in the vvec is at x=0, + * y=0. The image width is normalized to 1.0. The height of the image is + * computed from this assumption and on the assumption that pixels are square. + * * \param image_pixelwidth (input) The number of pixels that the image is wide * \param image_scale (input) The size that the image should be resampled to (same units as Grid) * \param image_offset (input) An offset in Grid units to shift the image wrt to the Grid's origin @@ -603,17 +608,26 @@ namespace morph { const morph::vec& image_scale, const morph::vec& image_offset) const { + if (this->order != morph::GridOrder::bottomleft_to_topright) { + throw std::runtime_error ("Grid::resample_image: resampling assumes image has morph::GridOrder::bottomleft_to_topright, so your Grid should, too."); + } + unsigned int csz = image_data.size(); morph::vec image_pixelsz = {image_pixelwidth, csz / image_pixelwidth}; std::cout << "image_pixelsz = " << image_pixelsz << " csz: " << csz << std::endl; + + // Before scaling, image assumed to have width 1, height whatever + morph::vec image_dims = { 1.0f, 0.0f }; + image_dims[1] = 1.0f / (image_pixelsz[0] - 1u) * (image_pixelsz[1] - 1u); + // Now scale the image dims to have the same width as *this: + image_dims *= this->width(); + // Then apply any manual scaling requested: + image_dims *= image_scale; + // Distance per pixel in the image. This defines the Gaussian width (sigma) for the - // resample. Assume that the unscaled image pixels are square. Use the image width to - // set the distance per pixel (hence divide by image_scale by image_pixelsz[*0*]). - morph::vec dist_per_pix = image_scale / (image_pixelsz[0]-1u); - std::cout << "dist_per_pix = " << dist_per_pix << std::endl; - // This is an offset to centre the image wrt to the Grid - morph::vec input_centering_offset = dist_per_pix * image_pixelsz * morph::vec{1, 1}; - std::cout << "input_centering_offset = " << input_centering_offset << std::endl; + // resample. Compute this from the image dimensions, assuming pixels are square + morph::vec dist_per_pix = image_dims / (image_pixelsz - 1u); + // Parameters for the Gaussian computation morph::vec params = 1.0f / (2.0f * dist_per_pix * dist_per_pix); morph::vec threesig = 3.0f * dist_per_pix; @@ -624,16 +638,12 @@ namespace morph { float expr = 0.0f; for (unsigned int i = 0; i < csz; ++i) { // Get x/y pixel coords: - morph::vec idx = {(i % image_pixelsz[0]), (image_pixelsz[1] - (i / image_pixelsz[0]))}; - // Get the coordinates of the input pixel at index idx (in Grid units): - morph::vec posn = (dist_per_pix * idx) + input_centering_offset + image_offset; + morph::vec idx = {(i % image_pixelsz[0]), (i / image_pixelsz[0])}; + // Get the coordinates of the input pixel at index idx (in target units): + morph::vec posn = (dist_per_pix * idx) + image_offset; // Distance from input pixel to output pixel: float _v_x = this->v_x[xi] - posn[0]; float _v_y = this->v_y[xi] - posn[1]; - if (xi == 0) { - std::cout << "Pixel " << i << " has idx : " << idx << " and position " - << posn << " v_x/y: (" << _v_x << "," << _v_y << ")\n"; - } // Compute contributions to each Grid pixel, using 2D (elliptical) Gaussian if (_v_x < threesig[0] && _v_y < threesig[1]) { // Testing for distance gives slight speedup expr += std::exp ( - ( (params[0] * _v_x * _v_x) + (params[1] * _v_y * _v_y) ) ) * image_data[i]; diff --git a/morph/Gridct.h b/morph/Gridct.h index 76437d05..a53d247d 100644 --- a/morph/Gridct.h +++ b/morph/Gridct.h @@ -327,18 +327,18 @@ namespace morph { * left-most pixel to the right edge of the right-most pixel? It could be either, so I * provide width() and width_of_pixels() as well as height() and height_of_pixels(). */ - constexpr C width() const { return dx[0] * w; } + constexpr C width() const { return dx[0] * (w - I{1}); } //! Return the width of the grid if drawn as pixels - constexpr C width_of_pixels() const { return dx[0] * w + dx[0]; } + constexpr C width_of_pixels() const { return dx[0] * w; } //! Return the distance from the centre of the bottom row to the centre of the top row - constexpr C height() const { return dx[1] * h; } + constexpr C height() const { return dx[1] * (h - I{1}); } constexpr C area() const { return this->width() * this->height(); } //! Return the height of the grid if drawn as pixels - constexpr C height_of_pixels() const { return dx[1] * h + dx[1]; } + constexpr C height_of_pixels() const { return dx[1] * h; } //! Return the area of the grid, if drawn as pixels constexpr C area_of_pixels() const { return this->width_of_pixels() * this->height_of_pixels(); } diff --git a/tests/testGrid.cpp b/tests/testGrid.cpp index 46e78e44..08c71a26 100644 --- a/tests/testGrid.cpp +++ b/tests/testGrid.cpp @@ -18,6 +18,24 @@ int main() std::cout << "Grid g_bltr extents: " << g_bltr.extents() << std::endl; std::cout << "Grid g_bltrc extents: " << g_bltrc.extents() << std::endl; + auto ext = g_bltr.extents(); + // Test extents are as wide as width: + if (ext[1] - ext[0] != g_bltr.width()) { + --rtn; + } + // And as high as height + if (ext[3] - ext[2] != g_bltr.height()) { + --rtn; + } + // Area of pixels should be 8 + if (g_bltr.area_of_pixels() != 8.0f) { + --rtn; + } + // Area should be 3 + if (g_bltr.area() != 3.0f) { + --rtn; + } + std::cout << "Grid g_tlbr extents: " << g_tlbr.extents() << std::endl; std::cout << "Grid g_tlbrc extents: " << g_tlbrc.extents() << std::endl; From d6f5381fb42cc2f411d1b6248bd7accbdae5031c Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 19 Jul 2024 12:01:28 +0100 Subject: [PATCH 7/8] Make the image dimensions neater to see the edge artefact all round the border --- examples/bike256_64.png | Bin 0 -> 33618 bytes examples/grid_resampled_image.cpp | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 examples/bike256_64.png diff --git a/examples/bike256_64.png b/examples/bike256_64.png new file mode 100644 index 0000000000000000000000000000000000000000..baca960465c66b80427ee0e833104f63fd371a42 GIT binary patch literal 33618 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.60691f, 1.39885f, -11.1f})); + unsigned int scaledown = 2U; + morph::vec dx = { 0.02f, 0.02f }; - morph::vec dx2 = { 0.04f, 0.04f }; + morph::vec dx2 = dx * scaledown; // Top left to bottom right order matches image loaded by loadpng and avoids the need for a // vec flip arg to morph::loadpng. - morph::Grid g1(256U, 65U, dx); + morph::Grid g1(256U, 64U, dx); std::cout << "g1 extents: (xmin,xmax,ymin,ymax): " << g1.extents() << std::endl; // Resample onto a lower res grid - morph::Grid g2(128U, 32U, dx2); + morph::Grid g2(256U/scaledown, 64U/scaledown, dx2); std::cout << "g2 extents: (xmin,xmax,ymin,ymax): " << g2.extents() << std::endl; // Load an image - std::string fn = "../examples/bike256_65.png"; + std::string fn = "../examples/bike256_64.png"; morph::vvec image_data; // image loaded BLTR by default morph::vec dims = morph::loadpng (fn, image_data); From a28872e540ce87345d67c556312e4eb7bb714b26 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 19 Jul 2024 12:53:31 +0100 Subject: [PATCH 8/8] Ignore growthbuffer code (unused) --- morph/HexGrid.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/morph/HexGrid.h b/morph/HexGrid.h index 3c2822cd..58ae58f7 100644 --- a/morph/HexGrid.h +++ b/morph/HexGrid.h @@ -130,7 +130,7 @@ namespace morph { * bottom? Set this to a larger number if the boundary is expected to grow * during a simulation. */ - unsigned int d_growthbuffer_horz = 5; + unsigned int d_growthbuffer_horz = 0; unsigned int d_growthbuffer_vert = 0; //! Add entries to all the d_ vectors for the Hex pointed to by hi. @@ -3939,12 +3939,13 @@ namespace morph { rtn[2] = (int)(limits[2] / d_gi); rtn[3] = (int)(limits[3] / d_gi); - // Add 'growth buffer' +#if 0 + // Add any 'growth buffer', in case boundary expected to change? I think this is old thinking and maybe not relevant? rtn[0] -= this->d_growthbuffer_horz; rtn[1] += this->d_growthbuffer_horz; rtn[2] -= this->d_growthbuffer_vert; rtn[3] += this->d_growthbuffer_vert; - +#endif return rtn; }