Authoring a Board

This guide is about making a printable calibration board with blob_board: setting up the environment, worked examples, output formats, printing and reproducing a board later. Every method form and every keyword (defaults, units, valid values) is listed once, in the blob_board reference. This guide shows them in use but does not repeat that list.

The authoring environment

Board authoring (figure layout and PDF/TIFF/PNG export) lives in the BlobBoardsAuthoringExt package extension. Julia loads it when CairoMakie is loaded. CairoMakie is a weak dependency of BlobBoards, so using BlobBoards (detection, patterns) never pays for Makie. The catch is that Julia never loads a package's own weak dependencies from that package's project, so julia --project=. in a checkout cannot load CairoMakie. Without the extension, blob_board is a stub with no methods:

julia> blob_board("A4", 300dpi)
ERROR: MethodError: no method matching blob_board(::String, ::DynamicQuantities.Quantity{…})
The function `blob_board` exists, but no method is defined for this combination of argument types.

The checkout ships a companion environment, authoring/, for this. It dev's the checkout and adds, as ordinary dependencies:

PackageActivatesFor
CairoMakieBlobBoardsAuthoringExt, BlobBoardsMakieExtblob_board, render_board, PDF/PNG export, the inspection plots
Libtiff_jllBlobBoardsTiffExtformat = "tiff" (the LZW TIFF writer)
PythonCallBlobBoardsPythonExtthe Python package (python/blobboards)
julia --project=authoring -e 'using Pkg; Pkg.instantiate()'   # once
julia --project=authoring                                     # authoring REPL
julia --project=authoring examples/generate/board.jl          # A4, 300 dpi, PDF

PDF output goes through Ghostscript, and PDF/X-4 (the default) needs Ghostscript ≥ 10.07. The repository's pixi environment provides it, so with pixi installed run Julia through it:

pixi install
pixi run julia --project=authoring

If you installed BlobBoards from a registry instead of a checkout, add the triggers to your own environment (Pkg.add(["BlobBoards", "CairoMakie"]), plus Libtiff_jll for TIFF). Patterns (blob_pattern) need only the core package.

Your first board

using BlobBoards      # re-exports the units: mm, inch, px, dpi, …using CairoMakie      # loads the board-authoring extensionbundle = blob_board("A4", 300dpi; seed = 0xBEEF, dir = "./output")

This writes two files named after the board's uid:

  • blob_board_<uid>.pdf: the print master, PDF/X-4.
  • blob_board_<uid>.json: the sidecar. It records the configuration and every blob, which is enough to reproduce or re-render the board and to detect it.

The returned BoardBundle holds the same data, so with dir left out nothing is written and you get the bundle in memory. seed makes the placement reproducible; leave it out and a random seed is drawn and recorded.

Page and board size

The page (canvas) can be a paper name, a width and height, or a Size2. The board can be smaller than the page; it is then centred, with white margins:

bundle = blob_board("Letter", 300dpi; dir = "./output")          # a paper name (BlobBoards.PRINT_SIZES)bundle = blob_board(200mm, 280mm, 300dpi; dir = "./output")      # width × heightbundle = blob_board(8inch, 10inch, 600dpi; dir = "./output")     # inches work toobundle = blob_board(Size2(width = 200mm, height = 280mm), 300dpi; dir = "./output")# a 180 mm square board centred on an A4 pagebundle = blob_board("A4", 300dpi;    board_size = Size2(width = 180mm, height = 180mm), dir = "./output")

The layout is three nested rectangles: the canvas (the page), the board (board_size, ≤ the canvas, default the whole page) and the pattern (the board minus a frame_width band on each side).

Blobs

blob_sizes = (min, max) are the smallest and largest printed disc diameters, as physical lengths converted to printer dots through the print density:

bundle = blob_board("A4", 300dpi;    blob_sizes = (4.0mm, nothing),   # (min, max); nothing = largest disc is 20% of the short side    seed = 0x1234,    dir = "./output")# printed discs from 2 mm to 12 mm acrossbundle = blob_board("A4", 300dpi; blob_sizes = (2mm, 12mm), dir = "./output")# the max as a fraction of the pattern's short sidebundle = blob_board("A4", 300dpi; blob_sizes = (4.0mm, 0.2short_side), dir = "./output")# light blobs on a black backgroundbundle = blob_board("A4", 300dpi; polarity = :light, dir = "./output")

The default min is 2·cutoff·1mm (4 mm at the default cutoff). The smallest disc must be at least 2·cutoff·2 printer dots across (σ ≥ 2 dots): 8 dots at the default cutoff. In millimetres:

Print densitySmallest disc diameter
150 dpi1.35 mm
300 dpi0.68 mm
600 dpi0.34 mm
1200 dpi0.17 mm

A smaller min is an ArgumentError that states this floor in mm at your density. The placement keywords (scales_per_octave, α, cutoff, max_trials) are described in the reference and the Patterns guide.

Frame and ruler

┌─────────────────────────────────────┐
│  QR  │    North Ruler    │  UID[0] │
├──────┼───────────────────┼─────────┤
│      │                   │         │
│ West │      Pattern      │  East   │
│Ruler │       Area        │  Ruler  │
│      │                   │         │
├──────┼───────────────────┼─────────┤
│UID[1]│    South Ruler    │  UID[2] │
└─────────────────────────────────────┘

The frame is a band of frame_width around the pattern (default 7.5% of the page's longer side). Its corners hold the QR code, which encodes the board's configuration, and the three uid characters. If the QR code does not fit its corner at this density, it is omitted with a warning. frame_width = 0mm gives a frameless board: no rulers, QR code or uid.

# ruler frame (the default), wider band, ticks every 10 mm / 5 mmbundle = blob_board("A4", 300dpi; frame_style = :ruler, frame_width = 15mm,    major_tick_spacing = 10mm, minor_tick_spacing = 5mm, dir = "./output")# checkerboard frame: the bundle also carries its saddle pointsbundle = blob_board("A4", 300dpi; frame_style = :saddle, frame_padding = 2mm, dir = "./output")# blank frame: corners (QR, uid) onlybundle = blob_board("A4", 300dpi; frame_style = :none, dir = "./output")

frame_padding adds white space between the frame's content (ticks, saddle squares, QR code, uid glyphs) and the band's edges.

Ruler widths

major_tick_width and minor_tick_width are each tick's full drawn length across the band, centred in it. By default the major ticks fill the padded band (major_tick_width = frame_width - 2frame_padding) and the minor ticks are half as long. Widths must satisfy 0 < minor_tick_width ≤ major_tick_width ≤ frame_width - 2frame_padding; anything else is an ArgumentError.

# 15 mm band; major ticks 8 mm long, minor ticks 4 mm, centred in the bandbundle = blob_board("A4", 300dpi; frame_width = 15mm,    major_tick_width = 8mm, minor_tick_width = 4mm, dir = "./output")

Output formats

format is exactly "pdf", "tiff" (or "tif") or "png". format, pdf_standard and icc_profile describe the file written to dir, so they are checked before placement. Each of these is an ArgumentError:

  • any of them without a dir
  • pdf_standard with a raster format
  • icc_profile with a PNG

PDF (default)

bundle = blob_board("A4", 300dpi; dir = "./output")                       # PDF/X-4bundle = blob_board("A4", 300dpi; pdf_standard = :x1a, dir = "./output")  # PDF 1.3, older RIPsbundle = blob_board("A4", 300dpi; pdf_standard = :none, dir = "./output") # plain PDF, no OutputIntent
  • PDF/X-4 by default (pdf_standard = :x4, what most print shops ask for); :x3 or :x1a for older RIPs, :none for a plain PDF
  • an OutputIntent declaring the print condition (linear-reflectance grey by default; see ICC profiles below) and a TrimBox at the board area
  • the pattern embedded pixel for pixel at the print density: lossless Flate, no downsampling, and no interpolation flag (a print master is not to be smoothed)
  • vector rulers and text

An older Ghostscript is refused with an error rather than allowed to write a non-conformant file.

TIFF

For print shops and photo labs that take raster files (needs using Libtiff_jll, which the authoring/ environment provides):

using Libtiff_jllbundle = blob_board("A4", 1200dpi; format = "tiff", dir = "./output")
  • the whole sheet at exactly the print density, 8-bit grey, with the pattern pixels identical to the PDF's
  • the density in the resolution tags, so the print software sizes it exactly
  • the ICC profile embedded (linear reflectance by default)
  • LZW compression with the horizontal predictor

PNG

For digital use (rendering, training):

bundle = blob_board("A4", 300dpi; format = "png", dir = "./output")

The PNG is single-channel grey with linear gamma (file_gamma = 1.0).

ICC profiles

A PDF/X file's OutputIntent says how its grey values are to be printed. The default, :linear, declares them linear reflectance: a value of 0.5 reflects half the light of bare paper, so a Gaussian in the file is a Gaussian in the light the camera sees. A colour-managed RIP (print shops) honours it; an office printer ignores it and applies its own tone curve.

bundle = blob_board("A4", 300dpi; icc_profile = :linear, dir = "./output")       # the defaultbundle = blob_board("A4", 300dpi; icc_profile = "/path/to/press_gray.icc", dir = "./output")

Identity Label

Every new board prints its identity in one 6 pt line under the board, in the sheet margin (outside the TrimBox, clear of the printer's unprintable border):

blob_board_e98_fdac4 · sha256 e98fdac4… (64 hex) · BlobBoards 0.36.0 · 200 × 200 mm @ 1200 dpi

so every printed sheet — frameless boards included — can be traced to its JSON. It is a rendering option, not part of the board's identity: id_label = false turns it off without changing the UID. A board that fills its sheet has no room for it, and the label is skipped (with a warning for a frameless board, which would otherwise carry no printed identity). The JSON's render block records whether it was drawn: blob_board(json) and the gallery's regeneration rebuild the sheet as recorded, and a board with no render block (written before 0.36.2) rebuilds without one. render_board defaults to false.

Printing

A board is a measuring instrument, so it must be printed at exactly 100%.

  • In the print dialog choose "Actual size" / 100%. Turn off "Fit", "Shrink oversized pages" and "Scale to printable area". An A4 page is larger than any printer's printable area, so a dialog left on "Fit" shrinks the whole board by about 3%. PDF/X-4 output asks Acrobat/Reader for actual size (/PrintScaling /None); other viewers may ignore it.
  • If your printer cannot print to the page edge and keeps scaling, make the board smaller than the page: board_size leaves a white margin, so the page prints at 100% without anything being clipped.
  • Print grey / black ink only, at the highest quality, with image enhancement off.
  • Check every print. Measure the rulers: the major ticks are major_tick_spacing apart, so at the default spacing ten intervals must span exactly 100.0 mm. On a frameless board, measure the pattern edge to edge. A "fit" print comes out about 3% short.
  • Print shops print PDF/X at 100% by default; the TrimBox marks the board.

Reproducing a board

The JSON sidecar is the board's definition. There are three ways to use it.

# 1. Load the bundle (and its image): the input to detection and coordinatesbundle, image = BlobBoards.load("./output/blob_board_<uid>.json")# 2. Re-render the stored board (its recorded blobs, frame, QR and uid);#    no placement is re-run, so it matches the printed board exactlyBlobBoards.render_board(bundle; dir = "./rerender", format = "png")# 3. Re-generate from the configuration (placement is re-run from the seed)bundle = blob_board(read("./output/blob_board_<uid>.json", String); dir = "./output2")

Reading a board back

bundle, image = BlobBoards.load("./output/blob_board_<uid>.json")blobs(bundle, :pattern)              # px, pattern frame (COLMAP convention)blobs(bundle, :board; units = mm)    # mm, board frame (includes the frame band)blobs(bundle, :world)                # centred, y-up, unitless mm: the z = 0 calibration planeboard_config(bundle).print_density   # the BoardConfigpattern_config(bundle).seed          # the PatternConfig

See Coordinate Systems for the reference frames and transforms.

Full keyword reference

All method forms and keywords, with defaults and valid values, are in the blob_board docstring. Paper names are the keys of BlobBoards.PRINT_SIZES.