|
| 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 | + 23. Juni 2026 |
| 99 | + ] |
| 100 | + ] |
| 101 | + ] |
| 102 | +] |
| 103 | + |
| 104 | +#slide("Implementation Progress")[ |
| 105 | + #v(-0.3cm) |
| 106 | + + preprocessing: binary closing & hole filling |
| 107 | + + new features & updated Feature Comparison Table |
| 108 | + + 3D thinning performance (#sym.arrow parallel scanning, epoch removal tracking) |
| 109 | + + benchmark results (2D + 3D vs skimage & VesselVio) |
| 110 | + + nicer CLI UX (shorter subcommands, worker feedback) |
| 111 | + + paper draft started |
| 112 | + + prediction notebook refactored |
| 113 | +] |
| 114 | + |
| 115 | +#slide("Preprocessing: Binary Closing & Hole Filling")[ |
| 116 | + *Binary closing* bridges small gaps in segmented vessels before thinning |
| 117 | + |
| 118 | + *Hole filling* fills enclosed background regions inside vessels |
| 119 | + |
| 120 | + #v(0.3cm) |
| 121 | + #table( |
| 122 | + columns: (auto, auto, 1fr), |
| 123 | + stroke: 0.5pt + rgb("#e5e7eb"), |
| 124 | + inset: 7pt, |
| 125 | + fill: (x, y) => if y == 0 { accent-light } else if calc.odd(y) { rgb("#f9fafb") }, |
| 126 | + [*Config Key*], [*Default*], [*Description*], |
| 127 | + [`closing_iterations`], [`0`], [binary closing iterations (0 = off)], |
| 128 | + [`fill_holes`], [`false`], [fill enclosed background regions], |
| 129 | + [`max_hole_size`], [`0`], [max filled hole area (0 = unlimited)], |
| 130 | + [`show_preprocessed`], [`false`], [show preprocessed binary in napari], |
| 131 | + ) |
| 132 | + |
| 133 | + #v(0.3cm) |
| 134 | + #highlight[ |
| 135 | + Supporting tests: in `test_preprocessing.py` covering closing, hole filling, size thresholding. |
| 136 | + ] |
| 137 | +] |
| 138 | + |
| 139 | +#slide("New Features")[ |
| 140 | + *Key additions since last update:* |
| 141 | + + Mean segment volume ($V = pi sum r_i^2$ for each segment) |
| 142 | + + Mean segment surface area ($A = 2 pi sum r_i$) |
| 143 | + + Added to `per_segment_radii` output and summary features |
| 144 | + + Used in downstream classification |
| 145 | + |
| 146 | + #v(0.3cm) |
| 147 | + *Feature Table Update:* |
| 148 | + + Re-mapped which features are feasible (many marked "nope") |
| 149 | + + only TODO left: per-node metrics |
| 150 | + |
| 151 | + SHOW TABLE |
| 152 | +] |
| 153 | + |
| 154 | +#slide("3D Thinning Performance Improvements")[ |
| 155 | + #columns(2, gutter: 1.5em)[ |
| 156 | + *Epoch-Based Removal Tracking* |
| 157 | + |
| 158 | + Problem: vanilla Lee94 re-checks simplicity (DFS on 26-neighbor graph) for *every* candidate before removal |
| 159 | + |
| 160 | + Insight: `_mark_removable_candidates` already verified all candidates before any removals in this batch. If none of a candidate's 26 neighbors have been removed yet, the old verdict is still valid - skip the DFS. |
| 161 | + |
| 162 | + |
| 163 | + #colbreak() |
| 164 | + Mechanism: |
| 165 | + + `epoch` increments per batch; stamped at each removed voxel |
| 166 | + + Scan 26 neighbors for stamp == current epoch (cheap, 26 int comparisons) |
| 167 | + + If no neighbor stamped → remove immediately |
| 168 | + + If neighbor stamped → re-run DFS to verify still simple |
| 169 | + + Never reset; monotonically increasing epoch makes old stamps invisible |
| 170 | + ] |
| 171 | +] |
| 172 | + |
| 173 | +#slide("Benchmark Results")[ |
| 174 | + #v(-0.3cm) |
| 175 | + #columns(2, gutter: 1.5em)[ |
| 176 | + |
| 177 | + *2D (HRF dataset, 45 images)* |
| 178 | + #table( |
| 179 | + columns: (auto, auto, auto), |
| 180 | + stroke: 0.5pt + rgb("#e5e7eb"), |
| 181 | + inset: 5pt, |
| 182 | + fill: (x, y) => if y == 0 { accent-light } else if calc.odd(y) { rgb("#f9fafb") }, |
| 183 | + [], [*Mean (s)*], [*vs vesskel*], |
| 184 | + [vesskel], [0.084], [-], |
| 185 | + [skimage Zhang], [0.157], [1.87x slower], |
| 186 | + [skimage Lee], [0.438], [5.21x slower], |
| 187 | + [VesselVio Lee], [0.796], [9.46x slower], |
| 188 | + ) |
| 189 | + |
| 190 | + #colbreak() |
| 191 | + *3D (vessap test volume)* |
| 192 | + #table( |
| 193 | + columns: (auto, auto, auto), |
| 194 | + stroke: 0.5pt + rgb("#e5e7eb"), |
| 195 | + inset: 5pt, |
| 196 | + fill: (x, y) => if y == 0 { accent-light } else if calc.odd(y) { rgb("#f9fafb") }, |
| 197 | + [], [*Mean (s)*], [*vs vesskel*], |
| 198 | + [vesskel], [0.730], [-], |
| 199 | + [skimage Lee], [1.024], [1.4x slower], |
| 200 | + [VesselVio Lee], [0.804], [1.10x slower], |
| 201 | + ) |
| 202 | + ] |
| 203 | + |
| 204 | + *Key Takeaways* |
| 205 | + #highlight[ |
| 206 | + + 2D is highly optimized via LUT-based simplicity check |
| 207 | + + 3D competitive with skimage and VesselVio |
| 208 | + + Pure Python + Numba - no Cython, no compiled extensions |
| 209 | + ] |
| 210 | +] |
| 211 | + |
| 212 | +#slide("CLI Improvements")[ |
| 213 | + *Shorter Subcommands* |
| 214 | + - `vesskel init config.json` instead of `vesskel config-init --out config.json` |
| 215 | + - `vesskel validate config.json` instead of `vesskel validate-config --config config.json` |
| 216 | + |
| 217 | + *Instant Worker Feedback* |
| 218 | + - `vesskel run (-j N)` now prints: `Spawning N worker processes...` |
| 219 | + |
| 220 | + *Default Config Changed* |
| 221 | + - `branches`, `branch_text`, `summary` now default `false` |
| 222 | +] |
| 223 | + |
| 224 | +#slide("Paper Draft")[ |
| 225 | + *First Table of Content draft started* |
| 226 | + |
| 227 | + #v(0.3cm) |
| 228 | + *also: Prediction Notebook Refactored* |
| 229 | + + `analysis/HRF_Prediction.ipynb` restructured |
| 230 | + + Pipeline runs within the notebook |
| 231 | + + Cleaner separation between analysis and reporting |
| 232 | + + No notable Performance improvements, even though cleanup ran and more features available #sym.arrow more investigation needed |
| 233 | +] |
| 234 | + |
| 235 | +#slide("Summary & Next Steps")[ |
| 236 | + #columns(2, gutter: 1.5em)[ |
| 237 | + *What I did:* |
| 238 | + + Preprocessing (closing, hole filling) |
| 239 | + + Per-segment volume & surface area |
| 240 | + + 3D thinning speedups (parallel scanning, epoch tracking) |
| 241 | + + Benchmark scripts (2D + 3D) |
| 242 | + + CLI polish (shorter cmds, worker feedback) |
| 243 | + + Table of Content draft |
| 244 | + + Feature table finalized |
| 245 | + + Prediction notebook refactored |
| 246 | + |
| 247 | + #colbreak() |
| 248 | + |
| 249 | + *Next steps:* |
| 250 | + + per_node features finalization |
| 251 | + + finalize Phenotype Prediction |
| 252 | + + nice documentation |
| 253 | + + Write the paper |
| 254 | + + (optional) more tests |
| 255 | + ] |
| 256 | +] |
0 commit comments