fix(flomni): reuse the same phase table for 360deg tomo instead of two
Supersedes the complementary even/odd-eighths tables from 900c810 with
a single shared phase_eighths table used in both 180 and 360 mode, so
subtomo N carries the same phase-tier role regardless of
tomo_angle_range. The complementary property of the low/high phase
sets now falls out as a consequence of the original table's structure
instead of needing a separately derived table.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2317,25 +2317,35 @@ class Flomni(
|
||||
the original scheme). In 360 mode each sub-tomogram instead
|
||||
covers only a 180-degree span - never the full circle - split by
|
||||
subtomo_number % 4 into a "low" half [0,180) (n%4 in (1,0)) and a
|
||||
"high" half [180,360) (n%4 in (2,3)). Without this split, every
|
||||
sub-tomogram's own 360-degree sweep would contain angle pairs
|
||||
exactly 180 degrees apart - redundant tomographic information,
|
||||
since a measurement 180 degrees from one already taken
|
||||
contributes no new information.
|
||||
"high" half [180,360) (n%4 in (2,3)), each sub-tomogram in the
|
||||
high half literally its low-half counterpart's own 180-degree
|
||||
scan, shifted by +180 (per subtomo_number, using the SAME
|
||||
phase_eighths value as 180 mode - not a different phase table).
|
||||
Without this split, every sub-tomogram's own 360-degree sweep
|
||||
would contain angle pairs exactly 180 degrees apart - redundant
|
||||
tomographic information, since a measurement 180 degrees from one
|
||||
already taken contributes no new information.
|
||||
|
||||
Critically, the low half and high half use COMPLEMENTARY halves
|
||||
of the original 8-way phase_eighths table (low: the even eighths
|
||||
{0,2,4,6}; high: the odd eighths {1,3,5,7}), not the same phases
|
||||
shifted by 180 - using the same phases would make every low-half
|
||||
angle exactly 180 degrees from a high-half angle, reintroducing
|
||||
the redundancy this scheme exists to remove. With complementary
|
||||
phases, low and high each independently interlace their own half
|
||||
at spacing step/4, but together - once the high half is folded
|
||||
mod 180 - they reconstruct the full 8-way, step/8 grid with every
|
||||
position hit EXACTLY once: the combined 360-degree angle set is
|
||||
mod-180-identical to what an equivalent 180-degree scan would
|
||||
produce, at the same total projection count, with zero redundant
|
||||
measurements anywhere.
|
||||
Reusing the same per-subtomo-number phase_eighths value in both
|
||||
modes (rather than reassigning phases by acquisition order within
|
||||
each half) is what makes subtomo N carry the same
|
||||
interlacing/refinement role in both modes - e.g. subtomo 2 always
|
||||
represents the phase_eighths[2] tier, whether that's within
|
||||
[0,180) (180 mode) or shifted to [180,360) (360 mode, since
|
||||
subtomo 2 % 4 == 2, a "high" sub-tomogram) - instead of an
|
||||
unrelated phase suddenly appearing under the same subtomo number
|
||||
depending on tomo_angle_range. Since the low half uses
|
||||
subtomo_numbers {1,4,5,8} (phase_eighths values {0,6,1,7}) and the
|
||||
high half uses {2,3,6,7} (phase_eighths values {4,2,5,3}), these
|
||||
two phase sets are complementary (their union is all 8 eighths,
|
||||
with no overlap) purely as a property of the original
|
||||
phase_eighths table's structure - not something this branch has
|
||||
to separately re-derive. Once the high half is folded mod 180,
|
||||
low and high together reconstruct the full 8-way, step/8 grid
|
||||
with every position hit EXACTLY once: the combined 360-degree
|
||||
angle set is mod-180-identical to what an equivalent 180-degree
|
||||
scan would produce, at the same total projection count, with zero
|
||||
redundant measurements anywhere.
|
||||
|
||||
Returns:
|
||||
angles (np.ndarray): the N angles (degrees) for this sub-tomogram.
|
||||
@@ -2357,20 +2367,19 @@ class Flomni(
|
||||
N = int(180.0 / tomo_angle_stepsize)
|
||||
step = 180.0 / N
|
||||
|
||||
# Same phase_eighths table in both modes - see docstring above for
|
||||
# why reusing it (rather than a separately-derived phase table for
|
||||
# 360 mode) is what keeps subtomo N's role consistent between
|
||||
# modes.
|
||||
phase_eighths = {1: 0, 2: 4, 3: 2, 4: 6, 5: 1, 6: 5, 7: 3, 8: 7}
|
||||
phase = step / 8.0 * phase_eighths[subtomo_number]
|
||||
|
||||
if tomo_angle_range == 180:
|
||||
base = 0.0
|
||||
phase_eighths = {1: 0, 2: 4, 3: 2, 4: 6, 5: 1, 6: 5, 7: 3, 8: 7}
|
||||
phase = step / 8.0 * phase_eighths[subtomo_number]
|
||||
forward = bool(subtomo_number % 2)
|
||||
else:
|
||||
# Low half (subtomos 1,4,5,8): even eighths. High half
|
||||
# (subtomos 2,3,6,7): odd eighths. Complementary, not shared -
|
||||
# see docstring above for why that's required to avoid
|
||||
# redundant 180-degrees-apart measurements.
|
||||
eighths = {1: 0, 4: 4, 5: 2, 8: 6, 2: 1, 3: 5, 6: 3, 7: 7}
|
||||
mod4 = subtomo_number % 4
|
||||
base = 0.0 if mod4 in (1, 0) else 180.0
|
||||
phase = step / 8.0 * eighths[subtomo_number]
|
||||
forward = mod4 in (1, 2)
|
||||
|
||||
if not explicit_start_angle:
|
||||
|
||||
@@ -94,6 +94,27 @@ def test_360_mode_mod_180_identical_to_180_mode(stepsize):
|
||||
assert set_folded == set180
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stepsize", STEPSIZES)
|
||||
@pytest.mark.parametrize("n", range(1, 9))
|
||||
def test_360_mode_subtomo_keeps_same_phase_tier_as_180_mode(stepsize, n):
|
||||
"""Subtomo N must carry the same phase_eighths tier in 360 mode as in
|
||||
180 mode - e.g. subtomo 2 always represents the phase_eighths[2] tier,
|
||||
whether that's within [0,180) (180 mode) or shifted +180 into
|
||||
[180,360) (360 mode). A previous implementation reassigned phases by
|
||||
acquisition-order-within-half instead of reusing the per-subtomo-number
|
||||
value, so e.g. the angle physically corresponding to subtomo 2's tier
|
||||
in 180 mode ended up under subtomo 4 in 360 mode - confusing, and
|
||||
breaks any expectation that "subtomo N" means the same thing across
|
||||
modes."""
|
||||
angles180, _, N, step = plan(n, 180, stepsize)
|
||||
angles360, _, _, _ = plan(n, 360, stepsize)
|
||||
|
||||
base = 0.0 if n % 4 in (1, 0) else 180.0
|
||||
offset180 = set(np.round(np.mod(angles180, step), 6))
|
||||
offset360 = set(np.round(np.mod(angles360 - base, step), 6))
|
||||
assert offset360 == offset180
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tomo_angle_range", [180, 360])
|
||||
@pytest.mark.parametrize("stepsize", STEPSIZES)
|
||||
def test_resume_round_trip(tomo_angle_range, stepsize):
|
||||
|
||||
Reference in New Issue
Block a user