Observing Operations | Reviews | Survey Management

How the Frames Pipeline Sets its Flags 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

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_.

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

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

1 OBJECT1_BRIGHT
  • For OBJECT1s, indicates that the object was detected as a bright object
  • For OBJCs, indicates that the object was measured as a bright object; it'll have been remeasured later as a faint object, and these measurements are recorded as a sibling of the original, bright, OBJC.

    Internally to the frames pipeline, more objects are detected as bright objects than are measured as such (the balance are used to tweak the astrometry); only those objects measured will have OBJECT1_BRIGHT set.

2 OBJECT1_EDGE
Object is too close to edge of frame in this band. Specifically, at least one of the following is true:
  • At least one pixel above the detection threshold touched the edge of the frame
  • The object was too close to the edge of the frame to be centroided satisfactorily. OBJECT1_PEAKCENTER will also be set.
  • Object was too close to edge of frame to be able to measure a radial profile. In this case, OBJECT1_NOPETRO and OBJECT1_NOPROFILE will also be set, and no further attempt will be made to measure the object.
  • When running the deblender, at least one child in at least one band extends beyond the field.
  • The centre determined in OBJECT1's own band was too close to the edge of the frame, although the canonical centre was not. OBJECT1_CANONICAL_CENTER is also set, and the (transformed) canonical centre is used.

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:
  • More than one peak is detected within an object in a single band together
  • Distinct peaks are found when merging different colours of one object together
  • Distinct peaks result when merging different objects together

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 centre 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:
  • The object had OBJECT1_EDGE set
  • The object had too many peaks; in this case OBJECT1_DEBLEND_TOO_MANY_PEAKS will be set.
  • The object was too large (larger than half a frame); in this case OBJECT1_TOO_LARGE will be set.
  • While attempting to deblend the object, at least one child overlapped the edge of the frame in at least one band. OBJECT1_EDGE will be set.
7 OBJECT1_NOPROFILE
Frames couldn't extract a radial profile. This can be caused by:
  • Object being too close to edge of frame. In this case, OBJECT1_EDGE and OBJECT1_NOPETRO are also set, and the object's properties are not measured
  • There are less than two points in the radial profile. The flags OBJECT1_NOPETRO, OBJECT1_ELLIPFAINT, and OBJECT1_NOSTOKES are set, and Petrosian quantities, Stokes parameters, and isophotal properties are not measured.
8 OBJECT1_NOPETRO
No Petrosian radius or other Petrosian quanties could be measured. This can be caused by:
  • Failing to measure at least two points of the radial profile; OBJECT1_NOPROFILE is also set.
  • The central value of the object being impossibly negative; OBJECT1_BADSKY is set
  • The Petrosian ratio never equals the value at which the Petrosian radius is defined, or the surface brightness when it does reach that value is too low; in the latter case OBJECT1_PETROFAINT is set.

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. 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.
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, centred 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. 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:
  • The object had fewer than two points in its radial profile
  • There were numerical difficulties in calculating either U or Q
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:
  • The object is still detectable at the outermost point of the extracted radial profile (a radius of approximately 260 arcsec)
  • When attempting to deblend an object, at least one child is larger than half a frame (in either row or column). In this case, OBJECT1_NODEBLEND is set, and the attempt to deblend is abandoned.
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:
  • The child is consistent with being a star
  • The child is not detected; in this case the OBJECT1_BINNED1 flags will not be set. Note that this can happen if an object is superimposed on the wings of a brighter object.
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:
  • There are less than two points in the radial profile
  • The object's centre is fainter than desired isophote
  • We were unable to fit an ellipse to the desired isophote, or to the ones a little brighter and fainter used in estimating sensitivity to photometric calibration
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.
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.
4 OBJECT2_STATIONARY
A "moving" object's velocity is consistent with zero
5 OBJECT2_PEAKS_TOO_CLOSE
Peaks in object were too close XXX
6 OBJECT2_MEDIAN_CENTRE
Quoted centre of object is based on a median-smoothed version of image. Not currently set.
7 OBJECT2_LOCAL_EDGE The object's centre 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.
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
moving child's fit was too poor An object containing interpolated pixels had too few good pixels to form a reliable estimate of its error; the quoted error may be underestimated
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).
11 OBJECT2_SATUR_CENTER
An object's centre is very close to at least one saturated pixel; the object may well be causing the saturation.
12 OBJECT2_INTERP_CENTER
An object's centre is very close to at least one interpolated pixel.
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.
14 OBJECT2_DEBLEND_NOPEAK
A child had no detected peak in a given band, but we centroided it anyway and set the BINNED1
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.
The following flag2 bits will never be set in output files, but they are used internally and are therefore included in this document.
28 OBJECT2_MEASURED
This object's properties have been measured.
29 OBJECT2_GROWN_MERGED
Growing this object after it had been detected (maybe as a single pixel over threshold) led to a merger.
30 OBJECT2_HAS_CENTER
This OBJC has a canonical centre.
31 OBJECT2_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(object includes unsearched pixels (e.g. unsmoothed edges of frames)) {
      object1->flags |= OBJECT1_NOTCHECKED;                                [19]
      reject all peaks in NOTCHECKED 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]
      }
   }
}
find_centre
{
   if((object1->flags & OBJECT1_SATUR) && saturated star centroider fails) { [18]
      object1->flags |= OBJECT1_PEAKCENTER;                                [5]
   }
   
   for(;;) {
      find_peak_centre;

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

      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 centre 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
{
      !(object1 & (OBJECT1_CANONICAL_CENTER |                              [0]
                   OBJECT1_PEAKCENTER |                                    [5]
   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_centre {
      foreach object1 in objc {
         if(objc->flags2 & OBJECT2_DEBLENDED_AS_MOVING) {                     [0]
            we already have a good centre;
            continue;
         }

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

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

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

      find_canonical_centre {
         object1 = object1 in canonical band;
	 if(object1->flags & OBJECT1_DETECTED &&                            [32]
            !(object1->flags) & OBJECT1_SATUR) {                            [18]
            use this object1's centre;
         } 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;
      }
   }

   create_object1s {
      foreach (missing OBJECT1) {
	 make object1;
	 if(bright) {
            object1->flags |= OBJECT1_BRIGHT;                              [1]
         }
      	 set centre 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 centre;
      } else {
         This cannot happen, so die;
      }
   }
/*
 * extract profile;
 */
   if(failed to extract radial profile) {
      object1->flags |= OBJECT1_EDGE | OBJECT1_NOPETRO | OBJECT1_NOPROFILE;
      object1->flags2 |= OBJECT2_LOCAL_EDGE;                            [7]
      give up measuring object in this band;                            [2,7,8]
   }

   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;
   }

   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% ||
            (obj1->flags2 & OBJECT2_BAD_COUNTS_ERROR)) {                   [8]
            obj1->flags2 |= OBJECT2_BAD_COUNTS_ERROR;                      [8]
         }
      }
   }
/*
 * Measure quantities about the centre 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 centre;
   }
/*
 * 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 centre
 */
   if(centre of object is within 3 pixels of an interpolated pixel) {
      objc->flags2 &= OBJECT2_INTERP_CENTER;                               [12]
   }
   if(centre 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;
   }
/*
 * propagate flags to and from OBJC
 */
   if(objc->flags & OBJECT1_BLENDED) {                                     [3]
      object1->flags |= OBJECT1_BLENDED;                                   [3]
   }

   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]
}
find_velocity
{
   if(objc->flags2 & OBJECT2_DEBLENDED_AS_MOVING) {                        [0]
      we already have the velocity;
      return;
   }

   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 centre in this band in velocity fit
   }

   if(chisq was too large) {
      objc->flags |= 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 lablelled as MOVED) {
            child->flags |= OBJECT1_MOVED;                                 [31]
         }

         child->flags = objc->flags & (OBJECT1_EDGE |                      [2]
                                       OBJECT1_PEAKCENTER |                [5]
                                       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 centre from peak;
            child_object1->flags |= OBJECT1_BINNED1;                       [28]
         } else {
	    set centre 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;
      } 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;
         }

	 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(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]
      }
   }
}

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(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 centres for all OBJECT1_CANONICAL_CENTER objects                 [0]
      from the fit to the object's motion;
   }
}