Skip to content

Commit 5d7a4d9

Browse files
committed
fixing decimation
1 parent 7f218b6 commit 5d7a4d9

5 files changed

Lines changed: 67 additions & 17 deletions

File tree

requirements.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ certifi
1111
flybrains>=0.6.3 # BANC support arrived in 0.6.x; 0.3.0 has none
1212
navis # transform registry + xform_brain
1313
trimesh # mesh IO and plane slicing
14-
fast-simplification # quadric decimation of the served OBJs (ISSUES.md IMG-1)
14+
# Quadric decimation of the served OBJs (docs/ISSUES.md IMG-1). MUST be version-marked:
15+
# 0.2.0's metadata says requires-python >=3.9 but its code uses PEP 604 unions
16+
# (`float | None` in simplify.py), which is a SyntaxError-at-import on 3.9. Every 0.1.x is
17+
# clean. pyproject.toml reads this file as its base dependencies, so an unpinned entry here
18+
# overrides the marker in the [images] extra — pin it in both places or neither works.
19+
fast-simplification<0.2 ; python_version < "3.10"
20+
fast-simplification ; python_version >= "3.10"
1521
shapely # required by trimesh.slice_plane, even with cap=False
1622
scipy # map_coordinates for the baked-field lookup
1723
cloud-volume # BANC precomputed mesh fetch

src/.DS_Store

0 Bytes
Binary file not shown.

src/vfb_connectomics_import/images/io.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,22 @@
2626
#: product key -> served filename. The keys are what `--products` accepts.
2727
PRODUCTS = {'swc': 'volume.swc', 'obj': 'volume_man.obj', 'nrrd': 'volume.nrrd'}
2828

29-
#: Globs for everything this loader considers "the served image". Thumbnails are included
30-
#: because a thumbnail depicts the OLD alignment: leaving one beside a replaced volume is a
31-
#: silently stale image.
29+
#: Globs for everything VFB serves per neuron. Used to decide whether an image exists at
30+
#: all, and to clear one that turns out to be spurious — NOT to decide what a successful
31+
#: replacement sweeps away (see SWEEP_AFTER_SWAP).
32+
#: Confirmed served 2026-08-26: volume.swc, volume.nrrd, volume_man.obj, volume.obj,
33+
#: volume.wlz, thumbnail.png, thumbnailT.png.
3234
SERVED_GLOBS = ('volume*', 'thumbnail*')
3335

36+
#: Extra files removed after a successful swap, beyond the products this run wrote.
37+
#: Decided 2026-08-26: replace the three we generate, additionally delete `volume.obj`,
38+
#: and **leave everything else** — `volume.wlz`, `thumbnail*` and anything new. Those will
39+
#: be briefly out of sync with the new alignment, which is accepted: other jobs refresh
40+
#: them in time, and the alternative (deleting products nothing here regenerates) is worse.
41+
#: An earlier version swept `volume*` + `thumbnail*` wholesale, which removed 5 files per
42+
#: neuron including `volume.wlz`.
43+
SWEEP_AFTER_SWAP = ('volume.obj',)
44+
3445
#: Statuses meaning "this neuron has been dealt with; do not redo it on resume".
3546
#: 'error' is deliberately absent — errors must be retried.
3647
TERMINAL = frozenset({'replaced', 'created', 'deleted_spurious', 'skipped',
@@ -94,12 +105,23 @@ def swap(self, built):
94105
95106
Returns (wrote, removed) as basename lists. `os.replace` overwrites, so there is
96107
no delete-then-write step and no window in which a served file is missing.
108+
109+
The sweep is **narrow and explicit**: `SWEEP_AFTER_SWAP`, plus any product this
110+
loader manages that this run did not write (so `--products swc,nrrd` does not
111+
leave last alignment's `volume_man.obj` behind). Everything else VFB serves is
112+
left untouched — see the SWEEP_AFTER_SWAP note.
97113
"""
98114
wrote = []
99115
for tmp, final in built.items():
100116
os.replace(tmp, final)
101117
wrote.append(os.path.basename(final))
102-
removed = self._sweep(keep=set(built.values()))
118+
119+
just_wrote = {os.path.basename(p) for p in built.values()}
120+
targets = set(SWEEP_AFTER_SWAP) | set(PRODUCTS.values())
121+
removed = []
122+
for name in sorted(targets - just_wrote):
123+
if _unlink(os.path.join(self.folder, name)):
124+
removed.append(name)
103125
return sorted(wrote), removed
104126

105127
def archive_to(self, dest):

src/vfb_connectomics_import/images/transforms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def __init__(self, npy_path, mmap=True):
133133

134134
def __repr__(self):
135135
return (f'<BakedField {os.path.basename(self.path)} -> {self.target}, '
136-
f'grid {tuple(self.shape)} @ {self.step[0]:.0f} nm>')
136+
f'grid {tuple(int(n) for n in self.shape)} @ {self.step[0]:.0f} nm>') # int(): numpy 2 reprs bare scalars as np.int64(445)
137137

138138
def __call__(self, pts):
139139
pts = np.asarray(pts, float)

tests/test_images.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,32 @@
1919
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'src'))
2020

2121
from vfb_connectomics_import.images.io import (
22-
PRODUCTS, TERMINAL, Ledger, OutputSet, partial_path)
22+
PRODUCTS, SWEEP_AFTER_SWAP, TERMINAL, Ledger, OutputSet, partial_path)
2323

2424
ALL = ('swc', 'obj', 'nrrd')
2525

2626

2727
# --------------------------------------------------------------------------- helpers
28-
def plant(folder, thumbnail=True, volumes=ALL):
28+
#: What VFB actually serves per neuron, confirmed live 2026-08-26. Only the first three
29+
#: are written by this loader; the rest belong to other jobs.
30+
SERVED = ('volume.swc', 'volume.nrrd', 'volume_man.obj',
31+
'volume.obj', 'volume.wlz', 'thumbnail.png', 'thumbnailT.png')
32+
33+
34+
def plant(folder, thumbnail=True, volumes=ALL, extras=True):
2935
"""An existing v626-era image, as the loader will find on almost every neuron."""
3036
os.makedirs(folder, exist_ok=True)
3137
for k in volumes:
3238
with open(os.path.join(folder, PRODUCTS[k]), 'w') as fh:
3339
fh.write(f'OLD v626 {k}')
40+
if extras:
41+
for n in ('volume.obj', 'volume.wlz'):
42+
with open(os.path.join(folder, n), 'w') as fh:
43+
fh.write('OLD ' + n)
3444
if thumbnail:
35-
with open(os.path.join(folder, 'thumbnail.png'), 'w') as fh:
36-
fh.write('OLD THUMB')
45+
for n in ('thumbnail.png', 'thumbnailT.png'):
46+
with open(os.path.join(folder, n), 'w') as fh:
47+
fh.write('OLD THUMB')
3748

3849

3950
def names(folder):
@@ -66,16 +77,27 @@ def test_partial_suffix_precedes_the_extension():
6677

6778

6879
# ------------------------------------------------------------------------ the swap is safe
69-
def test_swap_replaces_content_and_clears_thumbnail():
80+
def test_swap_replaces_three_products_and_deletes_only_volume_obj():
81+
"""The agreed contract (2026-08-26): replace swc / nrrd / volume_man.obj, additionally
82+
delete volume.obj, and LEAVE volume.wlz and the thumbnails. Those go briefly out of
83+
sync with the new alignment, which is accepted -- other jobs refresh them, and
84+
deleting products nothing here regenerates would be worse."""
7085
with tempfile.TemporaryDirectory() as d:
7186
plant(d)
7287
out = OutputSet(d, ALL)
7388
assert read(d, 'swc') == 'OLD v626 swc'
7489
wrote, removed = out.swap(build(out, ALL))
7590
assert wrote == sorted(PRODUCTS[k] for k in ALL)
76-
assert removed == ['thumbnail.png'], 'a stale thumbnail depicts the old alignment'
91+
assert removed == ['volume.obj'], removed
7792
assert read(d, 'swc') == 'NEW swc'
78-
assert 'thumbnail.png' not in names(d)
93+
for n in ('volume.wlz', 'thumbnail.png', 'thumbnailT.png'):
94+
assert n in names(d), n + ' must NOT be swept'
95+
96+
97+
def test_sweep_list_is_exactly_volume_obj():
98+
"""Pinned because an earlier version globbed volume*/thumbnail* and removed five files
99+
per neuron, including volume.wlz, which nothing in this repo regenerates."""
100+
assert SWEEP_AFTER_SWAP == ('volume.obj',)
79101

80102

81103
def test_swap_never_leaves_a_served_file_missing():
@@ -99,8 +121,8 @@ def test_swap_sweeps_a_product_dropped_from_products():
99121
plant(d, thumbnail=False)
100122
out = OutputSet(d, ('swc', 'nrrd'))
101123
_, removed = out.swap(build(out, ('swc', 'nrrd')))
102-
assert removed == ['volume_man.obj']
103-
assert names(d) == ['volume.nrrd', 'volume.swc']
124+
assert removed == ['volume.obj', 'volume_man.obj'], removed
125+
assert 'volume.wlz' in names(d), 'still not ours to delete'
104126

105127

106128
# ------------------------------------------------------------- existing-image bookkeeping
@@ -138,8 +160,8 @@ def test_remove_all_clears_volumes_and_thumbnails():
138160
with tempfile.TemporaryDirectory() as d:
139161
plant(d)
140162
removed = OutputSet(d, ALL).remove_all()
141-
assert removed == ['thumbnail.png', 'volume.nrrd', 'volume.swc', 'volume_man.obj']
142-
assert names(d) == []
163+
assert sorted(removed) == sorted(SERVED), removed
164+
assert names(d) == [], 'a spurious image must not be left half-served'
143165

144166

145167
# --------------------------------------------------------------------- the deletion policy

0 commit comments

Comments
 (0)