# Astronomy Image Classification and Transient Vetting Tool

Source: https://www.potatoannotator.com/use-cases/astronomy

**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](https://doi.org/10.1093/mnras/stz2357)). 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](https://doi.org/10.1093/mnras/stx1812)). 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](/docs/annotation-types/astronomy) |
| Record why a candidate is bogus | A `multiselect` question on the cause, shown only after an annotator picks Bogus | [Astronomy](/docs/annotation-types/astronomy#configuration) |
| Check the light curve | The candidate's light curve under the cutouts, with upper limits as open triangles | [Astronomy](/docs/annotation-types/astronomy#ztf-light-curves) |
| 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](/docs/annotation-types/astronomy#galaxy-morphology-with-a-question-tree) |
| 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](/docs/guides/digital-humanities#zooniverse) |
| Agreement on vetting labels | Percent agreement, Cohen's and Fleiss' kappa, and Krippendorff's alpha on the vetting question | [Inter-annotator agreement](/docs/guides/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](/docs/annotation-types/astronomy) page has the item format.

```yaml
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](https://www.astropy.org/), 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.gz` files.
- **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`:

```bash
python -m potato.export -c config.yaml -f consensus -o exports/consensus
```

With 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](https://www.zooniverse.org/) 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](https://skyportal.io/) 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](https://fritz.science/) 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](https://science.alerce.online/services/sn-hunter) and [Lasair](https://lasair.readthedocs.io/en/main/about.html) 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](/docs/getting-started/quick-start-with-a-coding-agent) 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](/docs/annotation-types/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](/docs/annotation-types/astronomy), for every display option and item key
- [Radio and multiselect](/docs/annotation-types/radio-multiselect), for the schemes that ask the vetting and tree questions
- [How many annotators do you need?](/docs/guides/how-many-annotators), for choosing the number of annotators per candidate
- [Quality control](/docs/features/quality-control), for gold-standard candidates and attention checks
- [Air-gapped deployment](/docs/measurement/air-gap), for running Potato on a network with no route out
- [Digital humanities](/use-cases/digital-humanities), for the Zooniverse import and IIIF images on this site

## References

1. 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
2. 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
