IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Changeset 16624


Ignore:
Timestamp:
Feb 22, 2008, 4:33:59 PM (18 years ago)
Author:
Paul Price
Message:

Expanding options for combination, especially for when there's a low
number of pixels in the stack. e.g., if "safe" is on, then a single
pixel won't get through. If "useVariance" is on, then use the
variance maps to help identify discrepant pixels (rather than the
standard deviation of the pixel values).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/psModules/src/imcombine/pmStack.c

    r16608 r16624  
    88 *  @author GLG, MHPCC
    99 *
    10  *  @version $Revision: 1.20 $ $Name: not supported by cvs2svn $
    11  *  @date $Date: 2008-02-22 20:05:17 $
     10 *  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
     11 *  @date $Date: 2008-02-23 02:33:59 $
    1212 *  Copyright 2004-2007 Institute for Astronomy, University of Hawaii
    1313 *
     
    3434#define MASK_BAD 0x01                   // Mask value for pixels that have formerly been masked as bad
    3535#define MASK_SUSPECT 0x02               // Mask value for pixels that are suspected bad (by pmStackCombine)
     36
     37#define NUM_DIRECT_STDEV 5              // For less than this number of values, measure stdev directly
    3638
    3739
     
    4244    psVector *masks;                    // Pixel masks
    4345    psVector *weights;                  // Pixel weights
     46    psVector *sources;                  // Pixel sources (which image did they come from?)
    4447    psVector *sort;                     // Buffer for sorting (to get a robust estimator of the standard dev)
    4548} combineBuffer;
     
    5053    psFree(buffer->masks);
    5154    psFree(buffer->weights);
     55    psFree(buffer->sources);
    5256    psFree(buffer->sort);
    5357    return;
     
    6468    buffer->masks = psVectorAlloc(numImages, PS_TYPE_MASK);
    6569    buffer->weights = psVectorAlloc(numImages, PS_TYPE_F32);
     70    buffer->sources = psVectorAlloc(numImages, PS_TYPE_U16);
    6671    buffer->sort = psVectorAlloc(numImages, PS_TYPE_F32);
    6772    return buffer;
     
    110115}
    111116
    112 // Generate a standard deviation for the combination
     117// Return the median and standard deviation for the pixels
    113118// Not using psVectorStats because it has additional allocations which slow things down
    114 static bool combinationStdev(float *median, // Median value, to return
    115                              float *stdev, // Standard deviation value, to return
    116                              const psVector *values, // Values to combine
    117                              const psVector *masks, // Mask to apply
    118                              psMaskType maskVal, // Value to mask
    119                              psVector *sortBuffer // Buffer for sorting
    120                              )
     119static bool combinationMedianStdev(float *median, // Median value, to return
     120                                   float *stdev, // Standard deviation value, to return
     121                                   const psVector *values, // Values to combine
     122                                   const psVector *masks, // Mask to apply
     123                                   psMaskType maskVal, // Value to mask
     124                                   psVector *sortBuffer // Buffer for sorting
     125                                   )
    121126{
    122127    assert(values && masks);
     
    126131    assert(sortBuffer && sortBuffer->nalloc >= values->n && sortBuffer->type.type == PS_TYPE_F32);
    127132
     133    // Need to filter out clipped values
    128134    int num = 0;            // Number of valid values
    129135    for (int i = 0; i < values->n; i++) {
    130         if (!(masks->data.PS_TYPE_MASK_DATA[i] & maskVal)) {
     136        if (!(masks->data.PS_TYPE_MASK_DATA[i])) {
    131137            sortBuffer->data.F32[num++] = values->data.F32[i];
    132138        }
     
    136142        return false;
    137143    }
    138     *median = num % 2 ? sortBuffer->data.F32[num / 2] :
    139         (sortBuffer->data.F32[num / 2] + sortBuffer->data.F32[num / 2 + 1]) / 2.0 ;
    140 #if 0
    141     if (num < NUM_DIRECT_STDEV) {
    142 #endif
    143         // If there are not many values, the direct standard deviation is better
    144         double sum = 0.0;
    145         for (int i = 0; i < num; i++) {
    146             sum += PS_SQR(sortBuffer->data.F32[i] - *median);
    147         }
    148         *stdev = sqrt(sum / (float)(num - 1));
    149 #if 0
     144
     145    if (num == 3) {
     146        // Attempt to measure standard deviation with only three values (and one of those possibly corrupted)
     147        *median = sortBuffer->data.F32[1];
     148        if (stdev) {
     149            float diff1 = sortBuffer->data.F32[0] - *median;
     150            float diff2 = sortBuffer->data.F32[2] - *median;
     151            // This factor of sqrt(2) might not be exact, but it's about right
     152            *stdev = M_SQRT2 * PS_MIN(fabsf(diff1), fabsf(diff2));
     153        }
    150154    } else {
    151         *stdev = 0.74 * (sortBuffer->data.F32[(int)(0.75 * num)] - sortBuffer->data.F32[(int)(0.25 * num)]);
    152     }
    153 #endif
     155        *median = num % 2 ? sortBuffer->data.F32[num / 2] :
     156            (sortBuffer->data.F32[num / 2] + sortBuffer->data.F32[num / 2 + 1]) / 2.0;
     157        if (stdev) {
     158            if (num <= NUM_DIRECT_STDEV) {
     159                // If there are not many values, the direct standard deviation is better
     160                double sum = 0.0;
     161                for (int i = 0; i < num; i++) {
     162                    sum += PS_SQR(sortBuffer->data.F32[i] - *median);
     163                }
     164                *stdev = sqrt(sum / (float)(num - 1));
     165            } else {
     166                // Standard deviation from the interquartile range
     167                *stdev = 0.74 * (sortBuffer->data.F32[(int)(0.75 * num)] -
     168                                 sortBuffer->data.F32[(int)(0.25 * num)]);
     169            }
     170        }
     171    }
    154172
    155173    return true;
     
    198216}
    199217
     218// Common path for a bad combined pixel
     219static inline bool combineBad(psImage *image, // Combined image
     220                              psImage *mask, // Combined mask
     221                              psImage *weight, // Combined variance map
     222                              int x, int y, // Coordinates of interest
     223                              psMaskType bad // Value for bad pixels
     224                              )
     225{
     226    image->data.F32[y][x] = NAN;
     227    if (weight) {
     228        weight->data.F32[y][x] = NAN;
     229    }
     230    mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
     231    return false;
     232}
     233
     234// Mark a pixel for inspection
     235static inline void combineInspect(const psArray *inputs, // Stack data
     236                                  int x, int y, // Pixel
     237                                  int source // Source image index
     238                                  )
     239{
     240    pmStackData *data = inputs->data[source]; // Stack data of interest
     241    if (!data) {
     242        return;
     243    }
     244    data->pixels = psPixelsAdd(data->pixels, PIXEL_LIST_BUFFER, x, y);
     245    return;
     246}
     247
    200248// Given a stack of images, combine with optional rejection.
    201249// Pixels in the stack that are rejected are marked for subsequent inspection
    202 static bool combinePixels(psImage *image, // Combined image, for output
     250static void combinePixels(psImage *image, // Combined image, for output
    203251                          psImage *mask, // Combined mask, for output
    204252                          psImage *weight, // Combined variance map, for output
     
    211259                          int numIter, // Number of rejection iterations
    212260                          float rej, // Number of standard deviations at which to reject
     261                          bool useVariance, // Use variance for rejection when combining?
     262                          bool safe,    // Combine safely?
    213263                          combineBuffer *buffer // Buffer for combination; to avoid multiple allocations
    214264                         )
     
    221271    assert(rej > 0);
    222272    assert(buffer);
    223 
    224     int num = inputs->n;          // Number of images to combine
     273    assert((useVariance && weight) || !useVariance);
    225274
    226275    psVector *pixelData = buffer->pixels; // Values for the pixel of interest
    227276    psVector *pixelMasks = buffer->masks; // Masks for the pixel of interest
    228277    psVector *pixelWeights = buffer->weights; // Weights for the pixel of interest
     278    psVector *pixelSources = buffer->sources; // Sources for the pixel of interest
    229279    psVector *sort = buffer->sort;      // Sort buffer
    230280
    231281    // Extract the pixel and mask data
    232     int numBad = 0;                     // Number of bad pixels
    233     for (int i = 0; i < num; i++) {
     282    int num = 0;                        // Number of good images
     283    for (int i = 0, j = 0; i < inputs->n; i++) {
     284        // Check if this pixel has been rejected.  Assumes that the rejection pixel list is sorted --- it
     285        // should be because of how pixelMapGenerate works
     286        if (reject && reject->data.U16[j] == i) {
     287            j++;
     288            continue;
     289        }
     290
    234291        pmStackData *data = inputs->data[i]; // Stack data of interest
    235292        if (!data) {
    236293            continue;
    237294        }
     295
    238296        int xIn = x - data->readout->col0, yIn = y - data->readout->row0; // Coordinates on input readout
     297        psImage *mask = data->readout->mask; // Mask of interest
     298        if (mask->data.PS_TYPE_MASK_DATA[yIn][xIn] & maskVal) {
     299            continue;
     300        }
     301
    239302        psImage *image = data->readout->image; // Image of interest
    240303        psImage *weight = data->readout->weight; // Weight map of interest
    241         psImage *mask = data->readout->mask; // Mask of interest
    242         pixelData->data.F32[i] = image->data.F32[yIn][xIn];
     304        pixelData->data.F32[num] = image->data.F32[yIn][xIn];
    243305        if (weight) {
    244             pixelWeights->data.F32[i] = weight->data.F32[yIn][xIn];
    245         }
    246         pixelMasks->data.PS_TYPE_MASK_DATA[i] = mask->data.PS_TYPE_MASK_DATA[yIn][xIn];
    247         if (pixelMasks->data.PS_TYPE_MASK_DATA[i] & maskVal) {
    248             numBad++;
    249         }
    250     }
    251     if (numBad == num) {
    252         // Catch the case where everything is masked
    253         image->data.F32[y][x] = NAN;
    254         if (weight) {
    255             weight->data.F32[y][x] = NAN;
    256         }
    257         mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
    258         return false;
    259     }
    260 
    261     if (reject) {
    262         for (int i = 0; i < reject->n; i++) {
    263             pixelMasks->data.PS_TYPE_MASK_DATA[reject->data.U16[i]] |= maskVal;
    264         }
    265     }
    266 
    267     // Record the value derived with no clipping, because pixels rejected using the harsh clipping applied in
    268     // the first pass might later be accepted.
    269     float mean, variance;               // Mean and variance of the combination
    270     if (!combinationMean(&mean, pixelData, weights, pixelMasks, maskVal) ||
    271         (weight && !combinationVariance(&variance, pixelWeights, weights, pixelMasks, maskVal))) {
    272         image->data.F32[y][x] = NAN;
    273             mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
    274         if (weight) {
    275             weight->data.F32[y][x] = NAN;
    276         }
    277         return false;
    278     }
    279 
    280     image->data.F32[y][x] = mean;
     306            pixelWeights->data.F32[num] = weight->data.F32[yIn][xIn];
     307        }
     308        pixelSources->data.U16[num] = i;
     309        num++;
     310    }
     311    pixelData->n = num;
    281312    if (weight) {
    282         weight->data.F32[y][x] = variance;
    283     }
    284     mask->data.PS_TYPE_MASK_DATA[y][x] = 0;
    285 
    286     // The clipping that follows is solely to identify suspect pixels.
    287     // These suspect pixels will be inspected in more detail by other functions.
    288     long numClipped = LONG_MAX;         // Number of pixels clipped
    289     psMaskType ignore = maskVal | bad;  // Ignore values with this mask value
    290     for (int i = 0; i < numIter && numClipped > 0; i++) {
    291         float median, stdev;                    // Median and stdev of the combination, for rejection
    292         // Only get the mean if it's not the first iteration (because we got the mean earlier)
    293         if (!combinationStdev(&median, &stdev, pixelData, pixelMasks, maskVal, sort)) {
    294             image->data.F32[y][x] = NAN;
    295             mask->data.PS_TYPE_MASK_DATA[y][x] = bad;
    296             weight->data.F32[y][x] = NAN;
    297             return false;
    298         }
    299 
    300         float limit = rej * stdev; // Rejection limit
    301         numClipped = 0;
    302         for (int j = 0; j < num; j++) {
    303             if (pixelMasks->data.PS_TYPE_MASK_DATA[j] & ignore) {
    304                 continue;
    305             }
    306             float diff = pixelData->data.F32[j] - median;
    307             if (fabsf(diff) > limit) {
    308                 // Add the pixel as one to inspect
    309                 pmStackData *data = inputs->data[j]; // Stack data of interest
    310                 if (!data) {
    311                     continue;
    312                 }
    313                 data->pixels = psPixelsAdd(data->pixels, PIXEL_LIST_BUFFER, x, y);
    314                 // Mask it so it's not considered in other iterations within this function
    315                 pixelMasks->data.PS_TYPE_MASK_DATA[j] |= bad;
    316                 numClipped++;
    317             }
    318         }
    319     }
    320 
    321     return true;
     313        pixelWeights->n = num;
     314    }
     315    pixelSources->n = num;
     316
     317    // The sensible thing to do varies according to how many good pixels there are.
     318    // Default option is that the pixel is bad
     319    float imageValue = NAN, varianceValue = NAN; // Value for combined image and variance map
     320    psMaskType maskValue = bad;         // Value for combined mask
     321    switch (num) {
     322      case 0:
     323        // Nothing to combine: it's bad
     324        break;
     325      case 1: {
     326          // Accept the single pixel unless we have to be safe
     327          if (!safe) {
     328              imageValue = pixelData->data.F32[0];
     329              if (weight) {
     330                  varianceValue = pixelWeights->data.F32[0];
     331              }
     332              maskValue = 0;
     333          }
     334          break;
     335      }
     336      case 2: {
     337          // Accept the mean of the pixels only if we're going to reject based on the variance, or we're not
     338          // playing safe
     339          if (useVariance || !safe) {
     340              float mean, variance;   // Mean and variance from combination
     341              if (combinationMean(&mean, pixelData, weights, pixelMasks, maskVal) &&
     342                  (!weight || combinationVariance(&variance, pixelWeights, weights, pixelMasks, maskVal))) {
     343                  imageValue = mean;
     344                  varianceValue = variance;
     345                  maskValue = 0;
     346              }
     347          }
     348          if (useVariance) {
     349              // Use variance to check that the two are consistent
     350              float diff = pixelData->data.F32[0] - pixelData->data.F32[1];
     351              float sigma = sqrtf(pixelWeights->data.F32[0] + pixelWeights->data.F32[1]);
     352              if (fabs(diff) > rej * sigma) {
     353                  // Not consistent: mark both for inspection
     354                  combineInspect(inputs, x, y, pixelSources->data.U16[0]);
     355                  combineInspect(inputs, x, y, pixelSources->data.U16[1]);
     356              }
     357          }
     358          break;
     359      }
     360      default: {
     361          // Record the value derived with no clipping, because pixels rejected using the harsh clipping
     362          // applied in the first pass might later be accepted.
     363          float mean, variance;           // Mean and variance of the combination
     364          if (!combinationMean(&mean, pixelData, weights, pixelMasks, maskVal) ||
     365              (weight && !combinationVariance(&variance, pixelWeights, weights, pixelMasks, maskVal))) {
     366              break;
     367          }
     368          imageValue = mean;
     369          varianceValue = variance;
     370          maskValue = 0;
     371
     372          // Prepare for clipping iteration
     373          if (numIter > 0) {
     374              pixelMasks->n = num;
     375              psVectorInit(pixelMasks, 0);
     376              if (useVariance) {
     377                  // Convert to rejection limits
     378                  for (int i = 0; i < num; i++) {
     379                      pixelWeights->data.F32[i] = rej * sqrtf(pixelWeights->data.F32[i]);
     380                  }
     381              }
     382          }
     383
     384          // The clipping that follows is solely to identify suspect pixels.
     385          // These suspect pixels will be inspected in more detail by other functions.
     386          int numClipped = INT_MAX;       // Number of pixels clipped per iteration
     387          int totalClipped = 0;           // Total number of pixels clipped
     388          for (int i = 0; i < numIter && numClipped > 0 && num - totalClipped > 2; i++) {
     389              numClipped = 0;
     390              float median, stdev;    // Median and stdev of the combination, for rejection
     391
     392              if (!combinationMedianStdev(&median, useVariance ? NULL : &stdev, pixelData, pixelMasks,
     393                                          maskVal, sort)) {
     394                  psWarning("Bad median/stdev at %d,%d", x, y);
     395                  break;
     396              }
     397
     398              float limit;                // Rejection limit, when rejecting based on data properties
     399              if (!useVariance) {
     400                  limit = rej * stdev;
     401              }
     402
     403              for (int j = 0; j < num; j++) {
     404                  if (pixelMasks->data.PS_TYPE_MASK_DATA[j]) {
     405                      continue;
     406                  }
     407                  float diff = fabsf(pixelData->data.F32[j] - median); // Difference from expected
     408                  if (diff > (useVariance ? pixelWeights->data.F32[i] : limit)) {
     409                      pixelMasks->data.PS_TYPE_MASK_DATA[j] = 0xff;
     410                      combineInspect(inputs, x, y, pixelSources->data.U16[j]);
     411                      numClipped++;
     412                      totalClipped++;
     413                  }
     414              }
     415          }
     416          break;
     417      }
     418    }
     419
     420    return;
    322421}
    323422
     
    549648/// Stack input images
    550649bool pmStackCombine(pmReadout *combined, psArray *input, psMaskType maskVal, psMaskType bad,
    551                     int numIter, float rej)
     650                    int numIter, float rej, bool useVariance, bool safe)
    552651{
    553652    PS_ASSERT_PTR_NON_NULL(combined, false);
     
    570669        PS_ASSERT_IMAGES_SIZE_EQUAL(combined->image, combined->mask, false);
    571670    }
     671    if (useVariance && !haveWeights) {
     672        psWarning("Unable to use variance in rejection if no variance maps supplied --- option turned off");
     673        useVariance = false;
     674    }
    572675
    573676    // Pull out the weightings
     
    629732            psVector *reject = pixelMapQuery(pixelMap, x, y); // Inspect these images closely
    630733            combinePixels(combinedImage, combinedMask, combinedWeight, input, weights, reject, x, y,
    631                           maskVal, bad, numIter, rej, buffer);
     734                          maskVal, bad, numIter, rej, useVariance, safe, buffer);
    632735        }
    633736        psTrace("psModules.imcombine", 5, "Additional %ld pixels fixed.\n", pixels->n);
     
    667770            for (int x = minInputCols; x < maxInputCols; x++) {
    668771                combinePixels(combinedImage, combinedMask, combinedWeights, input, weights, NULL, x, y,
    669                               maskVal, bad, numIter, rej, buffer);
     772                              maskVal, bad, numIter, rej, useVariance, safe, buffer);
    670773            }
    671774        }
Note: See TracChangeset for help on using the changeset viewer.