A purpose search groups candidates by whether they fit the current input, need preprocessing, or belong elsewhere. You can try a candidate once without adding a stage. Failures in the Problems pane show the cause, location, and concrete next actions for reselecting input, comparing values, opening the log, editing a knob, or finding a suggested stage.
Heavy work runs without freezing the window. The bottom area shows the active stage and progress; a stop request is acknowledged at the next stage boundary. If settings change during a run, the displayed generation remains identified and you can either view the completed older result or run the current settings.
日本語 · English · 简体中文 · 繁體中文 · 한국어 · Deutsch · हिन्दी
Fullseye Studio is an HDevelop-style visual pipeline workbench. You search for operators and
line them up, turn two knobs with sliders, run one stage at a time while looking at the
intermediate results with zoom/pan, and export the assembled pipeline as an --ops string /
Python / JSON. Underneath it is a thin GUI front end to the fullseye API; the pipeline logic
(PipelineModel), the Inspector (inspect_result), and the sample set (recipes) are all
Qt-independent and unit-tested.
This guide enumerates the features by checking studio.py (build_window) against the actual
code. The UX/design intent is in STUDIO_UX.md, and the background of the v14
perception panel is in V14.md / PERCEPTION.md.
You need the GUI extras (PySide6) (pip install -e ".[gui]").
py -3.11 studio.py # directly from the repository root
fullseye-studio # if you have run pip install -e ., via the console script
On launch a 1320×860 main window opens (title: Fullseye Studio). If assets/fullseye.ico is
present it is used as the window/taskbar icon. Initially a synthetic demo image is loaded
(demo_image, a 256×256 that includes edges, blobs and a gradient).
At the top there is a menu bar (File / Edit / View / Run / Help) and a brand toolbar, and
at the bottom a status bar (coordinates + pixel value on hover, and the transient messages of
flash()). The centre is three panels in a left/right split.
| Panel | Section (QGroupBox) | Role |
|---|---|---|
| Left | SAMPLE PIPELINES / OPERATORS | Loading samples and the operator browser |
| Centre | PIPELINE / SELECTED STAGE · KNOBS / EXPORT & I/O | Building the pipeline, adjusting knobs, exporting |
| Right | IMAGE / DISPLAY & PERCEPTION (v14) / ANALYSIS | Result display, colour map/perception, histogram/Inspector |
The initial split widths are 340 / 360 / 640 px, and the right panel stretches.
Choosing one of the 25 ready-made recipes (recipes.py) from the dropdown replaces the
pipeline with that recipe. Examples: “Edge — Sobel + Otsu”, “Denoise — bilateral + unsharp”,
“Segment — blob / coin”, “Count — blobs”, “Texture — Gabor”, and so on. Handy as a starting point
to run something first and then look at what it does.
name [in_sort → out_sort]. Double-click to insert. On hover a
tooltip shows “name / HALCON alias / category / sort conversion / description of the knobs a,b”.The insertion position is “right after the selected stage”. If no stage is selected, it is added at the end.
Each row is of the form N. op (a=…, b=…) -> summary of the result, with the state of the result
of running up to that stage (image/region/feature, etc.) shown on the right.
Selecting a stage draws the intermediate result up to that stage in the IMAGE panel on the right, and the ANALYSIS below (histogram / Inspector) syncs too. This is what amounts to a “step-through debugger”.
Shows the details of the selected stage (op_detail: name, in → out sort, category, HALCON
alias) and adjusts it with two sliders a / b (0.00–1.00). Moving a value recomputes the result
immediately. When no stage is selected the sliders are disabled (a design that doesn’t leave a
meaningless knob in a live state).
The meaning of the knobs differs per operator (radius / threshold / σ / direction, etc.). What you are adjusting can be confirmed in the stage’s detail label and the tooltip.
--ops "…" string and as a standalone Python function.{"fullseye_pipeline": 1, "stages": [...]}). This JSON is the input to FullseyeEngine.load /
imgevolve.py run.x, y, value (RGB for colour).gray / shaded relief / height
(color) / each colour map (jet, viridis, turbo, magma, plasma, inferno …).QtDataVisualization is present / best-effort). For inspecting a height/depth map.optical flow — visualise the dense optical flow between the two frames as hue.motion overlay — overlay the moving regions on the original image.stereo depth — estimate depth from stereo disparity and colour it.stereo terrain — stereo → point cloud → terrain height map, coloured.When frame B is missing or the size doesn’t match, it shows an error in the status bar and safely aborts.
detect.feature_table).Ctrl+P opens a fuzzy-search dialog that lets you run any action or any operator by name. It
ranks prefix match > word-start match > substring match (palette_filter, unit-tested
Qt-independently). Actions (like ▸ Open image) come first, then all operators (like
op: gaussian), and Enter runs it. You can go all the way to inserting an operator with the
keyboard alone.
Inside the app, Help ▸ Keyboard shortcuts (F1) shows the full list in a table
(self-documenting). The main ones (from the act_* definitions in studio.py):
| Action | Shortcut | Action | Shortcut |
|---|---|---|---|
| Open image | Ctrl+O |
Remove stage | Del |
| Synthetic demo | Ctrl+D |
Move stage up / down | Ctrl+↑ / Ctrl+↓ |
| Save result | Ctrl+S |
Clear pipeline | Ctrl+Shift+Backspace |
| Open pipeline | Ctrl+Shift+O |
Zoom in / out | Ctrl+= / Ctrl+- |
| Save pipeline | Ctrl+Shift+S |
Fit / Actual size (1:1) | Ctrl+0 / Ctrl+1 |
| Export | Ctrl+E |
3D surface | Ctrl+3 |
| Quit | Ctrl+Q |
Reset to start | Home |
| Command palette | Ctrl+P |
Step forward | Ctrl+→ |
| Keyboard shortcuts | F1 |
Run all | Ctrl+Enter |
| Image Viewer | Ctrl+Shift+I |
Run a ledger op | Ctrl+Shift+L |
| Pipeline as code → editor | Ctrl+Shift+E |
Each action is invoked by the same handler whether from the menu, the toolbar or a button (one action, several entry points).
dev_* display-control directivesAs in HDevelop, you can control the display behaviour from the program. Writing a dev_* line
in the Program window’s script is interpreted not as an image stage but as a display directive,
applied on Apply (a complete grasp of all 43 dev_* is in docs/HDEVELOP_DEV_OPS.md).
| Directive | Effect | Corresponding UI |
|---|---|---|
dev_update_window ('off'|'on') |
Toggle auto-update of the graphics window | View ▸ Display updates ▸ Graphics window |
dev_update_var ('off'|'on') |
Toggle auto-update of the variable window | ditto Variable window |
dev_update_pc ('off'|'on') |
Toggle updating the execution cursor | ditto Program counter |
dev_update_time ('off'|'on') |
Toggle the per-line processing-time display | ditto Operator timings |
dev_update_off () / dev_update_on () |
Turn all of the above off / on at once | the toolbar Auto-update toggle |
dev_set_part (Row1, Col1, Row2, Col2) |
Set the display range (zoom/pan); a negative value = whole | use together with mouse wheel / Fit |
dev_set_lut ('gray'|'jet'|'viridis'…) |
Switch the colour map (LUT) | View ▸ Display mode |
dev_clear_window () |
Clear the current window | — |
set_system ('thread_num', N) |
Set the number of OpenCV worker threads (0 = default/all) | Tools ▸ System settings |
set_system ('operator_timeout', ms) |
A soft operator timeout (warns about a slow stage in Run status) | ditto |
dev_set_draw ('fill'|'margin') |
Toggle a region overlay between fill and outline (margin) | View ▸ Display mode = region overlay |
dev_set_color ('red'|'green'…) |
The colour of the region overlay | ditto |
dev_set_line_width (N) |
The outline width of the margin (px) | ditto |
dev_disp_text ('label', Row, Col) |
A text annotation over the result (cleared on the next draw / dev_clear_window) |
— |
dev_open_window (Row, Col, W, H) |
Open, place and make current a graphics window (re-Apply re-places the same window = doesn’t multiply) | Ctrl+G / Window ▸ Graphics |
dev_set_window (Handle) |
Switch the current window by handle | click the window |
dev_set_window_extents (Row, Col, W, H) |
Position/size of the current window (-1 = keep as is) | drag the window |
dev_close_window () |
Close the current window (the resident main window is protected) | the window’s × |
set_system ('max_graphics_windows', N) |
The cap on the number of windows (default 256, fail-closed on every path) | Tools ▸ System settings ▸ Windows |
Use: putting dev_update_off () at the top lets you do heavy processing or many edits at no
drawing cost, then bring the display to the current state all at once with dev_update_on ()
(the same performance technique as in HDevelop). While updates are off, updates off: … appears at
the right of the status bar, so the frozen state never “looks broken”. The toolbar Auto-update
toggle does the same switch.
Note (honest): unlike a pipeline stage, dev_* does not obey if/for and is applied
unconditionally (it fires even placed inside a branch). Write them at the top level. An
unsupported dev_* is an error.
Try it out: File ▸ dev_* visualization demo loads and applies an HDevelop program that
actually uses the dev_* above on the coins image (segment → show the regions with a cyan outline
studio_assets/sample_images/manifest.json. synthetic = own work / coins, camera, etc. =
skimage.data’s BSD/public-domain. Regenerated with tools/gen_sample_images.py). Five of them are
factory-inspection (FA) scenes computed, not photographed, from lighting and reflection physics
(illumdesign) and the PoCs’ imaging models: a scratch on hairline metal, a dent and a foreign fibre on
resin, an engraved lot code, a back-lit plate with holes, and weld porosity in an X-ray. Each ships its
defect mask (<name>.truth.png) and a description in the manifest, is paired with one “FA — …” recipe
that a test shows finds the defects, and is regenerated with tools/gen_fa_samples.py.The UI language is switched at Tools ▸ Language / 言語 / 语言 (it is remembered). The
translations are a table centralised in studio_assets/i18n.json, and can be added without a
code change:
languages — the list of languages (add one and it lines up in the menu automatically; English
is always the base)tooltips — tooltip translations (the English original is the key)strings — the label translations of menus, buttons and dialogs (the English original is the
key; added 2026-08-30. Ships with 40+ Japanese items; an untranslated string stays English =
graceful fallback)guide — the quick guide text (Shift+F2)Op help appears per language if op_help/<name>.<lang>.html exists. Honest disclosure: the status
wording that changes during a run (running… / PASS, etc.) and the body of the op notes are for
now not translated (making the op notes English is a future task, paired with making the docstrings
bilingual).
Studio goes beyond the stage where “you can only call code from a pipeline” and can also be used as a Python development environment.
import fullseye works as is; an unsaved buffer runs from a scratch copy and doesn’t
force a Save). From Samples ▾ you can open every worked example in a new tab (it opens without a
path so you can’t accidentally overwrite a shipped sample). The interpreter to run with can be
changed in System settings ▸ Editor.v.mean() /
np.percentile(v, 99) / (v > 0.5).sum(), etc.; v = the selected variable, np = numpy, img
= the input) and it is re-evaluated automatically on every selection change / pipeline change.
A failed expression shows a ⚠ on its row (the panel doesn’t crash). A variable’s right-click ▸
Inspect in popup… immediately shows a per-type inspection + percentile + value preview. Known
limitation (honest): a watch expression is evaluated synchronously on the GUI thread, so a very
heavy expression (a full sweep of a huge array, etc.) makes the UI wait during it. Make the
expression lighter, or run heavy aggregation in the Python Editor.Drop files or folders onto the main window and each opens in the window that fits it (several files at once open several windows). The full list is under Help ▸ Files you can open (drag & drop)…; that table is built from the extension constants Studio actually uses, so it can be newer than this section.
| What you drop | Opens in | Notes |
|---|---|---|
| Images (png / jpg / tif / webp / pgm / pfm / jp2 …), folders | Image viewer | List, zoom, pixel values, histogram, “Use as pipeline input”. A folder opens the images directly inside it |
.py |
Python editor (one tab per file) | |
.json |
Pipeline | |
| Point clouds and meshes (PLY / STL / PCD / OBJ / OFF / XYZ) | 3-D viewer | ASCII and binary both (PLY in little- and big-endian, PCD also binary_compressed) |
3DGS (.ply / .splat) |
3-D viewer | Colour from the SH DC term; splats with opacity < 0.05 hidden |
| Medical volumes (NIfTI / NRRD / MHA / DICOM) | 3-D viewer | Needs SimpleITK or similar |
glTF (.glb / .gltf), LiDAR (.las / .laz) |
3-D viewer | Needs pygltflib / laspy |
| Robots (MJCF / URDF) | 3-D viewer | .xml only when its root is <mujoco> or <robot>. Needs mujoco |
Motion capture (.bvh), neuron morphology (.swc) |
3-D viewer | BVH joint paths coloured by time |
| Videos, animated GIF, HDF5 | Video cube | |
| Event-camera (x, y, t, p) (txt / csv / npy / npz) | Video cube | Recognised from the content, turned into polarity frames |
.npy |
By shape | 2-D → input image, (N, 3 / 6) → point cloud, 3-D → volume |
| Markdown, SVG | Document viewer | |
| Audio (wav / mp3 / flac / ogg …) | Audio window | Waveform, spectrogram, playback. Non-WAV needs soundfile |
A robot model + a qpos .npy (T, nq) together |
Robot player | Slider and play button |
A window that runs typed-ledger ops (PIV, math, driving, 3-D … about 1,700 of them) with input fields that match each argument’s type. Type a name or words in the search box on the left, pick an op, and its argument form appears on the right.
ode_vector_field_grid, then set piv_quiver’s input to
“last result” for an arrow plot). An input of the wrong type (the current image into a flow2d slot) is refused with
the reason instead of running.None means None, lists are 1, 2, 3. Matrix or dict samples are kept as the
token <sample>. A value that cannot be read is never silently replaced by the default — the window says
which argument and why.result = … lines).fs.ledger. or fs. offers names (Ctrl+Space at any time).
Picking a ledger op inserts the call with its data and required arguments — e.g. dem_slope(dem, cell_size=) — with the
cursor in the first blank. Right-click an op name, or press Ctrl+Shift+L on it, to open that op’s window.ledger: name opens the window for any ledger op in three keystrokes (Ctrl+P → name → Enter).Some ops do not run on their sample (a limit of how each op’s sample is built: on 2026-10-03, 27 of 60 sampled ops ran as is). That the window adds no failures of its own is checked by a gate: it must agree with passing the sample to the op directly.
For a typed table input, choose CSV file… in the existing ledger runner. Each role is selected from actual headers,
never free text; gauge R&R shows part / part ID, operator / operator, and value / measured value. The check reports
rows, distinct keys, missing values and repeats. Unequal repeats are refused before execution with the groups named; no
unsupported design is repaired. Mappings are remembered by operation and header signature (sorted names plus count), so
only the same format reuses them. The CSV is never modified or renamed.
Results that are not pixels are drawn according to their shape (the right panel and the window above share this).
| Result | Shown as |
|---|---|
flow2d (2, h, w) |
arrows (longest = 0.9 × spacing) over a colour wheel where hue = direction, brightness = speed |
| 1-D numbers | a line (MATLAB plot(y)) |
| table (dict) | its numeric columns of equal length as lines; the x axis is a t / x / freq … column if there is one |
(h, w, 4) / (h, w, 1) / complex |
RGB over white / grey / magnitude |z| |
| empty arrays, 4-D and more | shape and range as text (the first value is not shown as a “scalar”) |
⟳ calc marks a stage computed this time and ↺ cache one served from the cache. Changing the image, reordering or deleting stages drops everything from the affected stage onward. The cache keeps one latest result per stage, so dragging a knob repeatedly does not grow it.ran) and “is the result sound” (valid) are kept apart. When a result contains NaN / Inf, is empty, or has every pixel equal, the stage row gets a ⚠, with the reason in its tooltip and in Problems (a separate row from the ✕ errors). If the result is a table with fields such as converged / valid / ok, Studio mirrors that declaration. A result that carries no validity information is shown as “validity not judged”, never as OK. After Run all the status bar reads “Run: done / Result: needs review (reason)” or “Run: done / Result: validity not judged”. Studio only reports; it does not repair the operator’s numerical problem or change any value.input (the path as given, the file’s sha256, shape and dtype). On open, a file that exists with a matching sha256 is reloaded silently. A file that exists but differs is loaded and Problems shows “input file changed since save”. If it is missing, the current image is kept and Problems shows “input not found: path” (a file with the same name next to the .json is tried as a relink candidate). Older JSON files without input load exactly as before.true of the stage in the project file (nothing is written when it is off, so a file that never uses it is byte-identical to before); it is part of the cache key, so toggling it recomputes from that stage on. The Python export keeps only a # bypassed: op comment line, and the exported script gives the same result as the screen. In the Program text a bypassed stage is the line # bypassed: op (a, b), and “Apply” reads it back, so the mark survives.pinned (never the arrays) and recomputes the arrays when it is opened; if one cannot be recomputed the pin is kept and Problems says “cannot recompute A”. Older files without pinned open as before.Run ▸ Apply this processing to a folder… lets you pick an input folder and a file pattern, check the result on the first, middle, last or any chosen image, and then run the whole set. The inputs and the processing chain are frozen once at the start, so turning a knob during the run does not change the conditions of the running batch.
For files with the same name you choose between “do not overwrite”, “overwrite” and “add a sequence number”; when subfolders are included, the same hierarchy is created on the output side. summary.csv holds the file name, status, processing time, numeric outputs and the error reason. If reading or processing one image fails, the rest continues, and after the run you can review the failed files with their reasons in a list. A stop request is accepted at the boundary after the current image, and “stopped” is not shown until the run has actually stopped.
Choose Build processing, Compare results, Inspect values, or Make code in the top bar to enlarge the panels needed for that task and collapse the rest. Ctrl+1–4 switches modes. Inputs, processing steps, and computed results remain unchanged, and manual panel adjustments are remembered per mode. F11 temporarily shows only the image; Esc restores the exact previous layout. Find panel… recalls hidden panels by a Japanese or English name.
Auto per image assigns a range to each image, Shared for comparison uses one range across A/B, and Manual applies the entered limits to display modes that use a value range. A mode that does not use the range, such as region overlay, says so in the label. The “display processing only” note means that source values are unchanged. NaN and Inf are magenta; values outside the range use the end colours and produce a pixel-count warning. When saving, choose Original values, Current display image, or Both; original values are the default.
A pipeline built in Studio can be taken out in three forms.
| Form | How to produce it | Where to use it |
|---|---|---|
--ops string |
Export (Ctrl+E) | paste into the CLI’s imgevolve.py pipeline --ops "…" / run "…" |
| Python function | Export (Ctrl+E) | embed in your own code as fullseye.run_pipeline(...) |
| JSON | Save pipeline (Ctrl+Shift+S) | run with FullseyeEngine.load(...) / imgevolve.py run pipeline.json |
The HDevelop→HDevEngine-equivalent flow of design in Studio, execute in code/CLI is done via the JSON. For the side that receives and executes the JSON, see ENGINE.md.