Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions tests/test_all.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "test_remaining2d_common.h"
#include "test_zernike_regression.h"
#include "test_neighbor_regression.h"
#include "test_neighbor_cellprofiler.h"
#include "test_neighbor_analytic.h"
#include "test_initialization_mechanics.h"
#include "test_glcm_ibsi.h"
#include "test_gldm_ibsi.h"
Expand Down Expand Up @@ -1147,14 +1149,14 @@ TEST(TEST_NYXUS, TEST_NEIGHBORHOOD2D_CLOSEST_NEIGHBORS)
ASSERT_NO_THROW(test_neighborhood2d_closest_neighbors());
}

TEST(TEST_NYXUS, TEST_NEIGHBORHOOD2D_UNVETTED_NO_DIRECT_ORACLE_CLOSEST_NEIGHBOR_ANGLES)
TEST(TEST_NYXUS, TEST_NEIGHBORHOOD2D_CELLPROFILER_COUNTS_FIRST_DISTANCE)
{
ASSERT_NO_THROW(test_neighborhood2d_unvetted_no_direct_oracle_closest_neighbor_angles());
ASSERT_NO_THROW(test_neighborhood2d_cellprofiler_counts_and_first_distance());
}

TEST(TEST_NYXUS, TEST_NEIGHBORHOOD2D_UNVETTED_NO_DIRECT_ORACLE_ANGLE_STATS)
TEST(TEST_NYXUS, TEST_NEIGHBORHOOD2D_ANALYTIC_SECOND_DISTANCE_AND_ANGLES)
{
ASSERT_NO_THROW(test_neighborhood2d_unvetted_no_direct_oracle_neighbor_angle_stats());
ASSERT_NO_THROW(test_neighborhood2d_analytic_second_distance_and_angles());
}


Expand Down
89 changes: 89 additions & 0 deletions tests/test_neighbor_analytic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#pragma once

// ANALYTIC oracle for the 2D neighbor second-distance + angle features.
// Given the neighbor graph (which CellProfiler vets, see test_neighbor_cellprofiler.h),
// these six features are deterministic closed forms of the ROI centroids, so an
// independent numpy recomputation of Nyxus' documented formulas IS the oracle
// (same analytic-conformance basis as CIRCULARITY / intensity_histogram). Goldens
// and the formula derivation are in tests/vetting/oracles/gen_neighbor_analytic.py,
// which validates them to double precision against these values; asserted at 1e-4.
//
// Why not CellProfiler for these: CP's AngleBetweenNeighbors is the angle SUBTENDED
// at an object by its two neighbors, not Nyxus' absolute atan2 direction angle; and
// CP's SecondClosestDistance is the 2nd-closest of ANY object, whereas Nyxus reports
// the 2nd-closest neighbor WITHIN the search radius (0 when <2 in-radius neighbors).

#include <gtest/gtest.h>

#include "test_neighbor_regression.h" // shared fixture builder calculate_neighborhood2d_feature_values

static std::unordered_map<int, std::unordered_map<std::string, double>> neighbor2d_analytic_golden_by_label{
{1, {
{"CLOSEST_NEIGHBOR2_DIST", 2.54950975679639},
{"CLOSEST_NEIGHBOR1_ANG", 0.0},
{"CLOSEST_NEIGHBOR2_ANG", 191.30993247402},
{"ANG_BW_NEIGHBORS_MEAN", 132.172516881495},
{"ANG_BW_NEIGHBORS_STDDEV", 115.230018010206},
{"ANG_BW_NEIGHBORS_MODE", 0.0},
}},
{2, {
{"CLOSEST_NEIGHBOR2_DIST", 0.0},
{"CLOSEST_NEIGHBOR1_ANG", 11.3099324740202},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 11.3099324740202},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 11.0},
}},
{3, {
{"CLOSEST_NEIGHBOR2_DIST", 0.0},
{"CLOSEST_NEIGHBOR1_ANG", 78.6900675259798},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 78.6900675259798},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 79.0},
}},
{4, {
{"CLOSEST_NEIGHBOR2_DIST", 0.0},
{"CLOSEST_NEIGHBOR1_ANG", 180.0},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 180.0},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 180.0},
}},
{5, {
{"CLOSEST_NEIGHBOR2_DIST", 0.0},
{"CLOSEST_NEIGHBOR1_ANG", 258.69006752598},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 258.69006752598},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 259.0},
}}
};

static void assert_neighbor2d_analytic(
const std::unordered_map<int, LR>& roiData,
int label,
Nyxus::Feature2D feature,
const std::string& feature_name)
{
SCOPED_TRACE(std::string("ANALYTIC__") + feature_name + "__L" + std::to_string(label));
ASSERT_TRUE(neighbor2d_analytic_golden_by_label[label].count(feature_name) > 0);
ASSERT_NEAR(roiData.at(label).fvals[static_cast<int>(feature)][0],
neighbor2d_analytic_golden_by_label[label][feature_name], 1e-4);
}

void test_neighborhood2d_analytic_second_distance_and_angles()
{
std::unordered_map<int, LR> roiData;
calculate_neighborhood2d_feature_values(roiData);

for (int label : {1, 2, 3, 4, 5})
{
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR2_DIST, "CLOSEST_NEIGHBOR2_DIST");
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR1_ANG, "CLOSEST_NEIGHBOR1_ANG");
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR2_ANG, "CLOSEST_NEIGHBOR2_ANG");
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_MEAN, "ANG_BW_NEIGHBORS_MEAN");
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_STDDEV, "ANG_BW_NEIGHBORS_STDDEV");
assert_neighbor2d_analytic(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_MODE, "ANG_BW_NEIGHBORS_MODE");
}
}
49 changes: 49 additions & 0 deletions tests/test_neighbor_cellprofiler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

// CellProfiler oracle for the 2D neighbor-graph features NUM_NEIGHBORS and
// CLOSEST_NEIGHBOR1_DIST. The real cellprofiler.modules.MeasureObjectNeighbors
// module (Adjacent method) was run on this fixture; CP reproduces Nyxus exactly for
// both features. Goldens + the offline CP run are in
// tests/vetting/oracles/gen_neighbor_cellprofiler.py; asserted at 1e-4.
//
// PERCENT_TOUCHING is NOT vetted here: CP and Nyxus use different definitions
// (Nyxus = contour pixels 8-adjacent to a neighbor / contour length; CP = object
// outline pixels overlapping a disk(distance+0.5)-dilated neighbor / perimeter), and
// no CP distance method reproduces Nyxus (diverges on 3/5 ROIs). It stays a
// regression snapshot in test_neighbor_regression.h.

#include <gtest/gtest.h>

#include "test_neighbor_regression.h" // shared fixture builder calculate_neighborhood2d_feature_values

static std::unordered_map<int, std::unordered_map<std::string, double>> neighbor2d_cellprofiler_golden_by_label{
{1, {{"NUM_NEIGHBORS", 4.0}, {"CLOSEST_NEIGHBOR1_DIST", 2.5}}},
{2, {{"NUM_NEIGHBORS", 1.0}, {"CLOSEST_NEIGHBOR1_DIST", 2.54950975679639}}},
{3, {{"NUM_NEIGHBORS", 1.0}, {"CLOSEST_NEIGHBOR1_DIST", 2.54950975679639}}},
{4, {{"NUM_NEIGHBORS", 1.0}, {"CLOSEST_NEIGHBOR1_DIST", 2.5}}},
{5, {{"NUM_NEIGHBORS", 1.0}, {"CLOSEST_NEIGHBOR1_DIST", 2.54950975679639}}}
};

static void assert_neighbor2d_cellprofiler(
const std::unordered_map<int, LR>& roiData,
int label,
Nyxus::Feature2D feature,
const std::string& feature_name)
{
SCOPED_TRACE(std::string("CELLPROFILER__") + feature_name + "__L" + std::to_string(label));
ASSERT_TRUE(neighbor2d_cellprofiler_golden_by_label[label].count(feature_name) > 0);
ASSERT_NEAR(roiData.at(label).fvals[static_cast<int>(feature)][0],
neighbor2d_cellprofiler_golden_by_label[label][feature_name], 1e-4);
}

void test_neighborhood2d_cellprofiler_counts_and_first_distance()
{
std::unordered_map<int, LR> roiData;
calculate_neighborhood2d_feature_values(roiData);

for (int label : {1, 2, 3, 4, 5})
{
assert_neighbor2d_cellprofiler(roiData, label, Nyxus::Feature2D::NUM_NEIGHBORS, "NUM_NEIGHBORS");
assert_neighbor2d_cellprofiler(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR1_DIST, "CLOSEST_NEIGHBOR1_DIST");
}
}
79 changes: 5 additions & 74 deletions tests/test_neighbor_regression.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,43 +47,11 @@ static std::unordered_map<int, std::unordered_map<std::string, double>> unvetted
}}
};

static std::unordered_map<int, std::unordered_map<std::string, double>> unvetted_nyxus_regression_neighbor2d_angle_feature_golden_values_by_label{
{1, {
{"CLOSEST_NEIGHBOR1_ANG", 0.0},
{"CLOSEST_NEIGHBOR2_ANG", 191.30993247402},
{"ANG_BW_NEIGHBORS_MEAN", 132.172516881495},
{"ANG_BW_NEIGHBORS_STDDEV", 115.230018010206},
{"ANG_BW_NEIGHBORS_MODE", 0.0},
}},
{2, {
{"CLOSEST_NEIGHBOR1_ANG", 11.3099324740202},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 11.3099324740202},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 11.0},
}},
{3, {
{"CLOSEST_NEIGHBOR1_ANG", 78.6900675259798},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 78.6900675259798},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 79.0},
}},
{4, {
{"CLOSEST_NEIGHBOR1_ANG", 180.0},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 180.0},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 180.0},
}},
{5, {
{"CLOSEST_NEIGHBOR1_ANG", 258.69006752598},
{"CLOSEST_NEIGHBOR2_ANG", 0.0},
{"ANG_BW_NEIGHBORS_MEAN", 258.69006752598},
{"ANG_BW_NEIGHBORS_STDDEV", 0.0},
{"ANG_BW_NEIGHBORS_MODE", 259.0},
}}
};
// NOTE: the closest-neighbor angles and the angle-between-neighbors stats are now
// vetted at tight tolerance by the ANALYTIC oracle in test_neighbor_analytic.h
// (independent numpy recomputation of Nyxus' atan2 direction-angle formulas); the
// second-closest distance likewise. This regression file keeps only the fixture
// builder + the PERCENT_TOUCHING snapshot (the one feature with no promotable oracle).

static Fsettings make_neighbors2d_settings()
{
Expand Down Expand Up @@ -154,19 +122,6 @@ static void assert_neighbor2d_feature(
ASSERT_TRUE(agrees_gt(roiData.at(label).fvals[static_cast<int>(feature)][0], unvetted_nyxus_regression_neighbor2d_distance_feature_golden_values_by_label[label][feature_name], frac_tolerance));
}

static void assert_unvetted_no_direct_oracle_neighbor2d_feature(
const std::unordered_map<int, LR>& roiData,
int label,
Nyxus::Feature2D feature,
const std::string& feature_name,
double frac_tolerance = 1000.0)
{
SCOPED_TRACE(std::string("UNVETTED_NO_DIRECT_ORACLE__") + feature_name);
ASSERT_TRUE(unvetted_nyxus_regression_neighbor2d_angle_feature_golden_values_by_label.count(label) > 0);
ASSERT_TRUE(unvetted_nyxus_regression_neighbor2d_angle_feature_golden_values_by_label[label].count(feature_name) > 0);
ASSERT_TRUE(agrees_gt(roiData.at(label).fvals[static_cast<int>(feature)][0], unvetted_nyxus_regression_neighbor2d_angle_feature_golden_values_by_label[label][feature_name], frac_tolerance));
}

void test_neighborhood2d_counts_and_touching()
{
std::unordered_map<int, LR> roiData;
Expand Down Expand Up @@ -211,27 +166,3 @@ void test_neighborhood2d_closest_neighbors()
}
}

void test_neighborhood2d_unvetted_no_direct_oracle_closest_neighbor_angles()
{
std::unordered_map<int, LR> roiData;
calculate_neighborhood2d_feature_values(roiData);

for (int label : {1, 2, 3, 4, 5})
{
assert_unvetted_no_direct_oracle_neighbor2d_feature(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR1_ANG, "CLOSEST_NEIGHBOR1_ANG");
assert_unvetted_no_direct_oracle_neighbor2d_feature(roiData, label, Nyxus::Feature2D::CLOSEST_NEIGHBOR2_ANG, "CLOSEST_NEIGHBOR2_ANG");
}
}

void test_neighborhood2d_unvetted_no_direct_oracle_neighbor_angle_stats()
{
std::unordered_map<int, LR> roiData;
calculate_neighborhood2d_feature_values(roiData);

for (int label : {1, 2, 3, 4, 5})
{
assert_unvetted_no_direct_oracle_neighbor2d_feature(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_MEAN, "ANG_BW_NEIGHBORS_MEAN");
assert_unvetted_no_direct_oracle_neighbor2d_feature(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_STDDEV, "ANG_BW_NEIGHBORS_STDDEV");
assert_unvetted_no_direct_oracle_neighbor2d_feature(roiData, label, Nyxus::Feature2D::ANG_BW_NEIGHBORS_MODE, "ANG_BW_NEIGHBORS_MODE");
}
}
6 changes: 3 additions & 3 deletions tests/vetting/coverage_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

_Generated by check_coverage.py from oracle_coverage.csv. Do not edit by hand._

Features vetted by >=1 oracle: 604/758 (80%)
regression: 154 untested: 0
Features vetted by >=1 oracle: 610/758 (80%)
regression: 148 untested: 0

| family | total | vetted | regression | untested |
|---|---|---|---|---|
Expand All @@ -18,7 +18,7 @@ regression: 154 untested: 0
| intensity_histogram | 47 | 47 | 0 | 0 |
| moments | 180 | 118 | 62 | 0 |
| morphology | 113 | 77 | 36 | 0 |
| neighbor | 9 | 2 | 7 | 0 |
| neighbor | 9 | 8 | 1 | 0 |
| ngldm | 38 | 20 | 18 | 0 |
| ngtdm | 10 | 10 | 0 | 0 |
| radial | 3 | 0 | 3 | 0 |
Expand Down
Loading
Loading