Skip to content

v0.4.1 — the sulcus SVG export pycortex can actually read

Latest

Choose a tag to compare

@jackgallant jackgallant released this 09 Jul 20:52

Anyone using v0.4.0's Export sulci (SVG) should re-export. The file it produced could not be
read by pycortex, or by any XML parser. This release fixes that. ROI drawing and export are
unaffected; nothing about rois.json has changed.

The sulcus export was broken three ways

A code review checked the output against cortex/svgoverlay.py for the first time.

  1. It was not well-formed XML. The fragment used the inkscape: prefix with no namespace
    declaration and no <svg> root, so ET.parse failed with unbound prefix. Nothing — Python,
    lxml, a browser, Inkscape — could open the file.
  2. Its <text data-ptidx> labels crashed db.get_overlay(). Labels.__init__ reads
    float(text.get('x')) off every <text> in a labels layer. Ours carried no x/y, because a
    vertex index cannot supply one. TypeError, before anything rendered.
  3. data-ptidx is pycortex's output, not its input. SVGOverlay.set_coords computes it by
    kd-tree from each label's x/y. A real overlays.svg holds zero <text> elements —
    Shape.get_labelpos() derives one label per path, so a two-hemisphere sulcus is labelled twice
    for free.

Ten unit tests passed against all of this, because every one of them matched the output string
rather than parsing it.

What sulci.svg looks like now

A standalone SVG document: both namespaces declared, a real <svg> root, and the overlay's
width/height/viewBox so the path coordinates have a frame. No labels — but the labels layer
is still emitted, empty, because _find_layer(layer, "labels") raises ValueError without it.
The file carries its own merge instructions in an XML comment.

Installing it into a subject — this changed

Copy the <g inkscape:label="…"> groups out of #sulci_shapes into the subject's existing
#sulci_shapes group. Do not paste the whole <g id="sulci"> layer: SVGOverlay keys layers by
inkscape:label, so a second one named sulci silently replaces the subject's own, and every
pre-existing sulcus disappears. v0.4.0's docs said "paste or merge", which invited exactly that.

Also fixed, from the same review

  • All five bezier edit ops now share one out-of-range contract (return an unchanged copy). Two threw
    TypeError, one silently no-op'd, and setAnchorSmooth on a closed curve corrupted the
    smooth[] array — the exact desync the guard on moveAnchor/moveHandle was added to prevent.
  • loadJSON deep-copies the imported bezier and outline, as its comment already claimed.
  • A shape drawn before the viewer's SVG overlay finishes loading is now retried, instead of sitting
    in the panel, listed and counted, never drawn.
  • The edit toggle refuses a shape with no editable curve, rather than half-entering edit mode.
  • Timers and the adapter's host listener are released on destroy() / re-attach.
  • The lasso rejects a stray click, as the trace already did.
  • Export byte counts are real bytes, not UTF-16 code units.

Testing

test/test_sulci_svg.py (new, in CI) generates the writer's real output with node and parses it
with ElementTree, using svgoverlay.py's own namespaces and findall queries. It needs neither
cortex nor a subject. Every new assertion was mutation-checked: break the fix, confirm the test
fails. Suite: 152 → 161 JS tests, plus six Python suites.

Still unverified, and stated plainly in TESTING.md: svgoverlay.py itself has never run on this
output. That needs a subject and an importable cortex.

Install

Download roidraw.bundle.js and add two tags to your viewer:

<script src="roidraw.bundle.js"></script>
<script>window.ROIDraw.autoAttach();</script>