Skip to content

Commit 4032849

Browse files
committed
dont recompute mesh but reuse it. if not provided return NaN in compute cristae junctions
1 parent eea100c commit 4032849

2 files changed

Lines changed: 37 additions & 30 deletions

File tree

synapse_net/cristae_analysis.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -608,24 +608,25 @@ def compute_junction_distances(
608608
the nearest vertex of a triangle mesh, and pairwise surface geodesics are computed with
609609
``bioimage_cpp.distance.geodesic_distances_mesh``. The mesh is the **eroded-mito (lumen) surface**
610610
passed in as ``mesh_vertices``/``mesh_faces`` by :func:`_single_mito_row` (a clean, single-wall
611-
surface at the membrane's inner edge); if none is supplied a mesh is built from ``membrane_mask``
612-
as a convenience. When no usable surface mesh exists (empty membrane / degenerate mesh), the
613-
junction distances are NaN.
611+
surface at the membrane's inner edge). If no mesh is supplied — or no usable surface mesh exists
612+
(empty membrane / degenerate mesh) — the junction distances are NaN. (There is no membrane-band
613+
fallback mesh: the metric is defined on the lumen surface, and meshing the thick membrane band
614+
would give a different, capped double-wall surface.)
614615
615616
A Clark-Evans nearest-neighbour index summarises whether the junctions are clustered.
616617
617618
Args:
618619
contact_labels: Integer junction label array (0 = background, 1..n = junctions),
619620
e.g. the first return value of :func:`detect_contact_sites`.
620-
membrane_mask: Binary mitochondrial membrane mask the junctions sit on (used to build a
621-
fallback mesh when ``mesh_vertices``/``mesh_faces`` are not supplied).
621+
membrane_mask: Binary mitochondrial membrane mask the junctions sit on. Only used for the
622+
empty-membrane early-out (no membrane → NaN); it is not meshed.
622623
voxel_size: Voxel size in nm — scalar or dict with "z"/"y"/"x" keys.
623624
surface_area_nm2: Membrane/mito surface area used as the reference area for the
624625
Clark-Evans expectation. If None or non-positive, the clustering index is NaN.
625626
n_jobs: 1 = serial, -1 = all cores (forwarded to the mesh solver's thread count).
626627
mesh_vertices: Optional (n_vertices, 3) mesh vertices in nm (unpadded mask frame; see
627-
:func:`_surface_mesh`) — the eroded-mito (lumen) surface. If omitted, a mesh is built
628-
from ``membrane_mask``.
628+
:func:`_surface_mesh`) — the eroded-mito (lumen) surface. If omitted, the junction
629+
distances are NaN.
629630
mesh_faces: Optional (n_faces, 3) triangle indices matching ``mesh_vertices``.
630631
631632
Returns:
@@ -659,12 +660,9 @@ def compute_junction_distances(
659660
)
660661

661662
if mesh_vertices is not None and mesh_faces is not None and len(mesh_faces) > 0:
662-
mesh = (mesh_vertices, mesh_faces)
663+
distance_matrix = _junction_matrix_mesh(centroids, sampling, mesh_vertices, mesh_faces, n_jobs)
663664
else:
664-
mesh = _surface_mesh(membrane, sampling)
665-
distance_matrix = (
666-
_junction_matrix_mesh(centroids, sampling, mesh[0], mesh[1], n_jobs) if mesh is not None else None
667-
)
665+
distance_matrix = None
668666

669667
if distance_matrix is None:
670668
summary = dict(_JUNCTION_DISTANCE_NAN)

test/test_cristae_analysis.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -570,12 +570,13 @@ def _flat_membrane_with_junctions(positions, shape=(12, 40, 40), z=6):
570570
labels[z, y, x] = i
571571
return labels, membrane
572572

573-
# These exercise compute_junction_distances directly; with no mesh supplied it meshes the given
574-
# membrane and takes the surface geodesic, so they require the bioimage-cpp geodesic API.
573+
# These exercise compute_junction_distances directly. The metric is defined on the lumen surface,
574+
# so the mesh must be supplied explicitly (there is no membrane-band fallback); here the membrane's
575+
# own surface is meshed as the test fixture. They require the bioimage-cpp geodesic API.
575576
def test_geodesic_follows_bent_membrane(self):
576577
# An L-shaped membrane: the geodesic around the bend is longer than the straight line
577578
# between the two seed voxels.
578-
from synapse_net.cristae_analysis import compute_junction_distances
579+
from synapse_net.cristae_analysis import compute_junction_distances, _surface_mesh
579580
shape = (5, 40, 40)
580581
membrane = np.zeros(shape, dtype=bool)
581582
z = 2
@@ -584,7 +585,10 @@ def test_geodesic_follows_bent_membrane(self):
584585
labels = np.zeros(shape, dtype=np.int32)
585586
labels[z, 5, 6] = 1 # near the far end of the horizontal arm
586587
labels[z, 33, 34] = 2 # near the far end of the vertical arm
587-
dist, _ = compute_junction_distances(labels, membrane, voxel_size=1.0)
588+
verts, faces = _surface_mesh(membrane, np.ones(3))
589+
dist, _ = compute_junction_distances(
590+
labels, membrane, voxel_size=1.0, mesh_vertices=verts, mesh_faces=faces
591+
)
588592
straight = np.sqrt((33 - 5) ** 2 + (34 - 6) ** 2)
589593
self.assertGreater(dist[0, 1], straight * 1.2)
590594

@@ -600,16 +604,21 @@ def test_fewer_than_two_junctions_is_nan(self):
600604
def test_clustered_index_lower_than_dispersed(self):
601605
# Same membrane/area and junction count, but tightly grouped vs evenly spread:
602606
# the clustered arrangement must give a smaller Clark-Evans index.
603-
from synapse_net.cristae_analysis import compute_junction_distances
607+
from synapse_net.cristae_analysis import compute_junction_distances, _surface_mesh
604608
area = 40.0 * 40.0
605609
clustered_pos = [(18, 18), (18, 20), (20, 18), (20, 20)]
606610
dispersed_pos = [(8, 8), (8, 30), (30, 8), (30, 30)]
607-
_, clustered = compute_junction_distances(
608-
*self._flat_membrane_with_junctions(clustered_pos), voxel_size=1.0, surface_area_nm2=area
609-
)
610-
_, dispersed = compute_junction_distances(
611-
*self._flat_membrane_with_junctions(dispersed_pos), voxel_size=1.0, surface_area_nm2=area
612-
)
611+
612+
def _cjd(positions):
613+
labels, membrane = self._flat_membrane_with_junctions(positions)
614+
verts, faces = _surface_mesh(membrane, np.ones(3))
615+
return compute_junction_distances(
616+
labels, membrane, voxel_size=1.0, surface_area_nm2=area,
617+
mesh_vertices=verts, mesh_faces=faces,
618+
)
619+
620+
_, clustered = _cjd(clustered_pos)
621+
_, dispersed = _cjd(dispersed_pos)
613622
self.assertLess(clustered["junction_clustering_index"], dispersed["junction_clustering_index"])
614623

615624

@@ -936,18 +945,18 @@ def test_clipped_mito_open_mesh_finite(self):
936945
area_closed = _surface_area(mito_binary, np.ones(ndim))
937946
self.assertLess(area_open, area_closed) # the fabricated z-caps are no longer counted
938947

939-
def test_primitive_mesh_on_flat_membrane(self):
940-
# With no mesh supplied the primitive meshes the given membrane surface — finite, positive,
941-
# and of the right order (junctions ~17-20 apart). Absolute accuracy on a 1-voxel sheet is not
942-
# asserted (that degenerate mesh is only the fallback; the pipeline meshes the thicker lumen).
948+
def test_no_mesh_supplied_is_nan(self):
949+
# With no lumen mesh supplied the junction distances are NaN — there is no membrane-band
950+
# fallback mesh (the metric is defined on the eroded-mito lumen surface).
943951
from synapse_net.cristae_analysis import compute_junction_distances
944952
labels, membrane = TestJunctionDistances._flat_membrane_with_junctions(
945953
[(20, 8), (20, 28), (8, 20), (32, 20)]
946954
)
947955
_, summary = compute_junction_distances(labels, membrane, 1.0, surface_area_nm2=1000.0)
948-
mean_nn = summary["mean_nn_junction_distance_nm"]
949-
self.assertTrue(np.isfinite(mean_nn) and mean_nn > 0)
950-
self.assertLess(mean_nn, 100.0) # sane order of magnitude, not a runaway path
956+
self.assertEqual(summary["junction_count"], 4)
957+
self.assertTrue(np.isnan(summary["mean_nn_junction_distance_nm"]))
958+
self.assertTrue(np.isnan(summary["median_nn_junction_distance_nm"]))
959+
self.assertTrue(np.isnan(summary["junction_clustering_index"]))
951960

952961

953962
_EXPECTED_COLUMNS = [

0 commit comments

Comments
 (0)