Skip to content

Commit 1414f0a

Browse files
committed
docs: add todays status update presentation
1 parent 97e3a23 commit 1414f0a

2 files changed

Lines changed: 306 additions & 0 deletions

File tree

docs/update_06_09.pdf

114 KB
Binary file not shown.

docs/update_06_09.typ

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
#set page(
2+
width: 28.8cm,
3+
height: 16.2cm,
4+
margin: (top: 1.4cm, bottom: 1cm, left: 1.8cm, right: 1.8cm),
5+
fill: white,
6+
numbering: "1",
7+
number-align: right,
8+
)
9+
10+
#set text(font: "New Computer Modern", size: 16pt)
11+
#set par(justify: true, leading: 0.65em)
12+
13+
#let accent = rgb("#1d4ed8")
14+
#let accent-light = rgb("#eff6ff")
15+
#let accent-mid = rgb("#93c5fd")
16+
#let muted = rgb("#6b7280")
17+
#let dark = rgb("#111827")
18+
#let green-bg = rgb("#f0fdf4")
19+
#let green-border = rgb("#bbf7d0")
20+
#let green-text = rgb("#166534")
21+
22+
#let slide(title, body) = {
23+
pagebreak()
24+
block(
25+
width: 100%,
26+
inset: (top: 8pt, bottom: 10pt, left: 0pt, right: 0pt),
27+
stroke: (bottom: 2.5pt + accent),
28+
)[
29+
#text(size: 24pt, fill: accent, weight: "bold")[#title]
30+
]
31+
v(0.5cm)
32+
body
33+
}
34+
35+
#let highlight(body) = {
36+
block(
37+
width: 100%,
38+
fill: accent-light,
39+
inset: 14pt,
40+
radius: 6pt,
41+
stroke: 1pt + accent-mid,
42+
)[
43+
#body
44+
]
45+
}
46+
47+
#let proofbox(body) = {
48+
block(
49+
width: 100%,
50+
fill: green-bg,
51+
inset: 14pt,
52+
radius: 6pt,
53+
stroke: 1pt + green-border,
54+
)[
55+
#body
56+
]
57+
}
58+
59+
#let color-cell(c, label) = {
60+
block(
61+
width: 1.2em,
62+
height: 1.2em,
63+
fill: c,
64+
inset: 0pt,
65+
align(center + horizon, text(size: 0.55em, weight: "bold", fill: if label == "D" { black } else { white }, label)),
66+
)
67+
}
68+
69+
#let color-row(colors, labels, sz: 1.2em) = {
70+
let cells = ()
71+
for i in range(colors.len()) {
72+
cells.push(block(width: sz, height: sz, fill: colors.at(i), inset: 0pt))
73+
}
74+
grid(columns: (sz,) * colors.len(), rows: (sz,), gutter: 0pt, ..cells)
75+
}
76+
77+
#align(center + horizon)[
78+
#block(width: 80%)[
79+
#align(center)[
80+
#text(size: 34pt, fill: accent, weight: "bold")[
81+
VesSkel\
82+
]
83+
#v(1.0cm)
84+
#text(size: 20pt, fill: dark)[
85+
Vessel Skeletonization and Graph-Based\
86+
Phenotype Analysis in Retinal Fundus Images
87+
]
88+
#v(1.2cm)
89+
#line(length: 40%, stroke: 1.5pt + accent)
90+
#v(0.8cm)
91+
#text(size: 16pt, fill: muted)[
92+
Simon Wittmann
93+
94+
Supervisor: Anna Möller
95+
]
96+
#v(0.3cm)
97+
#text(size: 14pt, fill: muted)[
98+
09. Juni 2026
99+
]
100+
]
101+
]
102+
]
103+
104+
#slide("Implementation Progress")[
105+
#v(-0.3cm)
106+
+ Euclidean Distance Transformation #sym.arrow Radius, Diameter
107+
+ 39 features implemented (87 in comparison table)
108+
+ 2D Simple Point LUT
109+
+ Warning for unknown config settings
110+
+ lots of new tests (9 files, #sym.arrow 150+ tests)
111+
+ (not) running regression tests
112+
+ parallelized batching (`-j` / `--jobs N`)
113+
+ fast! shell completions (800 ms #sym.arrow 81 ms)
114+
+ wavefront experiment
115+
+ graph cleanup
116+
]
117+
118+
#slide("Vessel Radius & Diameter via EDT")[
119+
The *Euclidean Distance Transform* (EDT) computes the distance from each foreground pixel to the nearest background pixel.
120+
Sampling the EDT at skeleton positions yields the *local vessel radius* at every centerline point.
121+
122+
#v(0.5cm)
123+
#table(
124+
columns: (1fr, 1fr),
125+
stroke: 0.5pt + rgb("#e5e7eb"),
126+
inset: 8pt,
127+
fill: (x, y) => if y == 0 { accent-light } else if calc.odd(y) { rgb("#f9fafb") },
128+
[*Global Statistics*], [*Per-Segment Statistics*],
129+
[mean, std, min, max radius], [mean, std, min, max radius],
130+
[mean, std, min, max diameter], [mean, std, min, max diameter],
131+
)
132+
133+
#v(0.4cm)
134+
- Napari radius layer
135+
- used for junction cleanup diameter estimation
136+
- toggleable via `"vessel_radius": true` in config
137+
]
138+
139+
#slide("New Features")[
140+
141+
*Key additions since last update:*
142+
+ Per-segment radius & diameter stats (mean, std, min, max)
143+
- highest feature importance in random forest
144+
- plain SVM now achieves highest score
145+
+ Per-segment tortuosity & straightness
146+
+ Vessel area & vessel area fraction
147+
+ HGU (hyphal growth unit = total length / endpoint count)
148+
]
149+
150+
#slide("2D Simple Point LUT")[
151+
The simple point check used a flood-fill DFS for each candidate pixel, walking the 8-neighborhood to count connected foreground components. This is called *millions of times* during thinning.
152+
153+
*Solution:* Precompute the simple point status for all 256 possible 8-neighborhood patterns.
154+
155+
#v(0.3cm)
156+
#highlight[
157+
The LUT is built at module level and shared across all thinning calls via `@njit(cache=True)`.
158+
]
159+
160+
#v(0.3cm)
161+
The same approach cannot be used for 3D (26-neighborhood = $2^26$ entries = 67 million -- too large). The 3D implementation keeps the octant-based Euler check.
162+
]
163+
164+
#slide("Junction Triangle Cleanup I")[
165+
*Problem:* Wide vessel junctions appear as thick blobs to the thinning algorithm. Instead of producing a single junction pixel, the skeleton contains small triangle- or diamond-shaped cycles:
166+
167+
#align(center)[
168+
```text
169+
o ---o---
170+
/ \ / | \
171+
/ \ -> / | \
172+
o-----o / | \
173+
/ | \
174+
```
175+
]
176+
177+
These artifacts inflate bifurcation counts and distort topology metrics.
178+
179+
*Simple Algorithm:*
180+
+ Build junction graph via skan #sym.arrow find cycles via networkx
181+
+ Compute perimeter of each cycle (sum of Euclidean edge lengths)
182+
+ Estimate local vessel diameter from EDT at cycle node positions
183+
+ Filter: collapse cycles where $"perimeter" < "threshold_factor" times "diameter"$
184+
+ Merge overlapping cycles #sym.arrow collapse to centroid
185+
+ Reconnect external vessel arms
186+
187+
]
188+
189+
#slide("Junction Triangle Cleanup II")[
190+
191+
#v(0.3cm)
192+
*New Config Values:*
193+
- `"junction_cleanup": true`
194+
- `"cleanup_threshold_factor": 5.0` (range 2.5–10)
195+
196+
#highlight[
197+
When cleanup is enabled, the cleaned skeleton replaces the original for *every* downstream stage: saved files, summary features, napari layers.
198+
]
199+
]
200+
201+
#slide("CLI Improvements")[
202+
#columns(2, gutter: 1.5em)[
203+
204+
*Parallel Batch Processing*
205+
+ `vesskel run --jobs N` / `-j N`
206+
+ `ProcessPoolExecutor` spawns workers
207+
+ Each worker processes one image independently
208+
+ Errors are collected, processing continues
209+
+ full HRF now takes #sym.approx 23s instead of before \ #sym.approx 50s on my machine
210+
211+
*Config quality-of-life*
212+
+ Unknown keys in config JSON produce a warning
213+
#colbreak()
214+
215+
*Shell Completions*
216+
+ Generated via `argcomplete`
217+
+ naive: #sym.approx 800 ms (numpy, PIL, pipeline imported eagerly)
218+
+ After: #sym.approx 81 ms (heavy imports deferred after argcomplete guard)
219+
+ Supports bash, zsh, powershell
220+
+ Regression test enforces completion speed
221+
]
222+
]
223+
224+
#slide("Testing")[
225+
#table(
226+
columns: (auto, auto, 1fr),
227+
stroke: 0.5pt + rgb("#e5e7eb"),
228+
inset: 7pt,
229+
fill: (x, y) => if y == 0 { accent-light } else if calc.odd(y) { rgb("#f9fafb") },
230+
[*File*], [*Tests*], [*Scope*],
231+
[`test_cli.py`], [~25], [completions, input discovery, batch (seq + par)],
232+
[`test_config.py`], [~20], [serialization, validation, unknown keys],
233+
[`test_features.py`], [~20], [tortuosity, fractal dim, radii, graph],
234+
[`test_pipeline.py`], [~25], [full pipeline, all option toggles],
235+
[`test_napari_layers.py`], [~18], [layer generation, toggle combos],
236+
[`test_utils.py`], [~12], [`to_binary` behavior],
237+
[`test_2d_thinning_regression.py`], [45], [HRF images vs saved baselines],
238+
[`test_3d_thinning_regression.py`], [1], [brain volume vs saved baselines],
239+
[`test_3d_skimage_comparison.py`], [1], [vesskel = skimage bit-identical],
240+
)
241+
242+
+ Regression tests now marked `@pytest.mark.slow`
243+
+ Removed parallel test runner, not really needed anymore
244+
]
245+
246+
#slide("Wavefront Experiment")[
247+
#columns(2, gutter: 1.5em)[
248+
249+
*Problem:* The recheck-removal phase is sequential -- removing pixel A invalidates B's precomputed simplicity check.
250+
251+
*Key insight:* Two pixels can be processed in parallel if they are *not* 8-neighbors. The 8-neighbor graph on a 2D grid has chromatic number $4$.
252+
253+
#sym.arrow Color pixels by $(c % 2, r % 2)$:
254+
#grid(
255+
columns: (1.2em, 1.2em, 1.2em, 1.2em, 1.2em, 1.2em, 1.2em, 1.2em),
256+
rows: (1.2em, 1.2em, 1.2em, 1.2em),
257+
gutter: 0pt,
258+
stroke: 0.3pt + rgb("#cccccc"),
259+
align: center + horizon,
260+
..range(32).map(i => {
261+
let r = calc.quo(i, 8)
262+
let c = calc.rem(i, 8)
263+
let cpar = calc.rem(c, 2)
264+
let rpar = calc.rem(r, 2)
265+
if cpar == 0 and rpar == 0 {
266+
color-cell(rgb("#4A90D9"), "A")
267+
} else if cpar == 1 and rpar == 0 {
268+
color-cell(rgb("#E67E22"), "B")
269+
} else if cpar == 0 and rpar == 1 {
270+
color-cell(rgb("#27AE60"), "C")
271+
} else {
272+
color-cell(rgb("#F1C40F"), "D")
273+
}
274+
})
275+
)
276+
277+
#colbreak()
278+
279+
*Results:*
280+
+ neglegible performance improvements (x% - 1x% faster) (second phase is not that time consuming in general)
281+
+ different output, missing branches, not medial axis thinning anymore
282+
]
283+
]
284+
285+
#slide("Summary & Next Steps")[
286+
287+
#columns(2, gutter: 1.5em)[
288+
*What I did:*
289+
+ EDT-based vessel radius & diameter (global, per-segment)
290+
+ more new features (now 39)
291+
+ 2D Simple Point LUT #sym.arrow faster thinning
292+
+ junction cleanup
293+
+ cli improvements (parallel batching, completions, config warnings)
294+
+ more tests
295+
+ Wavefront experiment (4-coloring insight)
296+
297+
#colbreak()
298+
299+
*Next steps:*
300+
+ Group Feature Table into Categories
301+
+ more features
302+
+ 3D experiments with graph cleanup
303+
+ more preprocessing options? simple hole filling could help on some cases in HRF
304+
+ revisit phenotype classification
305+
]
306+
]

0 commit comments

Comments
 (0)