diff --git a/arbor/include/arbor/morph/morphology.hpp b/arbor/include/arbor/morph/morphology.hpp index aa9ad61b88e0969acea52976327e7982c97a1548..de45148a4ee0ea9dc344b39b66d1ad10c88f0bee 100644 --- a/arbor/include/arbor/morph/morphology.hpp +++ b/arbor/include/arbor/morph/morphology.hpp @@ -67,19 +67,16 @@ mlocation_list minset(const morphology&, const mlocation_list&); mlocation canonical(const morphology&, mlocation); // Represent a (possibly empty or disconnected) region on a morphology. -// Wraps an mcable_list, and satisfies the additional constraints: // -// I. Any two cables on the same branch are strictly disjoint, i.e. -// for cables p and q on the same branch, either p.prox_pos > q.dist_pos -// or p.dist_pos < q.prox_pos. +// Wraps an mcable_list, and satisfies the additional constraint that +// any two cables on the same branch are strictly disjoint, i.e. +// for cables p and q on the same branch, either p.prox_pos > q.dist_pos +// or p.dist_pos < q.prox_pos. // -// II. For any branch b on the morphology tree that intersects the subset -// described by the extent, there exists a cable in the extent defined -// on this branch b. -// -// While an mextent can only be built from an mcable_list in conjunction with -// a morphology, union, intersection, and location membership operations can -// be performed without one. +// Union, intersection, and location membership operations can be performed +// without a morphology. +// A morphology is required to assert the invariant that an mextent does +// not contain branches not in the morphology. struct mextent { mextent() = default; mextent(const mextent&) = default; @@ -88,10 +85,13 @@ struct mextent { mextent& operator=(const mextent&) = default; mextent& operator=(mextent&&) = default; - mextent(const morphology&, const mcable_list&); + mextent(const mcable_list&); - bool test_invariants() const; // check invariant (I) above. - bool test_invariants(const morphology&) const; // check invariants (I) and (II) above. + // Check that the cable segments are valid, and that cables are strictly disjoint. + bool test_invariants() const; + // Checks the above, along with asserting that only cables in the morphology + // are present. + bool test_invariants(const morphology&) const; const mcable_list& cables() const { return cables_; diff --git a/arbor/include/arbor/morph/region.hpp b/arbor/include/arbor/morph/region.hpp index bce98b639088b8eadd32c03c419332d1e8938520..19fbcbaaa2ceef56aebb3cff151a62b413e78e98 100644 --- a/arbor/include/arbor/morph/region.hpp +++ b/arbor/include/arbor/morph/region.hpp @@ -158,6 +158,9 @@ region z_dist_from_root_ge(double r); // Region with all segments in a cell. region all(); +// The extent of a region, i.e. including all fork cover points. +region super(region); + // Region associated with a name. region named(std::string); diff --git a/arbor/morph/locset.cpp b/arbor/morph/locset.cpp index ad6a9a4e6a4c33ede2c0806e7b646acb11c706db..ca3fbdb3800a23aaead3001c4d5bcdb1333aacdb 100644 --- a/arbor/morph/locset.cpp +++ b/arbor/morph/locset.cpp @@ -171,7 +171,7 @@ mlocation_list thingify_(const on_branches_& ob, const mprovider& p) { } std::ostream& operator<<(std::ostream& o, const on_branches_& x) { - return o << "(on_branchs " << x.pos << ")"; + return o << "(on_branches " << x.pos << ")"; } // Named locset. @@ -235,10 +235,10 @@ mlocation_list thingify_(const most_distal_& n, const mprovider& p) { } std::ostream& operator<<(std::ostream& o, const most_distal_& x) { - return o << "(locset \"" << x.reg << "\")"; + return o << "(distal \"" << x.reg << "\")"; } -// Most distal points of a region +// Most proximal points of a region struct most_proximal_: locset_tag { explicit most_proximal_(region reg): reg(std::move(reg)) {} @@ -251,15 +251,19 @@ locset most_proximal(region reg) { mlocation_list thingify_(const most_proximal_& n, const mprovider& p) { auto extent = thingify(n.reg, p); - if (extent.empty()) return {}; - arb_assert(extent.test_invariants(p.morphology())); - auto most_prox = extent.cables().front(); - return {{most_prox.branch, most_prox.prox_pos}}; + + // Make a list of the proximal ends of each cable segment. + mlocation_list P; + for (const auto& c: extent.cables()) { + P.push_back({c.branch, c.prox_pos}); + } + + return minset(p.morphology(), P); } std::ostream& operator<<(std::ostream& o, const most_proximal_& x) { - return o << "(locset \"" << x.reg << "\")"; + return o << "(proximal \"" << x.reg << "\")"; } diff --git a/arbor/morph/morphology.cpp b/arbor/morph/morphology.cpp index ad93e472873d9bd3efe6fa6c9e60aab56a67151a..877334c6bf832164aeb1e32d739c17028ff86251 100644 --- a/arbor/morph/morphology.cpp +++ b/arbor/morph/morphology.cpp @@ -280,26 +280,15 @@ mlocation canonical(const morphology& m, mlocation loc) { return loc; } -// Constructing an mextent from an mcable_list consists of taking the union -// of any intersecting cables, and adding any required zero-length cables -// around fork-points. - -mcable_list build_mextent_cables(const morphology& m, const mcable_list& cables) { +// Merge overlapping cables so that none of the cables in the output overlap. +// Used by the mextent constructor. +mcable_list build_mextent_cables(const mcable_list& cables) { arb_assert(arb::test_invariants(cables)); - std::unordered_set<msize_t> branch_tails; - mcable_list cs; for (auto& c: cables) { mcable* prev = cs.empty()? nullptr: &cs.back(); - if (c.prox_pos==0) { - branch_tails.insert(m.branch_parent(c.branch)); - } - if (c.dist_pos==1) { - branch_tails.insert(c.branch); - } - if (prev && prev->branch==c.branch && prev->dist_pos>=c.prox_pos) { prev->dist_pos = std::max(prev->dist_pos, c.dist_pos); } @@ -308,42 +297,16 @@ mcable_list build_mextent_cables(const morphology& m, const mcable_list& cables) } } - if (!branch_tails.empty()) { - std::vector<mcable> fork_covers; - - for (auto b: branch_tails) { - if (b!=mnpos) fork_covers.push_back(mcable{b, 1., 1.}); - for (auto b_child: m.branch_children(b)) { - fork_covers.push_back(mcable{b_child, 0., 0.}); - } - } - util::sort(fork_covers); - - // Merge cables in cs with 0-length cables corresponding to fork covers. - mcable_list a; - a.swap(cs); - - for (auto c: util::merge_view(a, fork_covers)) { - mcable* prev = cs.empty()? nullptr: &cs.back(); - - if (prev && prev->branch==c.branch && prev->dist_pos>=c.prox_pos) { - prev->dist_pos = std::max(prev->dist_pos, c.dist_pos); - } - else { - cs.push_back(c); - } - } - } - return cs; - } -mextent::mextent(const morphology& m, const mcable_list& cables): - cables_(build_mextent_cables(m, cables)) {} +mextent::mextent(const mcable_list& cables): + cables_(build_mextent_cables(cables)) {} bool mextent::test_invariants() const { - // Checks for sortedness: + // Checks for: + // * validity of each cables. + // * sortedness of cables in list. if (!arb::test_invariants(cables_)) return false; // Check for intersections: @@ -365,35 +328,6 @@ bool mextent::test_invariants(const morphology& m) const { // Too many branches? if (!empty() && cables_.back().branch>=m.num_branches()) return false; - // Gather branches which are covered at the proximal or distal end: - std::unordered_set<msize_t> branch_heads, branch_tails; - for (auto& c: cables_) { - if (c.prox_pos==0) branch_heads.insert(c.branch); - if (c.dist_pos==1) branch_tails.insert(c.branch); - } - - // There should be an entry in branch_tails for parent(j) for all j - // in branch_heads, and an entry j in branch_heads for every j - // with parent(j) in branch_tails. - - for (auto b: branch_heads) { - msize_t parent = m.branch_parent(b); - if (parent==mnpos) { - branch_tails.insert(mnpos); - } - else if (!branch_tails.count(parent)) { - return false; - } - } - - for (auto b: branch_tails) { - for (auto child: m.branch_children(b)) { - if (!branch_heads.count(child)) { - return false; - } - } - } - return true; } @@ -481,26 +415,5 @@ mextent join(const mextent& a, const mextent& b) { return m; } -mcable_list canonical(const morphology& m, const mextent& a) { - // For zero-length cables representing isolated points, keep - // only the most proximal. All other zero-length cables should be - // elided. - - mcable_list result; - std::unordered_set<msize_t> remove_set; - - for (auto& c: a.cables()) { - if (c.prox_pos==0 && c.dist_pos>0) { - remove_set.insert(m.branch_parent(c.branch)); - } - } - for (auto& c: a.cables()) { - if (c.prox_pos==1 && remove_set.count(c.branch)) continue; - if (c.dist_pos==0 && m.branch_parent(c.branch)!=mnpos) continue; - result.push_back(c); - } - return result; -} - } // namespace arb diff --git a/arbor/morph/region.cpp b/arbor/morph/region.cpp index 75663487806e8f0135b2899faaba0c9856d03986..2d057f0d2b1cc548f4c8218f0b9edf157b73242f 100644 --- a/arbor/morph/region.cpp +++ b/arbor/morph/region.cpp @@ -9,6 +9,7 @@ #include <arbor/morph/region.hpp> #include <arbor/util/optional.hpp> +#include "util/mergeview.hpp" #include "util/span.hpp" #include "util/strprintf.hpp" #include "util/range.hpp" @@ -65,7 +66,7 @@ mextent thingify_(const cable_& reg, const mprovider& p) { if (reg.cable.branch>=p.morphology().num_branches()) { throw no_such_branch(reg.cable.branch); } - return mextent(p.morphology(), mcable_list{{reg.cable}}); + return mextent(mcable_list{{reg.cable}}); } std::ostream& operator<<(std::ostream& o, const cable_& c) { @@ -94,7 +95,7 @@ mextent thingify_(const cable_list_& reg, const mprovider& p) { if (last_branch>=p.morphology().num_branches()) { throw no_such_branch(last_branch); } - return mextent(p.morphology(), reg.cables); + return mextent(reg.cables); } std::ostream& operator<<(std::ostream& o, const cable_list_& x) { @@ -182,7 +183,7 @@ mextent thingify_(const tagged_& reg, const mprovider& p) { } } - return mextent(m, L); + return mextent(L); } std::ostream& operator<<(std::ostream& o, const tagged_& t) { @@ -204,7 +205,7 @@ mextent thingify_(const all_&, const mprovider& p) { for (auto i: util::make_span(nb)) { branches.push_back({i,0,1}); } - return mextent(p.morphology(), branches); + return mextent(branches); } std::ostream& operator<<(std::ostream& o, const all_& t) { @@ -276,7 +277,7 @@ mextent thingify_(const distal_interval_& reg, const mprovider& p) { } util::sort(L); - return mextent(m, L); + return mextent(L); } std::ostream& operator<<(std::ostream& o, const distal_interval_& d) { @@ -330,7 +331,7 @@ mextent thingify_(const proximal_interval_& reg, const mprovider& p) { } util::sort(L); - return mextent(m, L); + return mextent(L); } std::ostream& operator<<(std::ostream& o, const proximal_interval_& d) { @@ -351,7 +352,7 @@ mextent radius_cmp(const mprovider& p, region r, double val, comp_op op) { } } - return intersect(reg_extent, mextent(p.morphology(), cmp_cables)); + return intersect(reg_extent, mextent(cmp_cables)); } // Region with all segments with radius less than r @@ -435,7 +436,7 @@ mextent projection_cmp(const mprovider& p, double v, comp_op op) { for (auto i: util::make_span(m.num_branches())) { util::append(L, e.projection_cmp(i, val, op)); } - return mextent(p.morphology(), L); + return mextent(L); } // Region with all segments with projection less than val @@ -551,6 +552,74 @@ std::ostream& operator<<(std::ostream& o, const named_& x) { return o << "(region \"" << x.name << "\")"; } +// Adds all cover points to a region. +// Ensures that all valid representations of all fork points in the region are included. +struct super_ { + region reg; +}; + +region super(region r) { + return region(super_{std::move(r)}); +} + +mextent thingify_(const super_& r, const mprovider& p) { + const auto& m = p.morphology(); + auto cables = thingify(r.reg, p).cables(); + std::unordered_set<msize_t> branch_tails; + + mcable_list cs; + for (auto& c: cables) { + mcable* prev = cs.empty()? nullptr: &cs.back(); + + if (c.prox_pos==0) { + branch_tails.insert(m.branch_parent(c.branch)); + } + if (c.dist_pos==1) { + branch_tails.insert(c.branch); + } + + if (prev && prev->branch==c.branch && prev->dist_pos>=c.prox_pos) { + prev->dist_pos = std::max(prev->dist_pos, c.dist_pos); + } + else { + cs.push_back(c); + } + } + + if (!branch_tails.empty()) { + std::vector<mcable> fork_covers; + + for (auto b: branch_tails) { + if (b!=mnpos) fork_covers.push_back(mcable{b, 1., 1.}); + for (auto b_child: m.branch_children(b)) { + fork_covers.push_back(mcable{b_child, 0., 0.}); + } + } + util::sort(fork_covers); + + // Merge cables in cs with 0-length cables corresponding to fork covers. + mcable_list a; + a.swap(cs); + + for (auto c: util::merge_view(a, fork_covers)) { + mcable* prev = cs.empty()? nullptr: &cs.back(); + + if (prev && prev->branch==c.branch && prev->dist_pos>=c.prox_pos) { + prev->dist_pos = std::max(prev->dist_pos, c.dist_pos); + } + else { + cs.push_back(c); + } + } + } + + return {cs}; +} + +std::ostream& operator<<(std::ostream& o, const super_& r) { + return o << "(super " << r.reg << ")"; +} + // Intersection of two regions. diff --git a/python/cells.cpp b/python/cells.cpp index 852b51a7138e118362c4c74953dbc3df10379230..a74856bd58d46cd8ec0b70f0d19abe1c1cfc5741 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -530,7 +530,7 @@ void register_cells(pybind11::module& m) { [](arb::cable_cell& c, const char* label) {return c.concrete_locset(label);}, "label"_a, "The locations of the cell morphology for a locset label.") .def("region", - [](arb::cable_cell& c, const char* label) {return c.concrete_region(label);}, + [](arb::cable_cell& c, const char* label) {return c.concrete_region(label).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") // Discretization control. .def("compartments_on_samples", diff --git a/test/unit/test_cv_geom.cpp b/test/unit/test_cv_geom.cpp index 63dca9639ff22219ac872fe958a3e787f12b19d7..f71bb9e3d7ed5e270ec1f73142df25ab304a850d 100644 --- a/test/unit/test_cv_geom.cpp +++ b/test/unit/test_cv_geom.cpp @@ -5,6 +5,7 @@ #include <arbor/cable_cell.hpp> #include <arbor/morph/morphology.hpp> #include <arbor/morph/locset.hpp> +#include <arbor/morph/region.hpp> #include "fvm_layout.hpp" #include "util/rangeutil.hpp" @@ -126,6 +127,10 @@ TEST(cv_geom, trivial) { TEST(cv_geom, one_cv_per_branch) { using namespace common_morphology; + auto super = [] (const arb::morphology& m, arb::mcable c) { + return thingify(arb::reg::super(arb::region(c)), arb::mprovider(m)).cables(); + }; + for (auto& p: test_morphologies) { if (p.second.empty()) continue; SCOPED_TRACE(p.first); @@ -133,7 +138,8 @@ TEST(cv_geom, one_cv_per_branch) { cable_cell cell{p.second}; auto& m = cell.morphology(); - cv_geometry geom = cv_geometry_from_ends(cell, sum(ls::on_branches(0), ls::on_branches(1))); + cv_geometry geom = + cv_geometry_from_ends(cell, sum(ls::on_branches(0), ls::on_branches(1))); EXPECT_TRUE(verify_cv_children(geom)); // Expect trivial CVs at every fork point, and single-cable CVs for each branch. @@ -154,7 +160,7 @@ TEST(cv_geom, one_cv_per_branch) { EXPECT_TRUE(n_branch_child(c.branch)>1); } // Cables in trivial CV should be the same as those in the extent over the point. - EXPECT_TRUE(testing::seq_eq(mextent{m, mcable_list{c}}.cables(), cables)); + EXPECT_TRUE(testing::seq_eq(super(m,c), cables)); } else { ASSERT_EQ(1u, cables.size()); @@ -165,9 +171,9 @@ TEST(cv_geom, one_cv_per_branch) { // Confirm parent CV is fork CV: if (i>0) { - mextent fork_ext{m, mcable_list{{c.branch, 0}}}; + auto fork_ext = super(m, {c.branch, 0}); mcable_list pcables = util::assign_from(geom.cables(geom.cv_parent[i])); - ASSERT_TRUE(testing::cablelist_eq(fork_ext.cables(), pcables)); + ASSERT_TRUE(testing::cablelist_eq(fork_ext, pcables)); } } } @@ -299,10 +305,14 @@ TEST(cv_geom, location_cv) { cable_cell cell{m_reg_b6}; auto& m = cell.morphology(); - auto cv_extent = [&m](const cv_geometry& geom, auto cv) { + auto cv_extent = [](const cv_geometry& geom, auto cv) { mcable_list cl; util::assign(cl, geom.cables(cv)); - return mextent{m, cl}; + return mextent(cl); + }; + + auto super = [] (const arb::morphology& m, arb::mcable c) { + return thingify(arb::reg::super(arb::region(c)), arb::mprovider(m)).cables(); }; // Two CVs per branch, plus trivial CV at forks. @@ -323,9 +333,8 @@ TEST(cv_geom, location_cv) { mcable cable0 = cables.front(); ASSERT_TRUE(cable0.prox_pos==cable0.dist_pos); - mextent fork_ext{m, mcable_list{cable0}}; mcable_list clist = util::assign_from(cables); - ASSERT_TRUE(testing::cablelist_eq(fork_ext.cables(), clist)); + ASSERT_TRUE(testing::cablelist_eq(super(m, cable0), clist)); } } diff --git a/test/unit/test_morph_expr.cpp b/test/unit/test_morph_expr.cpp index ff5ee103e113dde1c54951e1018d374ea398f621..35b0db1c25d2cc14b36388d7047a9baca9f5d896 100644 --- a/test/unit/test_morph_expr.cpp +++ b/test/unit/test_morph_expr.cpp @@ -33,6 +33,7 @@ TEST(region, expr_repn) { auto t1 = reg::tagged(1); auto t2 = reg::tagged(2); auto t3 = reg::tagged(3); + auto s = reg::super(t3); auto all = reg::all(); EXPECT_EQ(to_string(c1), "(cable 1 0 1)"); @@ -41,11 +42,11 @@ TEST(region, expr_repn) { EXPECT_EQ(to_string(b1), "(cable 1 0 1)"); EXPECT_EQ(to_string(t1), "(tag 1)"); EXPECT_EQ(to_string(t2), "(tag 2)"); + EXPECT_EQ(to_string(s), "(super (tag 3))"); EXPECT_EQ(to_string(intersect(c1, t2)), "(intersect (cable 1 0 1) (tag 2))"); EXPECT_EQ(to_string(join(c1, t2)), "(join (cable 1 0 1) (tag 2))"); EXPECT_EQ(to_string(join(t1, t2, t3)), "(join (join (tag 1) (tag 2)) (tag 3))"); EXPECT_EQ(to_string(intersect(t1, t2, t3)), "(intersect (intersect (tag 1) (tag 2)) (tag 3))"); - EXPECT_EQ(to_string(intersect(join(c1, t2), c2)), "(intersect (join (cable 1 0 1) (tag 2)) (cable 4 0.125 0.5))"); EXPECT_EQ(to_string(all), "(all)"); } @@ -397,7 +398,7 @@ TEST(region, thingify_simple_morphologies) { {{ 0,100, 0, 2}, 2}, }; - // with a spherical root + // Build morphology with a spherical root sample_tree sm(samples, parents); mprovider mp(morphology(sm, true)); @@ -417,7 +418,7 @@ TEST(region, thingify_simple_morphologies) { auto reg1_ = distal_interval(mid0_, 74); auto reg2_ = proximal_interval(end1_, 45); auto reg3_ = proximal_interval(end1_, 91); - auto reg4_ = distal_interval(end1_, 0); + auto reg4_ = distal_interval(end1_, 100); auto reg5_ = distal_interval(start1_, 0); auto reg6_ = proximal_interval(start1_, 0); @@ -431,8 +432,8 @@ TEST(region, thingify_simple_morphologies) { EXPECT_TRUE(region_eq(mp, reg2_, mcable_list{{1,0.5,1}})); EXPECT_TRUE(region_eq(mp, reg3_, mcable_list{{0, 0.75, 1}, {1,0,1}})); EXPECT_TRUE(region_eq(mp, reg4_, mcable_list{{1,1,1}})); - EXPECT_TRUE(region_eq(mp, reg5_, mcable_list{{0,1,1}})); - EXPECT_TRUE(region_eq(mp, reg6_, mcable_list{{0,1,1}})); + EXPECT_TRUE(region_eq(mp, reg5_, mcable_list{{1,0,0}})); + EXPECT_TRUE(region_eq(mp, reg6_, mcable_list{{1,0,0}})); } } @@ -485,6 +486,7 @@ TEST(region, thingify_moderate_morphologies) { using reg::branch; using reg::all; using reg::cable; + using reg::super; auto soma = tagged(1); auto axon = tagged(2); @@ -504,9 +506,6 @@ TEST(region, thingify_moderate_morphologies) { mcable b3_{3,0,1}; cl all_ = {b0_,b1_,b2_,b3_}; - mcable c_end1_{1,1,1}; - mcable c_root_{0,0,0}; - EXPECT_TRUE(region_eq(mp, all(), all_)); EXPECT_TRUE(region_eq(mp, axon, cl{b1_})); EXPECT_TRUE(region_eq(mp, dend, cl{b0_,b3_})); @@ -516,9 +515,13 @@ TEST(region, thingify_moderate_morphologies) { // Test that intersection correctly generates zero-length cables at // parent-child interfaces. - EXPECT_TRUE(region_eq(mp, intersect(apic, dend), cl{c_end1_})); - EXPECT_TRUE(region_eq(mp, intersect(apic, axon), cl{c_end1_})); - EXPECT_TRUE(region_eq(mp, intersect(axon, dend), cl{c_root_, c_end1_})); + EXPECT_TRUE(region_eq(mp, intersect(apic, dend), cl{})); + EXPECT_TRUE(region_eq(mp, intersect(super(apic), super(dend)), cl{{1,1,1}, {2,0,0}, {3,0,0}})); + EXPECT_TRUE(region_eq(mp, intersect(apic, axon), cl{})); + EXPECT_TRUE(region_eq(mp, intersect(super(apic), axon), cl{{1,1,1}})); + EXPECT_TRUE(region_eq(mp, intersect(axon, dend), cl{})); + EXPECT_TRUE(region_eq(mp, intersect(super(axon), dend), cl{{0,0,0}, {3,0,0}})); + EXPECT_TRUE(region_eq(mp, intersect(super(dend), axon), cl{{1,0,0}, {1,1,1}})); // Test distal and proximal interavls auto start0_ = location(0, 0 ); @@ -537,39 +540,39 @@ TEST(region, thingify_moderate_morphologies) { auto reg_d_ = join(cable(0,0,0.7), cable(2,0,0.5), cable(3,0.1,0.9)); // Distal from point and/or interval - EXPECT_TRUE(region_eq(mp, distal_interval(start0_, 1000), mcable_list{{0,0,1}})); - EXPECT_TRUE(region_eq(mp, distal_interval(quar_1_, 150), mcable_list{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}})); - EXPECT_TRUE(region_eq(mp, distal_interval(mid1_, 1000), mcable_list{{1,0.5,1}, {2,0,1}, {3,0,1}})); - EXPECT_TRUE(region_eq(mp, distal_interval(mid1_, 150), mcable_list{{1,0.5,1}, {2,0,1}, {3,0,0.5}})); - EXPECT_TRUE(region_eq(mp, distal_interval(end1_, 100), mcable_list{{2,0,1},{3,0,0.5}})); - EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, mid1_), 150), mcable_list{{1,0.25,1}, {2,0,1}, {3,0,0.5}})); - EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, loc_3_1_), 150), mcable_list{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}, {3,0.65,1}})); - EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, loc_3_1_), 150), mcable_list{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}, {3,0.65,1}})); + EXPECT_TRUE(region_eq(mp, distal_interval(start0_, 1000), cl{{0,0,1}})); + EXPECT_TRUE(region_eq(mp, distal_interval(quar_1_, 150), cl{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}})); + EXPECT_TRUE(region_eq(mp, distal_interval(mid1_, 1000), cl{{1,0.5,1}, {2,0,1}, {3,0,1}})); + EXPECT_TRUE(region_eq(mp, distal_interval(mid1_, 150), cl{{1,0.5,1}, {2,0,1}, {3,0,0.5}})); + EXPECT_TRUE(region_eq(mp, distal_interval(end1_, 100), cl{{1,1,1}, {2,0,1}, {3,0,0.5}})); + EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, mid1_), 150), cl{{1,0.25,1}, {2,0,1}, {3,0,0.5}})); + EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, loc_3_1_), 150), cl{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}, {3,0.65,1}})); + EXPECT_TRUE(region_eq(mp, distal_interval(join(quar_1_, loc_3_1_), 150), cl{{1,0.25,1}, {2,0,0.75}, {3,0,0.375}, {3,0.65,1}})); // Proximal from point and/or interval - EXPECT_TRUE(region_eq(mp, proximal_interval(mid3_, 100), mcable_list{{3,0,0.5}})); - EXPECT_TRUE(region_eq(mp, proximal_interval(mid3_, 150), mcable_list{{1,0.5,1}, {3,0,0.5}})); - EXPECT_TRUE(region_eq(mp, proximal_interval(end2_, 150), mcable_list{{1,0.5,1}, {2,0,1}})); - EXPECT_TRUE(region_eq(mp, proximal_interval(end2_, 500), mcable_list{{1,0,1}, {2,0,1}})); - EXPECT_TRUE(region_eq(mp, proximal_interval(loc_3_0_, 100), mcable_list{{1,0.8,1}, {3,0,0.4}})); - EXPECT_TRUE(region_eq(mp, proximal_interval(join(loc_3_0_, mid2_), 120), mcable_list{{1,0.3,1}, {2,0,0.5}, {3, 0, 0.4}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(mid3_, 100), cl{{3,0,0.5}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(mid3_, 150), cl{{1,0.5,1}, {3,0,0.5}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(end2_, 150), cl{{1,0.5,1}, {2,0,1}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(end2_, 500), cl{{1,0,1}, {2,0,1}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(loc_3_0_, 100), cl{{1,0.8,1}, {3,0,0.4}})); + EXPECT_TRUE(region_eq(mp, proximal_interval(join(loc_3_0_, mid2_), 120), cl{{1,0.3,1}, {2,0,0.5}, {3, 0, 0.4}})); // Test radius_lt and radius_gt - EXPECT_TRUE(region_eq(mp, radius_lt(all(), 2), mcable_list{{0,0,0.55}, {1,0,0.325}, {3,0.375,0.75}})); - EXPECT_TRUE(region_eq(mp, radius_lt(all(), 3), mcable_list{{0,0,1}, {1,0,0.55}, {2,6.0/9.0,1}, {3,0.25,1}})); - EXPECT_TRUE(region_eq(mp, radius_gt(all(), 2), mcable_list{{0,0.55,1}, {1,0.325,1}, {2,0,1}, {3,0,0.375}, {3,0.75,1}})); - EXPECT_TRUE(region_eq(mp, radius_gt(all(), 3), mcable_list{{1,0.55,1}, {2,0,6.0/9.0}, {3,0,0.25}})); - - EXPECT_TRUE(region_eq(mp, radius_le(all(), 2), mcable_list{{0,0,0.55}, {1,0,0.325}, {2,1,1}, {3,0.375,0.75}})); - EXPECT_TRUE(region_eq(mp, radius_le(all(), 3), mcable_list{{0,0,1}, {1,0,0.55}, {2,6.0/9.0,1}, {3,0.25,1}})); - EXPECT_TRUE(region_eq(mp, radius_ge(all(), 2), mcable_list{{0,0.55,1}, {1,0.325,1}, {2,0,1}, {3,0,0.375}, {3,0.75,1}})); - EXPECT_TRUE(region_eq(mp, radius_ge(all(), 3), mcable_list{{1,0.55,1}, {2,0,6.0/9.0}, {3,0,0.25}})); - - EXPECT_TRUE(region_eq(mp, radius_lt(reg_a_, 2), mcable_list{{0,0.1,0.4},{3,0.375,0.4}})); - EXPECT_TRUE(region_eq(mp, radius_gt(reg_a_, 2), mcable_list{{2,0,1},{3,0.1,0.375}})); - EXPECT_TRUE(region_eq(mp, radius_lt(reg_b_, 2), mcable_list{{0,0.1,0.4}})); - EXPECT_TRUE(region_eq(mp, radius_gt(reg_c_, 2), mcable_list{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.9,1}})); - EXPECT_TRUE(region_eq(mp, radius_gt(reg_d_, 2), mcable_list{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.75,0.9}})); + EXPECT_TRUE(region_eq(mp, radius_lt(all(), 2), cl{{0,0,0.55}, {1,0,0.325}, {3,0.375,0.75}})); + EXPECT_TRUE(region_eq(mp, radius_lt(all(), 3), cl{{0,0,1}, {1,0,0.55}, {2,6.0/9.0,1}, {3,0.25,1}})); + EXPECT_TRUE(region_eq(mp, radius_gt(all(), 2), cl{{0,0.55,1}, {1,0.325,1}, {2,0,1}, {3,0,0.375}, {3,0.75,1}})); + EXPECT_TRUE(region_eq(mp, radius_gt(all(), 3), cl{{1,0.55,1}, {2,0,6.0/9.0}, {3,0,0.25}})); + + EXPECT_TRUE(region_eq(mp, radius_le(all(), 2), cl{{0,0,0.55}, {1,0,0.325}, {2,1,1}, {3,0.375,0.75}})); + EXPECT_TRUE(region_eq(mp, radius_le(all(), 3), cl{{0,0,1}, {1,0,0.55}, {2,6.0/9.0,1}, {3,0.25,1}})); + EXPECT_TRUE(region_eq(mp, radius_ge(all(), 2), cl{{0,0.55,1}, {1,0.325,1}, {2,0,1}, {3,0,0.375}, {3,0.75,1}})); + EXPECT_TRUE(region_eq(mp, radius_ge(all(), 3), cl{{1,0.55,1}, {2,0,6.0/9.0}, {3,0,0.25}})); + + EXPECT_TRUE(region_eq(mp, radius_lt(reg_a_, 2), cl{{0,0.1,0.4},{3,0.375,0.4}})); + EXPECT_TRUE(region_eq(mp, radius_gt(reg_a_, 2), cl{{2,0,1},{3,0.1,0.375}})); + EXPECT_TRUE(region_eq(mp, radius_lt(reg_b_, 2), cl{{0,0.1,0.4}})); + EXPECT_TRUE(region_eq(mp, radius_gt(reg_c_, 2), cl{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.9,1}})); + EXPECT_TRUE(region_eq(mp, radius_gt(reg_d_, 2), cl{{0,0.55,0.7},{2,0,0.5},{3,0.1,0.375},{3,0.75,0.9}})); // Test some more interesting intersections and unions. @@ -654,11 +657,11 @@ TEST(region, thingify_moderate_morphologies) { // 123456789 123456789 // |xxxxx | | lhs // | |xxxxx | rhs - // x | | rand + // | | | rand // |xxxxx |xxxxx | ror lhs = cable(0,0,.5); rhs = cable(1,0,.5); - rand = cl{{0,0,0}}; + rand = cl{}; ror = cl{{0,0,.5},{1,0,.5}}; EXPECT_TRUE(region_eq(mp, intersect(lhs, rhs), rand)); EXPECT_TRUE(region_eq(mp, join(lhs, rhs), ror)); @@ -672,11 +675,11 @@ TEST(region, thingify_moderate_morphologies) { // 123456789 123456789 // |xxxxx | | lhs // | |xxxxx | rhs - // x | | rand + // | | | rand // |xxxxx |xxxxx | ror lhs = cable(2,0,.5); rhs = cable(3,0,.5); - rand = cl{{1,1,1}}; + rand = cl{}; ror = cl{{2,0,.5},{3,0,.5}}; EXPECT_TRUE(region_eq(mp, intersect(lhs, rhs), rand)); EXPECT_TRUE(region_eq(mp, join(lhs, rhs), ror)); @@ -706,6 +709,7 @@ TEST(region, thingify_moderate_morphologies) { } } + TEST(region, thingify_complex_morphologies) { using pvec = std::vector<msize_t>; using svec = std::vector<msample>; @@ -730,6 +734,7 @@ TEST(region, thingify_complex_morphologies) { { mprovider mp(m); using reg::cable; + using reg::super; using ls::most_distal; using ls::most_proximal; @@ -739,6 +744,7 @@ TEST(region, thingify_complex_morphologies) { auto reg_d_ = join(cable(2,0,0.9), cable(3,0.1,0.1), cable(4,0.1,0.6)); auto reg_e_ = join(cable(2,0,0.9), cable(4,0.1,0.1), cable(5,0.1,0.6)); auto reg_f_ = join(cable(7,0,1), cable(2,0,0.9), cable(4,0.1,0.1), cable(5,0.1,0.6)); + auto reg_g_ = join(cable(5,0,1), cable(6,0,1)); EXPECT_TRUE(mlocationlist_eq(thingify(most_distal(reg_a_), mp), mlocation_list{{0,0.9},{1,0.4}})); EXPECT_TRUE(mlocationlist_eq(thingify(most_distal(reg_b_), mp), mlocation_list{{0,0.9},{1,0.5}})); @@ -747,12 +753,15 @@ TEST(region, thingify_complex_morphologies) { EXPECT_TRUE(mlocationlist_eq(thingify(most_distal(reg_e_), mp), mlocation_list{{5,0.6}})); EXPECT_TRUE(mlocationlist_eq(thingify(most_distal(reg_f_), mp), mlocation_list{{5,0.6},{7,1}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_a_), mp), mlocation_list{{0,0}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_b_), mp), mlocation_list{{0,0}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_c_), mp), mlocation_list{{0,0}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_d_), mp), mlocation_list{{1,1}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_e_), mp), mlocation_list{{1,1}})); - EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_f_), mp), mlocation_list{{1,1}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_a_), mp), mlocation_list{{0,0}, {1,0.1}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_b_), mp), mlocation_list{{0,0}, {1,0.1}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_c_), mp), mlocation_list{{0,0}, {1,0.1}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_d_), mp), mlocation_list{{2,0}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_e_), mp), mlocation_list{{2,0}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_f_), mp), mlocation_list{{2,0}, {7,0}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(reg_g_), mp), mlocation_list{{5,0}, {6,0}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(super(reg_f_)), mp), mlocation_list{{1,1}})); + EXPECT_TRUE(mlocationlist_eq(thingify(most_proximal(super(reg_g_)), mp), mlocation_list{{4,1}})); } } { @@ -800,6 +809,7 @@ TEST(region, thingify_complex_morphologies) { {3,0,0.179407353580315756}})); EXPECT_TRUE(region_eq(mp, z_dist_from_root_ge(20), mcable_list{{0,1,1}, + {1,0,0}, {1,0.578250901781922829,1}, {2,0,0.61499300915417734997}, {2,0.8349970039232188642,1}, diff --git a/test/unit/test_morphology.cpp b/test/unit/test_morphology.cpp index fd3bece9d4e26dbf4701f53bbe51cea0660116fd..b4423c7606f6c95b9798a7596194fea44a35c67a 100644 --- a/test/unit/test_morphology.cpp +++ b/test/unit/test_morphology.cpp @@ -664,19 +664,31 @@ TEST(mextent, invariants) { {{ 0,300, 0, 2}, 3}, }; - morphology m(sample_tree(samples, parents)); + // Instantiate morphology with spherical_root=false. + morphology m(sample_tree(samples, parents), false); - mextent x1(m, cl{{1, 0.25, 0.75}}); + mextent x1(cl{{1, 0.25, 0.75}}); ASSERT_TRUE(x1.test_invariants(m)); EXPECT_TRUE(cablelist_eq(cl{{1, 0.25, 0.75}}, x1.cables())); - mextent x2(m, cl{{1, 0., 0.75}}); + mextent x2(cl{{1, 0., 0.75}}); ASSERT_TRUE(x2.test_invariants(m)); - EXPECT_TRUE(cablelist_eq(cl{{0, 0, 0}, {1, 0., 0.75}}, x2.cables())); + EXPECT_TRUE(cablelist_eq(cl{{1, 0., 0.75}}, x2.cables())); - mextent x3(m, cl{{2, 0., 1.}}); + mextent x3(cl{{2, 0., 1.}}); ASSERT_TRUE(x3.test_invariants(m)); - EXPECT_TRUE(cablelist_eq(cl{{1, 1, 1}, {2, 0., 1.}, {3, 0., 0.}}, x3.cables())); + EXPECT_TRUE(cablelist_eq(cl{{2, 0., 1.}}, x3.cables())); + + // Test that overlapping cables are merged on construction. + mextent x4(cl{{0, 0.0, 1.0}, {0, 0.5, 0.7}, + {1, 0.2, 0.8}, {1, 0.4, 0.6}, + {2, 0.2, 0.5}, {2, 0.5, 0.6}, + {3, 0.2, 0.5}, {3, 0.6, 0.7}}); + ASSERT_TRUE(x4.test_invariants(m)); + EXPECT_TRUE(cablelist_eq(cl{{0, 0.0, 1.0}, + {1, 0.2, 0.8}, + {2, 0.2, 0.6}, + {3, 0.2, 0.5}, {3, 0.6, 0.7}}, x4.cables())); } TEST(mextent, intersects) { @@ -709,7 +721,7 @@ TEST(mextent, intersects) { morphology m(sample_tree(samples, parents)); - mextent x1(m, cl{{1, 0.25, 0.75}}); + mextent x1(cl{{1, 0.25, 0.75}}); EXPECT_TRUE(x1.intersects(mlocation{1, 0.25})); EXPECT_TRUE(x1.intersects(mlocation{1, 0.5})); EXPECT_TRUE(x1.intersects(mlocation{1, 0.75})); @@ -723,67 +735,14 @@ TEST(mextent, intersects) { EXPECT_TRUE(x1.intersects(mcable{1, 0.75, 1.0})); EXPECT_FALSE(x1.intersects(mcable{1, 0.8, 1.0})); - mextent x2(m, cl{{1, 0., 0.75}}); + mextent x2(cl{{1, 0., 0.75}}); EXPECT_TRUE(x2.intersects(mlocation{1, 0.})); - EXPECT_TRUE(x2.intersects(mlocation{0, 0.})); + EXPECT_FALSE(x2.intersects(mlocation{0, 0.})); EXPECT_FALSE(x2.intersects(mlocation{0, 1.})); - mextent x3(m, cl{{2, 0., 1.}}); + mextent x3(cl{{2, 0., 1.}}); EXPECT_FALSE(x3.intersects(mlocation{1, 0.})); - EXPECT_TRUE(x3.intersects(mlocation{1, 1.})); - EXPECT_TRUE(x3.intersects(mlocation{3, 0.})); + EXPECT_FALSE(x3.intersects(mlocation{1, 1.})); + EXPECT_FALSE(x3.intersects(mlocation{3, 0.})); EXPECT_FALSE(x3.intersects(mlocation{3, 1.})); } - -TEST(mextent, canonical) { - using namespace arb; - using testing::cablelist_eq; - - using pvec = std::vector<msize_t>; - using svec = std::vector<msample>; - using cl = mcable_list; - - // Eight samples - // no-sphere - // sample branch - // 0 0 - // 1 3 0 1 - // 2 4 0 1 - // 5 6 2 3 - // 7 3 - pvec parents = {mnpos, 0, 1, 0, 3, 4, 4, 6}; - svec samples = { - {{ 0, 0, 0, 2}, 3}, - {{ 10, 0, 0, 2}, 3}, - {{100, 0, 0, 2}, 3}, - {{ 0, 10, 0, 2}, 3}, - {{ 0,100, 0, 2}, 3}, - {{100,100, 0, 2}, 3}, - {{ 0,200, 0, 2}, 3}, - {{ 0,300, 0, 2}, 3}, - }; - - morphology m(sample_tree(samples, parents)); - - mextent x1(m, cl{{0, 0.3, 0.7}, {1, 0.5, 0.7}, {3, 0.4, 1}}); - EXPECT_TRUE(cablelist_eq(canonical(m, x1), x1.cables())); - - mcable_list cl2{{1, 0.5, 1}, {2, 0, 0.5}}; // a reduced representation - mextent x2(m, cl2); - EXPECT_FALSE(cablelist_eq(cl2, x2.cables())); - EXPECT_TRUE(cablelist_eq(cl2, canonical(m, x2))); - - mcable_list cl3{{3, 0, 0}}; // extent is cover of fork point - mextent x3(m, cl3); - EXPECT_TRUE(cablelist_eq(cl{{1, 1, 1}}, canonical(m, x3))); - - mcable_list cl4{{1, 1, 1}, {3, 0, 0.5}}; // canonical repn excludes zero-length cable - mextent x4(m, cl4); - EXPECT_TRUE(cablelist_eq(cl{{3, 0, 0.5}}, canonical(m, x4))); - - // Heads of top-level branches should be preserved. - mcable_list cl5{{1, 0, 0}}; - mextent x5(m, cl5); - EXPECT_TRUE(cablelist_eq(cl{{0, 0, 0}, {1, 0, 0}}, x5.cables())); - EXPECT_TRUE(cablelist_eq(x5.cables(), canonical(m, x5))); -}