Skip to content

FigureLab v3.17.0

Latest

Choose a tag to compare

@mbaffour mbaffour released this 27 Sep 16:53
· 3 commits to master since this release

Open figure_lab.html in a browser — no installation, no server, no network.

Built by making real figures end to end and fixing every place the work stalled, then reviewing those fixes adversarially and fixing what the review found — plus six features for getting a figure through submission: a Methods paragraph, per-panel print resolution, LZW TIFF, merging ticked channel files, a split that keeps the field, and repeating an annotation across panels.

  • New: every panel shows the resolution it actually prints at. The DPI check was about the whole canvas. A 512 px confocal field or a tight inset stretched across a column exported at '300 DPI' while the panel itself was 90 ppi of interpolated pixels, and the report said ✓. Each image-list row now reads '412 ppi' or '180 ppi · enlarged 1.7×'. The compliance check (with a Select panel fix) and the Save dialog name any panel below 300 ppi. The FAQ no longer claims that a higher DPI adds detail to a picture that has none.

  • New: ✎ Write Methods paragraph. Journals want the image processing described in the Methods: the software and its version, whether adjustments were applied uniformly, any non-linear gamma, pseudocolour, resampling, projections and where the scale came from. FigureLab knew every one of these and told you to type them yourself. It now writes the paragraph from the figure's own state, using the same test the caption uses so the two cannot disagree, and puts it in the submission package as -methods.txt. Splice markers and projections now record what they are, so the paragraph can say so.

  • New: LZW-compressed TIFF. TIFF export was uncompressed only, and PLOS asks for LZW-compressed TIFF. The Save dialog and the Export panel now offer Compress (LZW, lossless), the PLOS preset switches it on, and the submission package follows the same setting. The pixels come back bit for bit, checked with FigureLab's own reader and independently with Pillow/libtiff (tests/validate_tiff.py). How much smaller the file gets depends on the picture: a figure with white background and flat panels went from 2.4 MB to 0.8 MB, while pure noise grows by about a third. The default uncompressed TIFF is unchanged byte for byte.

  • Splitting a merge keeps the field you framed. The single-channel panels were built from scratch and inherited only the calibration. A merge you had cropped and turned therefore sat beside DAPI and GFP panels showing the whole uncropped, unturned frame: a row comparing two different fields while looking like one. The parts now carry the merge's crop, tilt, crop shape, rotation, flips, scale bar, intensity bar and acquisition note. An inset linked to the merged panel stays linked. The split letters the panels you can see first and hidden ones after them, and a figure with a blank cell no longer throws when you split.

  • New: combine ticked panels into one merge. Separately exported channels (C1-DAPI.tif, C2-GFP.tif…) could only be merged by re-picking each file from disk inside one panel's drawer, and in Chrome that route did nothing at all with a TIFF: no channel, no message. Tick the panels and press ⊕ Merge channels. The first becomes the base and keeps its letter, the others become its channels coloured from their filenames, and the originals are hidden rather than deleted and lettered after the visible panels. One undo reverses the whole merge. + Add channel now opens TIFFs too, says so when it cannot open a file, and can be undone.

  • New: repeat an arrow, box or text on the other panels. Pointing at the same structure in the DAPI, GFP and merge panels meant drawing the arrow three times by eye, never quite in the same place, which makes it look as though the channels show different cells. Select a 📌 panel annotation and pick ⧉ Repeat on… this row / this match group / every panel from the floating toolbar or the command palette. Where two panels show the same picture (same source size, crop, turn and flip), the copy lands on the same pixels, even when rows hug their panels and the cells differ in height. Where they don't, the copy keeps the same place in the panel, and the toast says so. One undo takes it all back, and pressing it twice does not stack copies. Deleting a panel annotation is now undoable too; it never was.

  • A share link keeps per-gutter spacing. The link carried the per-gutter overrides but never read them back. The recipient got uniform gutters and a figure narrower than the one shared. Opening the link now restores each gutter and the per-gutter spacing switch, exactly as loading a saved session does.

  • The provenance hash knows when rows hug their panels. The SHA-256 and the PNG's FigureLab-Settings recorded the panel height but not Rows hug their panels. Two figures with different row heights therefore carried the same hash, which is exactly what the hash exists to tell apart. Both now record the switch and the height of every row as drawn, and the hash covers per-gutter spacing too.

  • Undo no longer brings back a loaded figure's extra headings. Loading a figure with four column headings over one with two, then undoing, left the third and fourth headings in the store. They reappeared as soon as the grid grew back and were saved with the figure. Undo now restores the heading list exactly as it was.

  • A second pass over this release's own fixes. A six-way adversarial review of the work above, with every finding checked three ways, confirmed thirteen defects in it, and each one is now fixed:

    • Rows hug their panels is saved with the figure: sessions, autosave, share links and templates carry it. A figure saved before the option existed reloads with uniform rows, because that is how it was drawn.
    • Typed headings stay with their own figure. Loading a session or starting a new figure used to let the previous figure's headings reappear as soon as the grid grew.
    • A hugged row no longer snaps back to full height because it has an empty cell, so an odd number of blots still stacks tight. Row heights round up, so a width-limited blot keeps its full width and its lanes stay in register.
    • The status bar, the deep figure audit, the compliance size row and the generated R/Python scripts report the row heights actually drawn, not the panel-height field. A hugged row under 80 px is a warning, not a failure: it is short because its panel is.
    • Panel-mode annotations get what figure-level ones already had: the toolbar steps out of the way mid-drag, a release off the canvas ends the drag, and a pinned channel key can be grabbed anywhere on it and exports to SVG as text.
    • The caption, CSV, compliance and manifest skip a panel pushed out of the grid by a spanning neighbour. Hidden panels no longer fail the calibration, adjustment or inset lines.
    • An SVG that fails to rasterise is reported and no longer stalls the rest of the drop. The freeform crop editor can draw a box after a straighten, the half of the straighten-then-crop fix that only covered the grid. Lane numbers sit above the blot instead of across its top edge, and a click that moved nothing records no undo step.
  • The shipped package describes the panels the figure actually draws. The caption, the metadata CSV, the journal-compliance check and the package manifest all enumerated images.slice(0, cols × rows) — raw array order. A hidden panel still occupied one of those places, so the ZIP described panels the reader cannot see and silently dropped a visible one off the end: a ten-panel figure shipped a caption for twelve, a CSV whose rows ran 1–6 then 8–12, and a compliance line reading "11/11 panels labelled". All four now use the panels in reading order that the figure draws. PANEL_HISTORY.txt still records a hidden panel — you did process it — but marks it [hidden from the figure].

  • Every plane of an OME-TIFF is calibrated, not just the first. OME-TIFF and ImageJ write their description into page 0 only, and the calibration reader looked at the current page's. Frames 2…N of a real Bio-Formats or tifffile stack — which carry PhysicalSizeX and no resolution tags — therefore arrived uncalibrated with their scale bar switched on and nothing to draw it from. The description now falls back to page 0's, exactly as the acquisition-time reader already did. Per-plane DateTime is untouched and stays per plane.

  • In panel mode the channel key sticks to its panel. 📌 Panel mode binds every annotation to the panel under the cursor — except the ▤ Key, which went on writing figure-level coordinates. Placing it looked identical; changing the H gap then left the key stranded on the background while its panel moved away. The Key tool's own tooltip promises it is "kept in step with the panels as they change".

  • New: rows hug their panels. Every cell was the same height, so a wide short panel — a blot, a gel, a kymograph — floated in a tall cell with dead space above and below it, and closing the gaps did not help because the space was inside the cell. Stacking a blot over its loading control left a gulf between them that no setting could remove: ⤢ Fit panels to images can only pick one shape for the whole grid, and says so. Tick Rows hug their panels (or ⌘K → Rows hug their panels) and each row shrinks to the tallest panel in it. Rows never grow past the panel height you set, panel width does not move, and the headings, group bands, gutter handles and every export follow the new heights.

  • Lane numbers, MW ladders and splice markers land on the blot. All three store their position as a fraction of the whole figure. In a one-panel figure that is the same thing, which is why it went unnoticed — but in a 2 × 3 figure ↕ MW ladder laid its weights down the left edge of the entire sheet, nowhere near the blot, and ⎸ Add splice marker drew a line straight down the middle of the figure. They now measure from the selected panel (or the only one), place lane numbers above it and weights beside it when the margin has room, and name the panel they landed on.

  • Typed row and column headings stop disappearing. The headings lived only in the text fields that showed them, and those fields are destroyed and rebuilt whenever the grid resizes or the "Column labels" box is ticked. Adding a column, hiding the headings, undoing an unrelated change, or saving a figure with the headings hidden all threw the text away — and the rebuilt field read Col 3, which looks like a heading you wrote and renders into the export as one. They now live in the figure's state, so they survive all of that and round-trip through saves, share links and undo.

  • The on-canvas heading toggle says what it does. An 18 px chip in the top-left corner wore a ×, had no tooltip and no hover state; one click on it hid every row and column heading. It now carries the same ⊞ mark as the sidebar's ⊞ Labels button, lights up under the pointer, and reads "hide the row & column headings".

  • Greyscale, Invert and Reset are undoable. All three are buttons, and the capture-before-edit handler that makes the sliders undoable only watches inputs and selects — so a click on Reset, which clears brightness, contrast, gamma, LUT and both level points at once, could not be taken back. Crop shape joins them.

  • One press of undo undoes a typed panel tilt. The Tilt field recorded two undo steps — one before the edit from the generic handler, one after it from its own — so the first press restored the tilt you had just typed and appeared to do nothing.

  • Group bands are in the undo history. They were left out of the state snapshot entirely, so the undo step that setFigGroups and the band ✕ button already recorded restored everything except the bands. Re-running a band generator also blanked the names you had typed; names now carry across when the band still covers the same rows.

  • Panels arrive in the order you picked the files. Every import path is asynchronous, and they were all fired at once, so panels were committed in decode-completion order — a small PNG overtook a large TIFF, and a twelve-file drop landed shuffled. Panel letters are handed out at commit time, so B could end up sitting left of A. Files now open one at a time.

  • Relabelling with a panel hidden no longer makes two panels called B. Hidden panels kept the letter they had while the visible run re-used it; they are now lettered after the visible ones, so the reader still sees A, B, C with no gaps and nothing is lettered twice.

  • Fixed (blocker): after straightening a panel you could never crop it. A panel opens the crop editor with the whole frame selected but no box drawn; one press of 📐 Level, ✨ Auto, ↺ ↻ or the Tilt field counted that full frame as a box, so every later drag was treated as moving it — clamped to no movement — instead of drawing one. Straightening a specimen and then cropping it is the entire point of the tilt, and it could not be done.

  • Fixed (blocker): an annotation endpoint could not be dragged under its own toolbar. The floating toolbar sits 54 px above the topmost endpoint and over the canvas, so dragging an endpoint upward moved it beneath the toolbar, which then swallowed the pointer and froze the drag — with no keyboard route to finish it, since grid annotations have no arrow-key nudge. The toolbar now steps out of the pointer's way for the duration of a drag.

  • Fixed: the crop editor showed the previous panel's tilt. Opening it on a panel that had never been cropped displayed the last panel's angle, pixel box, and the amber "resampled — panel pixels are interpolated" warning, while the canvas simultaneously said "Draw a region to continue". An interpolation warning that isn't true is the wrong kind of wrong on a figure-integrity tool.

  • Splitting a merge now produces the row it promises. splitChannelRow is named for a row, and its button offers "a row of separate single-channel panels plus a merge" — but it never touched the grid, so a 2×2 figure stayed 2×2, and its toast recommended Auto-arrange, which lays 3 panels out as 2×2 and so could not produce that row either. When the split parts are the whole figure it now sets one row; when other panels are present it leaves their layout alone and says so instead of giving advice that does not work.

  • The merge control is reachable. Both halves of making a two-colour merge sit at the bottom of a 1400 px per-panel drawer, past fifty other controls. ⌘K → Merge channels now opens that panel's drawer and scrolls + Add channel into view, and that button — the only one in the drawer with no tooltip — explains what it does.