Potato shows FITS images in the browser, where annotators vet transient candidates from surveys such as ZTF and Rubin as real or bogus on the science, reference, and difference cutouts and the light curve, or classify galaxy morphology with a Galaxy-Zoo-style question tree. Potato is free and open source, runs on your own server, and exports each annotator's labels separately for training a real-bogus classifier.
A difference image is a calibrated science exposure minus a reference (template) image of the same field, and a real transient appears in the difference image as a new point source. In real-bogus vetting, annotators label each detection as an astrophysical source or an artifact such as a cosmic ray. The Braai classifier for ZTF was trained on 32,640 labeled science, reference, and difference triplets, and the labels came from Zwickyverse, a simple web-based open-source tool (Duev et al., 2019). In the same paper, about 30% of the Zooniverse volunteers' bogus labels on candidates with low scores from an earlier random-forest classifier turned out to be real. Most of the mislabeled candidates were variable stars, and the volunteers had seen no light curves. In a Pan-STARRS1 supernova search, combining volunteer and neural-network classifications outperformed either one alone (Wright et al., 2017). Potato shows the light curve under the cutouts and sends each candidate to the number of annotators you set.
Astronomy tasks in Potato
Each row uses the fits_cutout display, new in Potato 2.13, with radio and multiselect schemes for the questions.
| Task | How Potato handles the task | Docs |
|---|---|---|
| Vet transient candidates | Science, reference, and difference cutouts side by side, with Real, Bogus, and Unsure on the R, B, and U keys | Astronomy |
| Record why a candidate is bogus | A multiselect question on the cause, shown only after an annotator picks Bogus | Astronomy |
| Check the light curve | The candidate's light curve under the cutouts, with upper limits as open triangles | Astronomy |
| Classify galaxy morphology | One image per item and one radio question per node of the tree, with display_logic naming the path to each follow-up | Astronomy |
| Move a Zooniverse workflow in-house | potato import -f zooniverse turns the workflow's tasks into schemes and can keep the volunteers' classifications | Digital humanities |
| Agreement on vetting labels | Percent agreement, Cohen's and Fleiss' kappa, and Krippendorff's alpha on the vetting question | Inter-annotator agreement |
A ZTF vetting study with three annotators
The config below has three annotators vet every ZTF candidate, reads the light curve in ZTF's column names, and asks for the cause of each bogus detection. The astronomy page has the item format.
annotation_task_name: "ZTF candidate vetting"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/candidates.json] # each item names three cutouts and a light curve
item_properties: {id_key: id, text_key: summary}
user_config:
allow_all_users: true
num_annotators_per_item: 3
instance_display:
fields:
- key: candidate
type: fits_cutout
label: "Cutouts"
display_options:
panels:
- {key: science, label: Science}
- {key: reference, label: Reference}
- {key: difference, label: Difference, stretch: asinh}
light_curve_columns: {time: jd, band: fid, mag: magpsf, err: sigmapsf, limit: diffmaglim}
time_label: JD
annotation_schemes:
- annotation_type: radio
name: vetting
description: "Is this a real astrophysical transient?"
humanize_labels: false
labels:
- {name: "Real", key_value: "r"}
- {name: "Bogus", key_value: "b"}
- {name: "Unsure", key_value: "u"}
label_requirement: {required: true}
- annotation_type: multiselect
name: bogus_reason
description: "What makes the candidate bogus?"
humanize_labels: false
labels: ["Subtraction residual (dipole)", "Cosmic ray or hot pixel", "Satellite or asteroid trail", "Bad column or image edge", "Other"]
display_logic:
show_when:
- {schema: vetting, operator: equals, value: "Bogus"}num_annotators_per_item: 3 sends each candidate to three annotators. Potato's light curve reader looks for column names such as mag and magerr, and light_curve_columns maps ZTF's magpsf and sigmapsf onto the mag and err roles. Without the mapping, every row with a diffmaglim value is drawn as an upper limit. With humanize_labels: false, "Bogus" on screen matches the value in the display_logic condition.
Getting cutouts in and labels out
Potato reads FITS files on the server with astropy, and the astro extra installs astropy (pip install 'potato-annotation[astro]'). Each item names FITS files and light curves relative to the media directory:
- Cutouts.
.fits,.fit,.fts, and gzipped.fits.gzfiles. - Positions. An item may give a center in RA and Dec or in FITS pixels, with a size in pixels or as an angle such as
"40 arcsec", and an item with no position shows the whole image. - Light curves. A CSV, TSV, or JSON file, or a list of points written into the item.
The display reads every file from the media directory and refuses URLs. The consensus export writes each candidate's top answer, vote fractions, and a retired or needs_review status to consensus_questions.csv:
python -m potato.export -c config.yaml -f consensus -o exports/consensusWith the default agreement_threshold of 0.8 and three annotators, a candidate retires only when all three annotators agree, and the rest go to needs_review. An answer to the cause question stays in the stored annotation after an annotator changes Bogus to Real, and neither the export nor the agreement metrics marks the answer as hidden. Filter bogus_reason on the vetting answer before training on the labels.
Working alongside Zooniverse, SkyPortal, and the alert brokers
Zooniverse hosts free citizen-science projects that launch publicly after a review and suits a public volunteer crowd, as in Galaxy Zoo and Zwicky's Quirky Transients. SkyPortal is a web application that displays astronomical datasets for annotation, analysis, and discovery, with a candidate scanning page for sources from a filtered alert stream. Fritz is the science data platform of ZTF, builds on SkyPortal, and suits a survey collaboration's daily scanning and follow-up. Alert brokers such as ALeRCE and Lasair filter and classify the full alert stream, and Lasair's SQL filters can run on the live stream. Potato fits a private team or a class labeling a fixed set of candidates or galaxies, because Potato assigns each item to a set number of annotators, trains annotators on practice items before the real ones, and exports each annotator's labels separately. One route is to save the cutouts of candidates from a broker as .fits.gz files, vet the candidates in Potato, and train on the retired labels.
Setting up the study with a coding agent
potato-skill 1.2.0 knows the fits_cutout display by name and guides Claude Code, Codex, or Cursor through the study-design side, such as what to ask annotators, how many annotators vet each candidate, training and attention checks, and export. The skill does not yet document the FITS item format, the display options, or the astro extra. Point the agent at the astronomy page and the page's Markdown copy, https://www.potatoannotator.com/docs/annotation-types/astronomy.md, for the formats and options.
Frequently asked questions
Can annotators see the science, template, and difference cutouts of an alert side by side?
Yes. The fits_cutout display shows the science, reference (template), and difference cutouts of each candidate in one row, with shared zoom and pan and one crosshair across the three panels. A ZTF alert cutout saved to disk with a .fits.gz name opens without decompression, and each panel can set a different stretch, such as asinh on the difference image.
Are cutouts that vary in orientation across visits rotated north up?
No. Potato shows each cutout in the orientation the FITS file stores and draws north and east arrows on each panel from the panel's WCS. When the panels are not on one pixel grid, a note under the panels says so, and the crosshair is placed on each panel by sky position.
Can annotators read RA and Dec from the WCS of a FITS image?
Yes. The readout under the panels shows the RA and Dec under the pointer, in sexagesimal and in degrees, with the FITS pixel of the original image. The readout interpolates a grid of sky positions that astropy computes on the server and follows any projection astropy reads, SIP distortion included.
Further reading
- Astronomy, for every display option and item key
- Radio and multiselect, for the schemes that ask the vetting and tree questions
- How many annotators do you need?, for choosing the number of annotators per candidate
- Quality control, for gold-standard candidates and attention checks
- Air-gapped deployment, for running Potato on a network with no route out
- Digital humanities, for the Zooniverse import and IIIF images on this site
References
- Duev et al. (2019). Real-bogus classification for the Zwicky Transient Facility using deep learning. Monthly Notices of the Royal Astronomical Society 489(3). https://doi.org/10.1093/mnras/stz2357
- Wright et al. (2017). A transient search using combined human and machine classifications. Monthly Notices of the Royal Astronomical Society 472(2). https://doi.org/10.1093/mnras/stx1812