Skip to content

Conversation

@kofa73
Copy link
Contributor

@kofa73 kofa73 commented Sep 13, 2025

Checking against the code, the descriptions were wrong.

static int process_bayer(const dt_iop_hotpixels_data_t *data,
                         const void *const ivoid, void *const ovoid,
                         const dt_iop_roi_t *const roi_out)
{
  const float threshold = data->threshold;
  const float multiplier = data->multiplier;
  const gboolean markfixed = data->markfixed;
  const int min_neighbours = data->permissive ? 3 : 4;
  const int width = roi_out->width;
  const int widthx2 = width * 2;
  int fixed = 0;

  DT_OMP_FOR(reduction(+ : fixed))
  for(int row = 2; row < roi_out->height - 2; row++)
  {
    const float *in = (float *)ivoid + (size_t)width * row + 2;
    float *out = (float *)ovoid + (size_t)width * row + 2;
    for(int col = 2; col < width - 2; col++, in++, out++)
    {
      float mid = *in * multiplier;
      if(*in > threshold)
      {
        int count = 0;
        float maxin = 0.0;
        float other;
#define TESTONE(OFFSET)                                                                                      \
  other = in[OFFSET];                                                                                        \
  if(mid > other)                                                                                            \
  {                                                                                                          \
    count++;                                                                                                 \
    if(other > maxin) maxin = other;                                                                         \
  }
        TESTONE(-2);
        TESTONE(-widthx2);
        TESTONE(+2);
        TESTONE(+widthx2);
#undef TESTONE
        if(count >= min_neighbours)
        {
          *out = maxin;
          fixed++;
          if(markfixed)
          {
            for(int i = -2; i >= -10 && i >= -col; i -= 2) out[i] = *in;
            for(int i = 2; i <= 10 && i < width - col; i += 2) out[i] = *in;
          }
        }
      }
    }
  }

  return fixed;
}
  • Multiplier is derived from the 'strength' the user set (d->multiplier = p->strength / 2.0; in commit_params)
  • We loop over the rows, starting with row 2, running until we hit height - 2
  • We run with column from 2 until we hit width - 2
  • Those are done because in a Bayer pattern every 2nd row has the same pattern, e.g. RGRGRG or GBGBGB, so a pixel 2 rows above of below in the same column has the same R/G/B filter, and the same is true from colum -2 and column + 2.
  • We calculate float mid = *in * multiplier -- this is the current pixel multiplied based on strength, and is used as the actual difference threshold, it's not a blending param
  • Then, if the component has a high-enough value (if(*in > threshold)), we process further -- this makes it a canditate, not a hot pixel.
  • We initialise the number of 'darker than mid' neighbours to 0 (int count = 0;)
  • We set float maxin = 0.0; (will track the brightest neighbour's value)
  • We define float other -- this will hold the value of the neighbour we currently check
  • We have a macro to test a neighbour:
       other = in[OFFSET];                                                                                        \
       if(mid > other)                                                                                            \
       {                                                                                                          \
         count++;                                                                                                 \
         if(other > maxin) maxin = other;                                                                         \
       }
  • We test 4 neighbours:
    TESTONE(-2); (left)
    TESTONE(-widthx2); (above)
    TESTONE(+2); (right)
    TESTONE(+widthx2); (below)
  • If 'mid' is > the neighbour (other), we count++, and potentially update maxin if the new value is larger than the ones seen previously
  • if enough neighbours (3 or 4) satisfied the check (so the pixel at *in is sufficiently brighter than its neighbours), we set the pixel to the max. neighbour we found.
  • 'markfixed' is only to visualise the fixed pixels on the GUI, we can ignore that. This is proven by d->markfixed = p->markfixed && (!(pipe->type & (DT_DEV_PIXELPIPE_EXPORT | DT_DEV_PIXELPIPE_THUMBNAIL))); in commit_params (the export and the thumbnail are not affected).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant