aboutsummaryrefslogtreecommitdiff
path: root/svg-flatten
diff options
context:
space:
mode:
Diffstat (limited to 'svg-flatten')
-rw-r--r--svg-flatten/src/nopencv.cpp94
-rw-r--r--svg-flatten/src/nopencv_test.cpp194
2 files changed, 186 insertions, 102 deletions
diff --git a/svg-flatten/src/nopencv.cpp b/svg-flatten/src/nopencv.cpp
index afe4881..2384fcc 100644
--- a/svg-flatten/src/nopencv.cpp
+++ b/svg-flatten/src/nopencv.cpp
@@ -247,6 +247,7 @@ double k_cos(const Polygon_i &poly, size_t i, size_t k) {
return dp / (sqrt(sq_a)*sqrt(sq_b));
}
+constexpr bool debug = false;
ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback cb) {
return [&cb](Polygon_i &poly, ContourPolarity cpol) {
size_t sz = poly.size();
@@ -261,30 +262,32 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
retain[i] = true;
}
- cerr << endl;
- cerr << "Polarity: " << cpol <<endl;
- cerr << "Coords:"<<endl;
- cerr << " x: ";
- for (size_t i=0; i<sz; i++) {
- cerr << setfill(' ') << setw(2) << poly[i][0] << " ";
- }
- cerr << endl;
- cerr << " y: ";
- for (size_t i=0; i<sz; i++) {
- cerr << setfill(' ') << setw(2) << poly[i][1] << " ";
- }
- cerr << endl;
- cerr << "Metrics:"<<endl;
- cerr << "ros: ";
- for (size_t i=0; i<sz; i++) {
- cerr << setfill(' ') << setw(2) << ros[i] << " ";
- }
- cerr << endl;
- cerr << "sig: ";
- for (size_t i=0; i<sz; i++) {
- cerr << setfill(' ') << setw(2) << sig[i] << " ";
+ if (debug) {
+ cerr << endl;
+ cerr << "Polarity: " << cpol <<endl;
+ cerr << "Coords:"<<endl;
+ cerr << " x: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << setfill(' ') << setw(2) << poly[i][0] << " ";
+ }
+ cerr << endl;
+ cerr << " y: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << setfill(' ') << setw(2) << poly[i][1] << " ";
+ }
+ cerr << endl;
+ cerr << "Metrics:"<<endl;
+ cerr << "ros: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << setfill(' ') << setw(2) << ros[i] << " ";
+ }
+ cerr << endl;
+ cerr << "sig: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << setfill(' ') << setw(2) << sig[i] << " ";
+ }
+ cerr << endl;
}
- cerr << endl;
/* Pass 0 (like opencv): Remove points with zero 1-curvature */
for (size_t i=0; i<sz; i++) {
@@ -294,11 +297,13 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
}
}
- cerr << "pass 0: ";
- for (size_t i=0; i<sz; i++) {
- cerr << (retain[i] ? "#" : ".");
+ if (debug) {
+ cerr << "pass 0: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << (retain[i] ? "#" : ".");
+ }
+ cerr << endl;
}
- cerr << endl;
/* 3a, Pass 1: Non-maxima suppression */
for (size_t i=0; i<sz; i++) {
@@ -310,11 +315,13 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
}
}
- cerr << "pass 1: ";
- for (size_t i=0; i<sz; i++) {
- cerr << (retain[i] ? "#" : ".");
+ if (debug) {
+ cerr << "pass 1: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << (retain[i] ? "#" : ".");
+ }
+ cerr << endl;
}
- cerr << endl;
/* 3b, Pass 2: Zero-curvature suppression */
for (size_t i=0; i<sz; i++) {
@@ -325,11 +332,13 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
}
}
- cerr << "pass 2: ";
- for (size_t i=0; i<sz; i++) {
- cerr << (retain[i] ? "#" : ".");
+ if (debug) {
+ cerr << "pass 2: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << (retain[i] ? "#" : ".");
+ }
+ cerr << endl;
}
- cerr << endl;
/* 3c, Pass 3: Further thinning */
for (size_t i=0; i<sz; i++) {
@@ -344,11 +353,13 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
}
}
- cerr << "pass 3: ";
- for (size_t i=0; i<sz; i++) {
- cerr << (retain[i] ? "#" : ".");
+ if (debug) {
+ cerr << "pass 3: ";
+ for (size_t i=0; i<sz; i++) {
+ cerr << (retain[i] ? "#" : ".");
+ }
+ cerr << endl;
}
- cerr << endl;
Polygon_i new_poly;
for (size_t i=0; i<sz; i++) {
@@ -356,7 +367,10 @@ ContourCallback gerbolyze::nopencv::simplify_contours_teh_chin(ContourCallback c
new_poly.push_back(poly[i]);
}
}
- cb(new_poly, cpol);
+
+ if (!new_poly.empty()) {
+ cb(new_poly, cpol);
+ }
};
}
diff --git a/svg-flatten/src/nopencv_test.cpp b/svg-flatten/src/nopencv_test.cpp
index 4027072..389de60 100644
--- a/svg-flatten/src/nopencv_test.cpp
+++ b/svg-flatten/src/nopencv_test.cpp
@@ -29,6 +29,40 @@ private:
filesystem::path m_path;
};
+class SVGPolyRenderer {
+public:
+ SVGPolyRenderer(const char *fn, int width_px, int height_px)
+ : m_svg(fn) {
+ m_svg << "<svg width=\"" << width_px << "px\" height=\"" << height_px << "px\" viewBox=\"0 0 "
+ << width_px << " " << height_px << "\" "
+ << "xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << endl;
+ m_svg << "<rect width=\"100%\" height=\"100%\" fill=\"black\"/>" << endl;
+ }
+
+ ContourCallback callback() {
+ return [this](Polygon_i &poly, ContourPolarity pol) {
+ mu_assert(poly.size() > 0, "Empty contour returned");
+ mu_assert(poly.size() > 2, "Contour has less than three points, no area");
+ mu_assert(pol == CP_CONTOUR || pol == CP_HOLE, "Contour has invalid polarity");
+
+ m_svg << "<path fill=\"" << ((pol == CP_HOLE) ? "black" : "white") << "\" d=\"";
+ m_svg << "M " << poly[0][0] << " " << poly[0][1];
+ for (size_t i=1; i<poly.size(); i++) {
+ m_svg << " L " << poly[i][0] << " " << poly[i][1];
+ }
+ m_svg << " Z\"/>" << endl;
+ };
+ }
+
+ void close() {
+ m_svg << "</svg>" << endl;
+ m_svg.close();
+ }
+
+private:
+ ofstream m_svg;
+};
+
MU_TEST(test_complex_example_from_paper) {
int32_t img_data[6*9] = {
0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -74,16 +108,13 @@ MU_TEST(test_complex_example_from_paper) {
i2p last;
bool first = true;
Polygon_i exp = expected_polys[invocation_count-1];
- //cout << "poly: ";
for (auto &p : poly) {
- //cout << "(" << p[0] << ", " << p[1] << "), ";
if (!first) {
mu_assert((fabs(p[0] - last[0]) + fabs(p[1] - last[1]) == 1), "Subsequent contour points have distance other than one");
mu_assert(find(exp.begin(), exp.end(), p) != exp.end(), "Got unexpected contour point");
}
last = p;
}
- //cout << endl;
});
mu_assert_int_eq(3, invocation_count);
@@ -127,6 +158,27 @@ MU_TEST(test_complex_example_from_paper) {
}
}
+int render_svg(const char *in_svg, const char *out_png) {
+ const char *command_line[] = {"resvg", in_svg, out_png, nullptr};
+ struct subprocess_s subprocess;
+ int rc = subprocess_create(command_line, subprocess_option_inherit_environment, &subprocess);
+ if (rc)
+ return rc;
+
+ int resvg_rc = -1;
+ rc = subprocess_join(&subprocess, &resvg_rc);
+ if (rc)
+ return rc;
+ if (resvg_rc)
+ return -resvg_rc;
+
+ rc = subprocess_destroy(&subprocess);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
static void testdata_roundtrip(const char *fn) {
int x, y;
uint8_t *data = stbi_load(fn, &x, &y, nullptr, 1);
@@ -142,40 +194,11 @@ static void testdata_roundtrip(const char *fn) {
TempfileHack tmp_svg(".svg");
TempfileHack tmp_png(".png");
- ofstream svg(tmp_svg.c_str());
-
- svg << "<svg width=\"" << x << "px\" height=\"" << y << "px\" viewBox=\"0 0 "
- << x << " " << y << "\" "
- << "xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << endl;
- svg << "<rect width=\"100%\" height=\"100%\" fill=\"black\"/>" << endl;
-
- gerbolyze::nopencv::find_blobs(ref_img, [&svg](Polygon_i &poly, ContourPolarity pol) {
- mu_assert(poly.size() > 0, "Empty contour returned");
- mu_assert(poly.size() > 2, "Contour has less than three points, no area");
- mu_assert(pol == CP_CONTOUR || pol == CP_HOLE, "Contour has invalid polarity");
+ SVGPolyRenderer ctx(tmp_svg.c_str(), x, y);
+ gerbolyze::nopencv::find_blobs(ref_img, ctx.callback());
+ ctx.close();
- svg << "<path fill=\"" << ((pol == CP_HOLE) ? "black" : "white") << "\" d=\"";
- svg << "M " << poly[0][0] << " " << poly[0][1];
- for (size_t i=1; i<poly.size(); i++) {
- svg << " L " << poly[i][0] << " " << poly[i][1];
- }
- svg << " Z\"/>" << endl;
- });
- svg << "</svg>" << endl;
- svg.close();
-
- const char *command_line[] = {"resvg", tmp_svg.c_str(), tmp_png.c_str()};
- struct subprocess_s subprocess;
- int rc = subprocess_create(command_line, subprocess_option_inherit_environment, &subprocess);
- mu_assert_int_eq(0, rc);
-
- int resvg_rc = -1;
- rc = subprocess_join(&subprocess, &resvg_rc);
- mu_assert_int_eq(0, rc);
- mu_assert_int_eq(0, resvg_rc);
-
- rc = subprocess_destroy(&subprocess);
- mu_assert_int_eq(0, rc);
+ mu_assert_int_eq(0, render_svg(tmp_svg.c_str(), tmp_png.c_str()));
int out_x, out_y;
uint8_t *out_data = stbi_load(tmp_png.c_str(), &out_x, &out_y, nullptr, 1);
@@ -213,44 +236,77 @@ MU_TEST(test_round_trip_two_px_inv) { testdata_roundtrip("testdata/two-p
static void chain_approx_test(const char *fn) {
- int x, y;
- uint8_t *data = stbi_load(fn, &x, &y, nullptr, 1);
- Image32 ref_img(x, y);
- for (int cy=0; cy<y; cy++) {
- for (int cx=0; cx<x; cx++) {
- ref_img.at(cx, cy) = data[cy*x + cx] / 255;
+ //cout << endl << "Testing \"" << fn << "\"" << endl;
+ int w, h;
+ uint8_t *data = stbi_load(fn, &w, &h, nullptr, 1);
+ Image32 ref_img(w, h);
+ for (int y=0; y<h; y++) {
+ for (int x=0; x<w; x++) {
+ ref_img.at(x, y) = data[y*w + x] / 255;
}
}
- stbi_image_free(data);
TempfileHack tmp_svg(".svg");
+ TempfileHack tmp_png(".png");
- //ofstream svg(tmp_svg.c_str());
- ofstream svg("/tmp/teh-chin-test.svg");
-
- svg << "<svg width=\"" << x << "px\" height=\"" << y << "px\" viewBox=\"0 0 "
- << x << " " << y << "\" "
- << "xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">" << endl;
- svg << "<rect width=\"100%\" height=\"100%\" fill=\"black\"/>" << endl;
-
- gerbolyze::nopencv::find_blobs(ref_img, simplify_contours_teh_chin([&svg](Polygon_i &poly, ContourPolarity pol) {
- svg << "<path fill=\"" << ((pol == CP_HOLE) ? "black" : "white") << "\" d=\"";
- svg << "M " << poly[0][0] << " " << poly[0][1];
- for (size_t i=1; i<poly.size(); i++) {
- svg << " L " << poly[i][0] << " " << poly[i][1];
+ SVGPolyRenderer ctx(tmp_svg.c_str(), w, h);
+ gerbolyze::nopencv::find_blobs(ref_img, simplify_contours_teh_chin(ctx.callback()));
+ ctx.close();
+
+ mu_assert_int_eq(0, render_svg(tmp_svg.c_str(), "/tmp/out.png"));
+
+ int out_w, out_h;
+ uint8_t *out_data = stbi_load("/tmp/out.png", &out_w, &out_h, nullptr, 1);
+ mu_assert_int_eq(w, out_w);
+ mu_assert_int_eq(h, out_h);
+
+ double max_abs_deviation = 0;
+ double rms_sum = 0;
+ double mean_sum = 0;
+ for (int y=0; y<h; y++) {
+ for (int x=0; x<w; x++) {
+ double delta = fabs((double)out_data[y*w + x] - (double)data[y*w + x]) / 255.0;
+ max_abs_deviation = fmax(max_abs_deviation, delta);
+ rms_sum += delta*delta;
+ mean_sum += delta;
}
- svg << " Z\"/>" << endl;
- }));
- svg << "</svg>" << endl;
- svg.close();
+ }
+ stbi_image_free(data);
+ stbi_image_free(out_data);
+
+ rms_sum = sqrt(rms_sum / (w*h));
+ mean_sum /= w*h;
+ if (rms_sum > 0.5) {
+ snprintf(msg, sizeof(msg), "%s: Chain approximation RMS error is above threshold: %.3f > 0.5\n", fn, rms_sum);
+ mu_fail(msg);
+ }
+ if (mean_sum > 0.1) {
+ snprintf(msg, sizeof(msg), "%s: Chain approximation mean error is above threshold: %.3f > 0.1\n", fn, mean_sum);
+ mu_fail(msg);
+ }
+ //mu_assert(max_abs_deviation < 0.5, "Maximum chain approximation error is above threshold");
}
MU_TEST(chain_approx_test_chromosome) { chain_approx_test("testdata/chain-approx-teh-chin-chromosome.png"); }
+MU_TEST(chain_approx_test_blank) { chain_approx_test("testdata/blank.png"); }
+MU_TEST(chain_approx_test_white) { chain_approx_test("testdata/white.png"); }
+MU_TEST(chain_approx_test_blob_border_w) { chain_approx_test("testdata/blob-border-w.png"); }
+MU_TEST(chain_approx_test_blobs_borders) { chain_approx_test("testdata/blobs-borders.png"); }
+MU_TEST(chain_approx_test_blobs_corners) { chain_approx_test("testdata/blobs-corners.png"); }
+MU_TEST(chain_approx_test_blobs_crossing) { chain_approx_test("testdata/blobs-crossing.png"); }
+MU_TEST(chain_approx_test_cross) { chain_approx_test("testdata/cross.png"); }
+MU_TEST(chain_approx_test_letter_e) { chain_approx_test("testdata/letter-e.png"); }
+MU_TEST(chain_approx_test_paper_example) { chain_approx_test("testdata/paper-example.png"); }
+MU_TEST(chain_approx_test_paper_example_inv) { chain_approx_test("testdata/paper-example-inv.png"); }
+MU_TEST(chain_approx_test_single_px) { chain_approx_test("testdata/single-px.png"); }
+MU_TEST(chain_approx_test_single_px_inv) { chain_approx_test("testdata/single-px-inv.png"); }
+MU_TEST(chain_approx_test_two_blobs) { chain_approx_test("testdata/two-blobs.png"); }
+MU_TEST(chain_approx_test_two_px) { chain_approx_test("testdata/two-px.png"); }
+MU_TEST(chain_approx_test_two_px_inv) { chain_approx_test("testdata/two-px-inv.png"); }
MU_TEST_SUITE(nopencv_contours_suite) {
- /*
MU_RUN_TEST(test_complex_example_from_paper);
MU_RUN_TEST(test_round_trip_blank);
MU_RUN_TEST(test_round_trip_white);
@@ -267,8 +323,22 @@ MU_TEST_SUITE(nopencv_contours_suite) {
MU_RUN_TEST(test_round_trip_two_blobs);
MU_RUN_TEST(test_round_trip_two_px);
MU_RUN_TEST(test_round_trip_two_px_inv);
- */
MU_RUN_TEST(chain_approx_test_chromosome);
+ MU_RUN_TEST(chain_approx_test_blank);
+ MU_RUN_TEST(chain_approx_test_white);
+ MU_RUN_TEST(chain_approx_test_blob_border_w);
+ MU_RUN_TEST(chain_approx_test_blobs_borders);
+ MU_RUN_TEST(chain_approx_test_blobs_corners);
+ MU_RUN_TEST(chain_approx_test_blobs_crossing);
+ MU_RUN_TEST(chain_approx_test_cross);
+ MU_RUN_TEST(chain_approx_test_letter_e);
+ MU_RUN_TEST(chain_approx_test_paper_example);
+ MU_RUN_TEST(chain_approx_test_paper_example_inv);
+ MU_RUN_TEST(chain_approx_test_single_px);
+ MU_RUN_TEST(chain_approx_test_single_px_inv);
+ MU_RUN_TEST(chain_approx_test_two_blobs);
+ MU_RUN_TEST(chain_approx_test_two_px);
+ MU_RUN_TEST(chain_approx_test_two_px_inv);
};
int main(int argc, char **argv) {