Robert Lupton

How the Frames Pipeline Sets its Flags

Photo version $Name$

The frames pipeline uses a set of flags for each object to call attention to possible problems and to record decisions made during processing. These flags apply both to the object as a whole (including data from each band), and to each band separately.

An OBJC is the entire object in its polychromatic glory, while an OBJECT1 is the measured object in just one of the bands.

Reading the following description will be easier if you have a nodding acquaintance with the frames pipeline, but it should be useful to all.

The Flags that Frames Uses
A description of each flag
When and Where Flags are Set as Frames Runs
Pseudo-code describing how the flags are set
Meaning of Photo's Object Classification
A description of the categories that photo uses to classify objects
Meaning of Photo's mask (`fpM') files
A description of mask bits that photo sets.

The Flags that Frames Uses

As of version V4_8, photo has two sets of flags, flags and flags2. There is no essential difference between the two; the first 32 bits if information are set in flags and the remainder in flags2. Flags in the former have names beginning OBJECT1_, in the latter they begin OBJECT2_.

The numerical values given in the following tables are the offsets of the named bits; for example, OBJECT1_EDGE (2) corresponds to an integer value of (1 << 2) == 0x4.

0 OBJECT1_CANONICAL_CENTER
The quantities (psf counts, model fits and likelihoods) that are usually determined at an object's center as determined band-by-band were in fact determined at the canonical center (suitably transformed).

This is due to the object being to close to the edge to extract a profile at the local center, and OBJECT1_EDGE is also set.

1 OBJECT1_BRIGHT
2 OBJECT1_EDGE
Object is too close to edge of frame in this band. Specifically, at least one of the following is true:

OBJECT1_EDGE objects will have OBJECT1_NODEBLEND set, and the object will not be deblended.

If OBJECT1_EDGE is set in any band, it will be set for the OBJC. Children inherit this flag.

3 OBJECT1_BLENDED
Object was determined to be a blend. The flag is set if:

If OBJECT1_EDGE is set in any band, it will be set for all bands, and for the OBJC itself.

If, while running the deblender, it so happens that only one child remains, this flag is turned off.

4 OBJECT1_CHILD
Object is a child, created by the deblender. This flag is set in all bands, and also in the OBJC.
5 OBJECT1_PEAKCENTER
Given center is position of peak pixel, as attempts to determine a better centroid failed.

This flag is inherited by children. (XXX can it ever be set for an OBJC?)

6 OBJECT1_NODEBLEND
Although this object was marked as a blend, no deblending was attempted. This can happen because:
7 OBJECT1_NOPROFILE
Frames couldn't extract a radial profile. This can be caused by:
8 OBJECT1_NOPETRO
No Petrosian radius or other Petrosian quanties could be measured. This can be caused by:

If this bit is set, the error in the Petrosian radius is set to -1000, and if it's set in the canonical band the uncertainty in the Petrosian radius is not included in the Petrosian counts error.

This flag is inherited by the OBJC.

9 OBJECT1_MANYPETRO
Object has more than one possible Petrosian radius.

This flag is inherited by the OBJC.

10 OBJECT1_NOPETRO_BIG
The Petrosian ratio has not fallen to the value at which the Petrosian radius is defined at the outermost point of the extracted radial profile. OBJECT1_NOPETRO is set, and the "Petrosian radius" is set to the outermost point in the profile.
11 OBJECT1_DEBLEND_TOO_MANY_PEAKS
The object had the OBJECT1_DEBLEND flag set, but it contained too many candidate children to be fully deblended. This flag is only set in the parent, i.e. the object with too many peaks. Only the brightest nchild_max peaks are considered; the value of nchild_max is an input parameter to the frames pipeline.
12 OBJECT1_CR
Object contains at least one pixel which was contaminated by a cosmic ray. The OBJECT1_INTERP flag is also set. N.b. This flag does not mean that this object is a cosmic ray; rather it means that a cosmic ray has been removed. The flag OBJECT2_MAYBE_CR does mean that this object may be a cosmic ray but should be taken with a pinch of salt --- it's main use is for cleaning up samples of objects detected in only a single band.
13 OBJECT1_MANYR50
More than one radius was found to contain 50% of the Petrosian flux. (For this to happen part of the radial profile must be negative)
14 OBJECT1_MANYR90
More than one radius was found to contain 90% of the Petrosian flux. (For this to happen part of the radial profile must be negative)
15 OBJECT1_BAD_RADIAL
Measured profile includes points with a S/N <= 0. In practice this flag is essentially meaningless, and it may be withdrawn in the future.
16 OBJECT1_INCOMPLETE_PROFILE
A circle, centerd on the object, of radius the "canonical" Petrosian radius extends beyond the edge of the frame. The radial profile is still measured from those parts of the object that do lie on the frame.
17 OBJECT1_INTERP
The object contains interpolated pixels (e.g. cosmic rays or bad columns). See also OBJECT2_INTERP_CENTER.
18 OBJECT1_SATUR
The object contains saturated pixels; OBJECT1_INTERP is also set. See also OBJECT2_SATUR_CENTER.
19 OBJECT1_NOTCHECKED
Object includes pixels that were not checked for peaks, for example the unsmoothed edges of frames, and the cores of subtracted or saturated stars (specifically, regions where mask bit NOTCHECKED is set in the fpM file). If no peaks are found in the checked part of the object it is rejected. Note that bright stars are detected before the wings are subtracted.
20 OBJECT1_SUBTRACTED
Object (presumably a star) had wings subtracted
21 OBJECT1_NOSTOKES
Object has no measured Stokes params. This can happen because:
22 OBJECT1_BADSKY
The estimated sky level is so bad that the central value of the radial profile is crazily negative; this is usually the result of the subtraction of the wings of bright stars failing.
23 OBJECT1_PETROFAINT
At least one candidate Petrosian radius occured at an unacceptably low surface brightness; this can lead to OBJECT1_NOPETRO being set.
24 OBJECT1_TOO_LARGE
The object is (as it says) too large. The two ways that this can be set are:
25 OBJECT1_DEBLENDED_AS_PSF
When deblending an object, in this band this child was treated as a PSF. The two ways that this can happen are:
26 OBJECT1_DEBLEND_PRUNED
When solving for the weights to be assigned to each child the deblender encountered a nearly singular matrix, and therefore deleted at least one of them.

This flag is propagated to the OBJC.

27 OBJECT1_ELLIPFAINT
No isophotal fits were performed. Possible reasons are:
28 OBJECT1_BINNED1
The object was detected in an unbinned image
29 OBJECT1_BINNED2
The object was detected in a 2x2 binned image after all unbinned detections have been replaced by the background level
30 OBJECT1_BINNED4
The object was detected in a 4x4 binned image. The objects detected in the 2x2 binned image are not removed before doing this.
31 OBJECT1_MOVED
The object appears to have moved during the exposure. Such objects are candidates to be deblended as moving objects; see OBJECT2_DEBLENDED_AS_MOVING.
32 OBJECT1_DETECTED
A meta-flag, defined as:
(OBJECT1_BINNED1 | OBJECT1_BINNED2 | OBJECT1_BINNED4)                      [28,29,30]
Now for the OBJECT2 flags:
0 OBJECT2_DEBLENDED_AS_MOVING
The object has the OBJECT1_MOVED flag set, and was deblended on the assumption that it was moving.
1 OBJECT2_NODEBLEND_MOVING
A blend labelled OBJECT1_MOVED was not deblended as a moving object.

This flag is inherited by the OBJC.

2 OBJECT2_TOO_FEW_DETECTIONS
The object has the OBJECT1_MOVED flag set, but was detected in too few bands to be reliably deblended as moving; OBJECT1_NODEBLEND will also be set.
3 OBJECT2_BAD_MOVING_FIT
Fit to moving object was too poor to be believable. If a candidate moving object, is wasn't deblended as moving.

This flag is inherited by the OBJC.

4 OBJECT2_STATIONARY
A "moving" object's velocity is consistent with zero

This flag is inherited by the OBJC.

5 OBJECT2_PEAKS_TOO_CLOSE
Peaks in object were too close XXX; this flag is set only in the parent.

This flag is inherited by the OBJC.

6 OBJECT2_BINNED_CENTER
When centroiding the object the object's size is larger than the (PSF) filter used to smooth the image. Photo bins the image by a factor of 2 in row and/or column, tries again, and sets this flag.
7 OBJECT2_LOCAL_EDGE The object's center in some band was too close to the edge of the frame to extract a profile. For extended objects, this may only be the case after binning; see the quoted errors in rowc and colc. This flag is also set if an object touches the edge of the region being searched for objects; this is almost the same as the prescription for setting OBJECT1_EDGE except in the case where part or all of a CCD is misbehaving when the `notchecked' area may be large. This flag is inherited by children.
8 OBJECT2_BAD_COUNTS_ERROR
An object containing interpolated pixels had too few good pixels to form a reliable estimate of its error; the quoted error may be underestimated
9 OBJECT2_BAD_MOVING_FIT_CHILD
A putative moving child's velocity fit was too poor, so it was discarded and the parent wasn't deblended as moving.

This flag is inherited by the OBJC.

10 OBJECT2_DEBLEND_UNASSIGNED_FLUX
After deblending, the fraction of flux assigned to none of the children was too large (this flux is then shared out as described elsewhere).

This flag is inherited by the OBJC.

11 OBJECT2_SATUR_CENTER
An object's center is very close to at least one saturated pixel; the object may well be causing the saturation.

This flag is inherited by the OBJC.

12 OBJECT2_INTERP_CENTER
An object's center is very close to at least one interpolated pixel.

This flag is inherited by the OBJC.

13 OBJECT2_DEBLENDED_AT_EDGE
An object so close to the edge of the frame that it would not ordinarily be deblended has been deblended anyway. Only set for objects large enough to be EDGE in all fields/strips.

This flag is inherited by the OBJC.

14 OBJECT2_DEBLEND_NOPEAK
A child had no detected peak in a given band, but we centroided it anyway and set the BINNED1

This flag is inherited by the OBJC.

15 OBJECT2_PSF_FLUX_INTERP
The fraction of light actually detected (as opposed to guessed at by the interpolator) was less than some number (currently 80%) of the total.

This flag is inherited by the OBJC.

16 OBJECT2_TOO_FEW_GOOD_DETECTIONS
A child of this object had too few good detections to be deblended as moving.

This flag is inherited by the OBJC.

17 OBJECT2_CENTER_OFF_AIMAGE
At least one peak's center lay off the atlas image in some band. This can happen when the object's being deblended as moving, or if the astrometry is badly confused.

This flag is inherited by the OBJC.

18 OBJECT2_DEBLEND_DEGENERATE
At least one potential child has been pruned because its template was too similar to some other child's template.

This flag is inherited by the OBJC.

19 OBJECT2_BRIGHTEST_GALAXY_CHILD
This is the brightest child galaxy in a blend.
20 OBJECT2_CANONICAL_BAND
This band was the canonical band (usually r'). This is the band used to measure the Petrosian radius used to calculate the Petrosian counts in each band, and to define the model used to calculate model colours; it has no effect upon the coordinate system used for the OBJC center.
21 OBJECT2_AMOMENT_UNWEIGHTED
`Adaptive' moments are actually unweighted XXX Update all the AMOMENT flags. N.b. there is no flag for failed to measure (look at the errors).
22 OBJECT2_AMOMENT_SHIFT Object's center moved too far while determining adaptive moments XXX In this case, the M_e1 and M_e2 give the (row, column) shift, not the object's shape. Caveat Astronomor.
23 OBJECT2_AMOMENT_MAXITER Too many iterations while determining adaptive moments XXX
24 OBJECT2_MAYBE_CR
This object may be a cosmic ray. This bit can get set in the cores of bright stars, and is quite likely to be set for the cores of saturated stars (so you may want to ignore it if OBJECT2_SATUR_CENTER is set). [11]

This flag is inherited by the OBJC.

25 OBJECT2_MAYBE_EGHOST
Object appears in the right place to be an electronics ghost.
26 OBJECT2_NOTCHECKED_CENTER
Centre of object lies in a NOTCHECKED region. The object is almost certainly bogus.
27 OBJECT2_HAS_SATUR_DN
This object is saturated in this band and the bleed trail doesn't touch the edge of the frame, we we've made an attempt to add up all the flux in the bleed trails, and to include it in the object's photometry. Note: some of the CCDs saturate at over 65535 DN; for these chips, the bled flux will be underestimated.
28 OBJECT2_DEBLEND_PEEPHOLE
After the regular deblender had completed, photo took another pass looking for some special cases, and the deblend was modified based on this analysis. The special cases covered are currently:
The following bits are in an internal-only flag word called flags3 which is never written to output files, but they are used internally and are therefore included in this document. Photo defines aliases for them (OBJECT2_USR1 ... OBJECT2_USR4) which are used while analysing home-made fpObjc files.
28 OBJECT3_MEASURED
This object's properties have been measured.
29 OBJECT3_GROWN_MERGED
Growing this object after it had been detected (maybe as a single pixel over threshold) led to a merger.
30 OBJECT3_HAS_CENTER
This OBJC has a canonical center.
31 OBJECT3_MEASURE_BRIGHT
This BRIGHT object should be measured, not just treasured for its astrometric properties.

When and Where Flags are Set as Frames Runs

The variable objc refers to an OBJC, while object1 refers to an OBJECT1. You are expected to imagine a loop over all filters, setting flags as appropriate.

The following pseudo-code roughly corresponds to the organisation of the frames pipeline, and bears some resemblence to C. The following idioms are used:

var |= FLAG;
Set the FLAG bit in the variable var
if(var & FLAG)
Is var's FLAG bit set?
var &= ~FLAG;
Clear the FLAG bit in the variable var

The numbers in the right hand margin refer to the previous section, where the individual flags are described.

find_objects
{
   if(pixels over threshold touch edge of frame) {
      object1->flags |= OBJECT1_EDGE;                                      [2]
   }
   if(pixels over threshold touch notchecked part of frame) {
      object2->flags |= OBJECT2_LOCAL_EDGE;                                [7]
   }
   if(object includes unsearched pixels (e.g. unsmoothed edges of frames)) {
      object1->flags |= OBJECT1_NOTCHECKED;                                [19]
      reject all peaks in  parts of frame;
   }
/*
 * information about which mode object was detected in
 */
   if(bright objects) {
      object1->flags |= OBJECT1_BRIGHT;                                    [1]
   }

   if(not binned) {
      object1->flags |= OBJECT1_BINNED1;                                   [28]
   } else if(binned 2x2) {
      object1->flags |= OBJECT1_BINNED2;                                   [29]
   } else if(binned 4x4) {
      object1->flags |= OBJECT1_BINNED4;                                   [30]
   }
/*
 * information about pixels contained in object
 */
   if(object contains interpolated pixels) {
      object1->flags |= OBJECT1_INTERP;                                    [17]

      if(object contains cosmic ray contaminated pixels) {
         object1->flags |= OBJECT1_CR;                                     [12]
      }

      if(object contains saturated pixels after centering on peak) {
         object1->flags |= OBJECT1_SATUR;                                  [18]
         if(only one object intersects a bleed trail &&
             the bleed trail doesn't touch the edge of the frame) {
            object1->flags2 |= OBJECT2_HAS_SATUR_DN;                       [27]
         }
      }
   }
}
find_center
{
   if((object1->flags & OBJECT1_SATUR) && saturated star centroider fails) { [18]
      object1->flags |= OBJECT1_PEAKCENTER;                                [5]
   }
   
   for(;;) {
      find_peak_center;

      if(center is OK && width estimate is consistent with size of object) {
         break;
      }

      object2->flags |= OBJECT2_BINNED_CENTER;                             [6]

      if(too close to edge while searching for peak) {
         object1->flags |= OBJECT1_EDGE | OBJECT1_PEAKCENTER;              [2,5]
         break;
      }
   
      if(object has vanishing second derviative || centroider fails otherwise) {
         object1->flags |= OBJECT1_PEAKCENTER;                             [5]
         break;
      }

      bin image in row/and or column;
   }

   if(object1->flags & OBJECT1_PEAKCENTER) {                               [5]
      take center of brightest pixel as peak position;
   }
}
merge_colors
{
   merge together peaks in saturated cores;

   if(more than one peaks is detected within an object in one band) {
      objc->flags |= OBJECT1_BLENDED;                                      [3]
   }

   if(distinct peaks found in different bands of same object) {
      objc->flags |= OBJECT1_BLENDED;                                      [3]
   }

   if(multiple distinct peaks in merged objects) {
      objc->flags |= OBJECT1_BLENDED;                                      [3]
   }

   if(obj1 and obj2 are to be merged) {
      if((!(obj1->flags & OBJECT1_DETECTED) &&
           (obj2->flags & OBJECT1_DETECTED)) ||                            [32]
          (obj1->flags & OBJECT1_CANONICAL_CENTER)) {                      [0]
         use obj2;
      } else if(((obj1->flags & OBJECT1_DETECTED) &&
                !(obj2->flags & OBJECT1_DETECTED)) ||                      [32]
                (obj2->flags & OBJECT1_CANONICAL_CENTER)) {                [0]
         use obj1;
      } else {
         use the one with the brighter peak;
      }
   }
}
subtract_bright_stars
{
   if(object1->flags & OBJECT1_EDGE) {                                     [2]
      don't subtract wings;
      return;
   }

   if(object1->flags & OBJECT1_SATUR) {                                    [18]
      estimate psfCounts from profile;
      if(more than 20% of counts are interpolated) {
         object1->flags2 |= OBJECT2_PSF_FLUX_INTERP;                       [15]
      }
   }

   if(object contains pixels from which star wings were subtracted) {
      object1->flags |= OBJECT1_SUBTRACTED;                                [20]
   }
   if(object is in part of frame where bright star wings exceeded 10 sky sigma,
                           or star wings increased variance by more than 50%) {
      object1->flags |= OBJECT1_NOTCHECKED;                                [19]
      
   }
}
peak_up_astrometry
{
   if(!(object1->flags & OBJECT1_DETECTED) ||                              [32]
      (object1->flags & (OBJECT1_SATUR | OBJECT1_PEAKCENTER))) {           [18,5]
      Don't use star in matchup
   }
}
measure_objects
{
/*
 * book keeping
 */
   if(detected as a bright object) {
      if(measuring bright objects &&
         !(objc->flags2 & OBJECT2_MEASURE_BRIGHT)) {                       [1]
         don't measure object;
         return;
      }
      if(being remeasured after faint object detection) {
         make new object which is sibling of bright object,
         and prepare to measure this new creation;
         unset OBJECT1_BRIGHT in objc and object1;                         [1]
      }
   }

   recentroid_and_find_canonical_center {
      foreach object1 in objc {
         if(objc->flags2 & OBJECT2_DEBLENDED_AS_MOVING) {                  [0]
            we already have a good center;
            continue;
         }

         if(!(detected in this band)) {
            if(!child) {
               continue;
            }

            set center from objc;
            object1->flags |= OBJECT1_CANONICAL_CENTER | OBJECT1_BINNED1;  [0,28]

            object1->flags2 |= OBJECT2_DEBLEND_NOPEAK;                     [14]
         }
      }

      find_canonical_center {
         object1 = object1 in canonical band;
         if(object1->flags & OBJECT1_DETECTED &&                           [32]
            !(object1->flags) & OBJECT1_SATUR) {                           [18]
            use this object1's center;
         } else if(at least one peak is non-saturated) {
            use brightest non-saturated peak;
         } else {
            use brightest peak;
         }

         convert to canonical band's coordinate system;

         clear all OBJECT2_CANONICAL_BAND bits;                            [20]
         if(detected in canonical band) {
            object1 = object1 in canonical band;
         } else {
            use object1 as above;
         }
         object1->flags2 |= OBJECT2_CANONICAL_BAND;                        [20]
      }
   }

   create_object1s {
      foreach (missing OBJECT1) {
         make object1;
         if(bright) {
            object1->flags |= OBJECT1_BRIGHT;                              [1]
         }
         set center from OBJC;
         object1->flags |= OBJECT1_CANONICAL_CENTER;                       [0]
      }

      if(blended) {
         objc->flags |= object1->flags & OBJECT1_BLENDED;                  [3]
      }
   }

   if(!(objc->flags & OBJECT1_BRIGHT)) {                                   [1]
      save atlas images;
   }
/*
 * set sky level
 */
   if(object1->flags & OBJECT1_CHILD) {                                    [4]
      sky += contribution from siblings;
      if(sky summation failed &&
         child->flags2 & (OBJECT2_DEBLENDED_AT_EDGE |                      [13]
                          OBJECT2_DEBLENDED_AS_MOVING)) {                  [0]
         sky = sky level at child's center;
      } else {
         This cannot happen, so die;
      }
   }
/*
 * Was this object "found" in an area we weren't checking for objects?
 */
   if(center near a  part of the frame) {
      object1->flags2 |= OBJECT2_NOTCHECKED_CENTER;                        [26]
      if((object1->flags & OBJECT1_DETECTED) &&                            [32]
         (object1->flags2 & OBJECT2_DEBLEND_NOPEAK)) {                     [14]
            object1->flags &= ~OBJECT1_DETECTED;                           [32]
         }
      }
   }
/*
 * extract profile;
 */
   if(failed to extract radial profile) {
      object1->flags |= OBJECT1_EDGE | OBJECT1_NOPETRO | OBJECT1_NOPROFILE;
                                                                           [2,7,8]
      object1->flags2 |= OBJECT2_LOCAL_EDGE;                               [7]
      give up measuring object in this band;
   }

   if(pixel overflow while extracting radial profile) {
      object1->flags |= OBJECT1_SATUR;                                     [18]
   }
   
   if(object is still detected at edge of extracted profile (about 260arcsec)){
      object1->flags |= OBJECT1_TOO_LARGE;                                 [24]
   }

   if(measured profile includes points with a S/N <= 0) {
      object1->flags |= OBJECT1_BAD_RADIAL;                                [15]
   }
   
   if(central value of object more than 100 sigma below sky level) {
      object1->flags |= OBJECT1_BADSKY | OBJECT1_NOPETRO;                  [8,22]
      use fallback value for "Petrosian" radius;
      give up measuring object in this band; something is horribly wrong.
   }

   if(radial profile has fewer than two points) {
      object1->flags |= OBJECT1_NOPROFILE;                                 [7]
      object1->flags |=
                       OBJECT1_NOPETRO | OBJECT1_ELLIPFAINT | OBJECT1_NOSTOKES;
      give up measuring Petrosian quantities, Stokes parameters, and
        isophotal quantities;                                              [8,21,27]
   }
/*
 * measure a few misc quantities
 */
   if(central surface brightness is below threshold for isophotal shape ||
      failed to fit ellipse at threshold +- delta) {
      object1->flags |= OBJECT1_ELLIPFAINT;                                [27]
      give up fitting ellipse;
   }

   if(failed to measure U or Q due to numerical difficulties) {
      object1->flags |= OBJECT1_NOSTOKES;                                  [21]
   }
/*
 * measure Petrosian quantities
 */
   while(surface brightness at innermost candidate for Petrosian radius
         is too low) {
      reject candidate;
      object1->flags |= OBJECT1_PETROFAINT;                                [23]
   }
   
   if(there are no Petrosian radii) {
      object1->flags |= OBJECT1_NOPETRO;                                   [8]
      if(we didn't reject any candidates for the Petrosian radius) {
         object1->flags |= OBJECT1_NOPETRO_BIG;                            [10]
         use outermost point in radial profile as "Petrosian" radius;
      } else {
         use fallback value for "Petrosian" radius;
      }
   } else {
      if(there is more than one Petrosian radius) {
         object1->flags |= OBJECT1_MANYPETRO;                              [9]
      }
   }
   
   if(more than one value of Petrosian 50% light radius) {
      object1->flags |= OBJECT1_MANYR50; /* how can this happen? */        [13]
   }
   if(more than one value of Petrosian 90% light radius) {
      object1->flags |= OBJECT1_MANYR90; /* how can this happen? */        [14]
   }
   if(object1->flags & OBJECT1_NOPETRO) {                                  [8]
      give up on errors for Petrosian quantities in this band;
   }

   if(canonical_object1->flags & OBJECT1_NOPETRO) {                        [8]
      don't propagate error in Petrosian radius into Petrosian counts;
   }

   if(object is less than one (r') Petrosian radius from edge of frame) {
      object1->flags |= OBJECT1_INCOMPLETE_PROFILE;                        [16]
   }
/*
 * Measure fibre and psf counts
 */
   foreach type (fibre psf) {
      if(counts were contaminated by INTERP pixels) {                      [17]
         estimate errors from "real" pixels
         if(contamination exceeded 50% ||
            (object1->flags2 & OBJECT2_BAD_COUNTS_ERROR)) {                [8]
            object1->flags2 |= OBJECT2_BAD_COUNTS_ERROR;                   [8]
         }
      }
   }
/*
 * Measure quantities about the center determined in this band
 */
   if(local band is to close to the edge of the frame) {
      object1->flags |= OBJECT1_CANONICAL_CENTER | OBJECT1_EDGE;           [0,2]
      object1->flags2 |= OBJECT2_LOCAL_EDGE;                               [7]
      use (transformed) canonical center;
   }
/*
 * mark measured as a bright object
 */
   if(measuring bright objects) {
      objc->flags |= OBJECT1_BRIGHT;                                       [1]
   }
/*
 * If object is a child and a blend, note that we don't (yet) run
 * deblender recursively. Moving objects appear blended, but they
 * aren't, so handle that case too
 */
   if((objc->flags & OBJECT1_CHILD) & (objc->flags & OBJECT1_BLENDED)) {   [43]
      if(objc->flags2 & OBJECT2_DEBLENDED_AS_MOVING) {                     [0]
         objc->flags &= ~OBJECT1_BLENDED;                                  [3]
      } else {
         objc->flags |= OBJECT1_NODEBLEND;                                 [6]
      }
   }
/*
 * Flag problems near the object's center
 */
   if(center of object is within 3 pixels of an interpolated pixel) {
      objc->flags2 &= OBJECT2_INTERP_CENTER;                               [12]
   }
   if(center of object is within 3 pixels of a saturated pixel) {
      objc->flags2 &= OBJECT2_SATUR_CENTER;                                [11]
   }
/*
 * Classify object as e.g. star/galaxy
 */
   if(object1->flags2 & OBJECT2_INTERP_CENTER) {                           [12]
      object1->type = unknown;
   }

   if(!(object1->flags & OBJECT1_DETECTED) ||                              [32]
      object1->flags2 & (OBJECT2_DEBLEND_NOPEAK | OBJECT2_INTERP_CENTER)) { [14,12]
      don't consider this band in objc's classification;
   }

   if(object1 looks like a cosmic ray) {
      object1->flags2 |= OBJECT2_MAYBE_CR;                                 [24]
   }

   if(object1 is in right place to be an electronics ghost) {
      object1->flags2 |= OBJECT2_MAYBE_EGHOST;                             [25]
   }
/*
 * Handle flux in bleed trails
 */
   if(object1->flags2 & OBJECT2_HAS_SATUR_DN) {                            [27]
      fiberCounts += counts_from_bleed_trail;
      petroCounts += counts_from_bleed_trail;
      counts_model += counts_from_bleed_trail;
      profMean[0] += counts_from_bleed_trail;
      fiberCounts += counts_from_bleed_trail;
      psfCounts = flux_within_aperture(nann_ap_run) + counts_from_bleed_trail;
   }
/*
 * In each band label the brightest child that's classified as a galaxy;
 * this is needed to avoid artificial restrictions in target selection
 */
   object1->flags2 |= OBJECT2_BRIGHTEST_GALAXY_CHILD                       [19]
/*
 * propagate flags to and from OBJC
 */
   if(objc->flags & OBJECT1_BLENDED) {                                     [3]
      object1->flags |= (OBJECT1_BLENDED | OBJECT1_NODEBLEND);             [3,6]
   }

   objc->flags |= object1->flags & (OBJECT1_EDGE |                         [2]
                                    OBJECT1_BLENDED |                      [3]
                                    OBJECT1_CHILD |                        [4]
                                    OBJECT1_NOPETRO |                      [8]
                                    OBJECT1_MANYPETRO |                    [9]
                                    OBJECT1_INTERP |                       [17]
                                    OBJECT1_CR |                           [12]
                                    OBJECT1_SATUR |                        [18]
                                    OBJECT1_NOTCHECKED |                   [19]
                                    OBJECT1_BINNED1 |                      [28]
                                    OBJECT1_BINNED2 |                      [29]
                                    OBJECT1_BINNED4);                      [30]
   objc->flags2 &= object1->flags2 & (OBJECT2_DEBLENDED_AS_MOVING |        [1]
                                      OBJECT2_NODEBLEND_MOVING |           [1]
                                      OBJECT2_TOO_FEW_DETECTIONS |         [2]
                                      OBJECT2_BAD_MOVING_FIT |             [3]
                                      OBJECT2_STATIONARY |                 [4]
                                      OBJECT2_PEAKS_TOO_CLOSE |            [5]
                                      OBJECT2_BAD_MOVING_FIT_CHILD |       [9]
                                      OBJECT2_DEBLEND_UNASSIGNED_FLUX |    [10]
                                      OBJECT2_SATUR_CENTER |               [12]
                                      OBJECT2_INTERP_CENTER |              [11]
                                      OBJECT2_DEBLENDED_AT_EDGE |          [13]
                                      OBJECT2_DEBLEND_NOPEAK |             [14]
                                      OBJECT2_PSF_FLUX_INTERP |            [15]
                                      OBJECT2_TOO_FEW_GOOD_DETECTIONS |    [16]
                                      OBJECT2_CENTER_OFF_AIMAGE |          [17]
                                      OBJECT2_DEBLEND_DEGENERATE |         [18]
                                      OBJECT2_MAYBE_CR);                   [24]
}
find_velocity
{
   if(((object1->flags & OBJECT1_DETECTED) &&                              [32]
      !(object1->flags2 & (OBJECT2_DEBLEND_NOPEAK |                        [14]
                           OBJECT2_INTERP_CENTER)) &&                      [12]
      !(object1->flags & (OBJECT1_CANONICAL_CENTER |                       [0]
                          OBJECT1_PEAKCENTER |                             [5]
                          OBJECT2_INTERP_CENTER)) {                        [12]
      use center in this band in velocity fit
   }

   if(object has good detections in <= 2 bands) {
      objc->flags2 |= OBJECT2_TOO_FEW_GOOD_DETECTIONS;                     [16]
   }

   if(chi^2 was too large) {
      objc->flags2 |= OBJECT2_BAD_MOVING_FIT;                               [3]
   }
}
deblender
{
   if(measuring bright objects && OBJECT1_BLENDED) {                       [3]
      objc->flags |= OBJECT1_NODEBLEND;                                    [6]
      give up on the object as it'll be reprocessed !BRIGHT;
   }

   if(there are too many peaks in object) {
      objc->flags |= OBJECT1_DEBLEND_TOO_MANY_PEAKS;                       [11]
      only use the n brightest peaks;
   }

   if(peaks are too close together) {
      forget the fainter peak;
      objc->flags |= OBJECT2_PEAKS_TOO_CLOSE;                              [5]
   }

   if(there's an "object" found at a different place in each band) {
      if(enough peaks in significantly different places) {
         objc->flags |= OBJECT1_MOVED;                                     [31]
      }
   }

   if(objc->flags & OBJECT1_MOVED) {                                       [31]
      objc->flags2 |= OBJECT2_DEBLENDED_AS_MOVING;                         [0]
      create extra MOVED child;
      child->flags |= OBJECT1_MOVED;                                       [31]
      child->flags2 |= OBJECT2_DEBLENDED_AS_MOVING;                        [0]
   }

   foreach(merged peak found in parent) {
      make_new_child_from_objc {
         if(child is detected in <= deblend_min_detect bands) {
            objc->parent->flags2 |= OBJECT2_TOO_FEW_DETECTIONS;            [2]
            don't create child;
            continue;
         }

         if(peak is labelled as MOVED) {
            child->flags |= OBJECT1_MOVED;                                 [31]
         }
         if(peak is labelled as PEAKCENTER) {
            child->flags |= OBJECT1_PEAKCENTER;                            [5]
            child_object1->flags |= OBJECT1_PEAKCENTER;                    [5]
         }

         child->flags = objc->flags & (OBJECT1_EDGE |                      [2]
                                       OBJECT1_INTERP |                    [17]
                                       OBJECT1_NOTCHECKED |                [19]
                                       OBJECT1_SUBTRACTED);                [20]
         child->flags |= OBJECT1_CHILD;                                    [4]

         child_object1->flags = object1->flags & (OBJECT1_CANONICAL_CENTER | [0]
                                                  OBJECT1_EDGE |           [2]
                                                  OBJECT1_PEAKCENTER |     [5]
                                                  OBJECT1_NOTCHECKED |     [19]
                                                  OBJECT1_SUBTRACTED);     [20]
         child_object1->flags |= OBJECT1_CHILD;                            [4]

         if(a peak was found in this band) {
            set center from peak;
            child_object1->flags |= OBJECT1_BINNED1;                       [28]
         } else {
            set center from OBJC;
            child_object1->flags |= OBJECT1_CANONICAL_CENTER;              [0]
         }
      }
   }

   phObjcDeblend {
      if(objc will be OBJECT1_EDGE in next strip/field too) {              [2]
         objc->flags2 |= OBJECT2_DEBLENDED_AT_EDGE;                        [13]
         child->flags2 |= OBJECT2_DEBLENDED_AT_EDGE;                       [13]
         trim the part of objc's atlas image that's too close to edge of field;
         if(no pixels survive trimming) {
            objc->flags |= OBJECT1_NODEBLEND;                              [6]
            give up
         }
      } else {
         if(objc->flags & OBJECT1_EDGE) {                                  [2]
            objc->flags |= OBJECT1_NODEBLEND;                              [6]
            give up, as edge objects invalidate assumptions made by deblender,
            and will be deblended in next strip/field anywayl
         }
      }

      if(objc->flags & OBJECT1_MOVED) {                                    [31]
         find child with (child->flags2 & OBJECT2_DEBLENDED_AS_MOVING);    [0]
         if(phObjcDeblendMovingChild(moving_child) succeeds) {
            delete all children with OBJECT1_MOVED set, as we have decided [31]
            to treat them as part of their own moving object;
         }
      }

      deblend_template_find {
         if(!(objc->flags & OBJECT1_SATUR) &&                              [18]
            child is consistent with PSF) {
            Subtract PSF from parent and use PSF as template in this band;
            objc_object1->flags |= OBJECT1_DEBLENDED_AS_PSF;               [25]
         }
      
         if(template for this child is too large (more than half a frame)) {
            objc->parent->flags |= OBJECT1_TOO_LARGE | OBJECT1_NODEBLEND;  [6,24]
            give up on deblending parent;
         }
         
         if(template + (smoothing length)/2 hangs over edge of frame in any band) {
            objc->parent->flags |= OBJECT1_EDGE | OBJECT1_NODEBLEND;       [2,6]
            give up on deblending parent;
         }

         if(center of child lies off the atlas image in some band) {
            parent->flags2 |= OBJECT2_CENTER_OFF_AIMAGE;                   [17] 
            parent_object1->flags2 |= OBJECT2_CENTER_OFF_AIMAGE;           [17] 
            delete child;
         }

         find templates for child;

         if(we wouldn't have detected this child in any band) {
            delete child;
         }

         if(failed to find a template in this band) {
            object1->flags &= ~OBJECT1_DETECTED;                           [32]
            object1->flags |= OBJECT1_DEBLENDED_AS_PSF;                    [25]
            use PSF template;
         }
         
         objc->flags |= object1->flags & OBJECT1_DETECTED;                 [32]
      }
      
      setup Normal Equations, and solve for weights for each child;

      if(at least one possible child is rejected due to singular matrix) {
         objc->flags |= OBJECT1_DEBLEND_PRUNED;                            [26]
         object1->flags |= OBJECT1_DEBLEND_PRUNED;                         [26]
      }
      
      if(two templates are too similar) {
         reject template corresponding to brighter child;
         objc->flags2 |= OBJECT1_DEBLEND_DEGENERATE;                       [18]
         child->flags2 |= OBJECT1_DEBLEND_DEGENERATE;                      [18]

         child->flags |= (reject->flags & OBJECT1_DETECTED);               [32]
         cobject1->flags |= (robject1->flags & OBJECT1_DETECTED);          [32]
      }
      
      if(only one child remains) {
         objc->flags &= ~OBJECT1_BLENDED;                                  [3]
         ensure that object's peaks correspond to those in the child,
         with OBJECT1_CANONICAL_CENTER and OBJECT1_DETECTED set correctly; [0,32]
         no need to deblend;
      }

      if(object is detectable in this band, but wasn't seen due (usually) to the
         vaguaries of peak matching) {
         object1->flags |= OBJECT1_BINNED1;                                [28]
      }

      if(Petrosian flux in children> deblend_allowed_unassigned*(parent's petroCounts)) {
         objc->color[c]->flags2 |= OBJECT2_DEBLEND_UNASSIGNED_FLUX;        [10]
      }
      
      if(child includes CR pixels) {
         object1->flags |= OBJECT1_CR;                                     [12]
      }
      if(child includes interpolated pixels) {
         object1->flags |= OBJECT1_INTERP;                                 [17]
      }
      if(child includes saturated pixels) {
         object1->flags |= OBJECT1_SATUR;                                  [18]
      }
   }
/*
 * Run peephole optimiser
 */
/*
 * Moving objects
 */
   if(any pair of children has no peak in the same band in common &&
      the set of peaks define a straight line in the order riuzg) {
      parent->flags2 &= ~(OBJECT2_NODEBLEND_MOVING |                       [1]
                          OBJECT2_BAD_MOVING_FIT |                         [9]
                          OBJECT2_BAD_MOVING_FIT_CHILD |                   [9]
                          OBJECT2_TOO_FEW_DETECTIONS);                     [2]

      child->flags2 &= ~(OBJECT2_NODEBLEND_MOVING |                        [1]
                         OBJECT2_BAD_MOVING_FIT |                          [9]
                         OBJECT2_BAD_MOVING_FIT_CHILD |                    [9]
                         OBJECT2_TOO_FEW_DETECTIONS);                      [2]

      parent->flags2 |=
            OBJECT2_DEBLENDED_AS_MOVING | OBJECT2_DEBLEND_PEEPHOLE;        [0,28]
      child->flags2 |=
            OBJECT2_DEBLENDED_AS_MOVING | OBJECT2_DEBLEND_PEEPHOLE;        [0,28]
      Set positions based on velocity vector;
      child->object1->flags |= OBJECT1_CANONICAL_CENTER;                   [0]

      repeat process, looking for other detections of the moving object
      in other children at the correct calculated position;
   }
}

phObjcDeblendMovingChild
{
   if(object is detected in <= 2 bands) {
      objc->parent->flags2 |= (OBJECT2_TOO_FEW_DETECTIONS |                [2]
                               OBJECT2_NODEBLEND_MOVING);                  [1]
      objc->parent->flags2 &= ~OBJECT2_DEBLENDED_AS_MOVING;                [0]
   }

   if(object has good detections in <= 2 bands) {
      objc->parent->flags2 |= (OBJECT2_TOO_FEW_GOOD_DETECTIONS |           [16]
                               OBJECT2_NODEBLEND_MOVING);                  [1]
      objc->parent->flags2 &= ~OBJECT2_DEBLENDED_AS_MOVING;                [0]
   }

   if(chi^2 for velocity fit is too large) {
      objc->parent->flags2 |= (OBJECT2_BAD_MOVING_FIT_CHILD |              [9]
                               OBJECT2_NODEBLEND_MOVING);                  [1]
      objc->parent->flags2 &= ~OBJECT2_DEBLENDED_AS_MOVING;                [0]
   }

   if(velocity is consistent with 0) {
      objc->parent->flags2 |= (OBJECT2_STATIONARY |                        [4]
                               OBJECT2_NODEBLEND_MOVING);                  [1]
      objc->parent->flags2 &= ~OBJECT2_DEBLENDED_AS_MOVING;                [0]
   }

   if(deblended as moving) {
      set centers for all OBJECT1_CANONICAL_CENTER objects                 [0]
      from the fit to the object's motion;
   }
}

Meaning of Photo's Object Classification

Photo classifies all objects, separately in each band, and also the entire object, into one of the following classes (the photo enum OBJ_TYPE):
OBJ_UNK
An object of unknown type.
OBJ_CR
Object is a cosmic ray.
Not used; instead the OBJECT2_MAYBE_CR bit is set.
OBJ_DEFECT
Not Used
OBJ_GALAXY
Object is classified as a galaxy
OBJ_GHOST
Object is a ghost, either produced by the optics or the electronics.
Not Used; instead the OBJECT2_MAYBE_EGHOST bit is set for electronics bits; optical ghosts are not recognised at this time.
OBJ_KNOWNOBJ
Object came from a known objects catalog, e.g. a FIRST source.
Note: these are not currently created; see PR 42.
OBJ_STAR
Object is classified as a star
OBJ_TRAIL
Object is a satellite/asteroid trail.
Not (yet?) used.
OBJ_SKY
Object was chosen not to overlap with any detected objects; used for positioning sky fibres for spectroscopy (and potentially for studies of the statistical properties of `blank' sky)

Meaning of Photo's mask (`fpM') files

The `bitplanes' in photo's fpM files have the following meanings. The fpM files are not in fact stored as as bitplanes; see the description readAtlasImages code of stand-alone code to convert these files to FITS files; here's the code.

INTERP
pixel's value has been interpolated
SATUR
pixel is/was saturated
NOTCHECKED
pixel was NOT examined for an object
OBJECT
pixel is part of some object
BRIGHTOBJECT
pixel is part of bright object
BINOBJECT
pixel is part of binned object
CATOBJECT
pixel is part of a catalogued object
SUBTRACTED
model has been subtracted from pixel
GHOST
pixel is part of a ghost
CR
pixel is part of a cosmic ray