CR2RE Pipeline Reference Manual 1.7.0
irplib_slitpos.c
1/*
2 * This file is part of the irplib package
3 * Copyright (C) 2002,2003,2014 European Southern Observatory
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24/* The IRPLIB-based application may have checked for the availability of
25 memrchr() in which case the macro HAVE_DECL_MEMRCHR is defined as either
26 0 or 1. Without checks it is assumed that the function is not available.
27 With a suitable version of autoconf the macro can be defined with this
28 entry in configure.ac:
29 AC_CHECK_DECLS([memrchr])
30*/
31
32/*-----------------------------------------------------------------------------
33 Includes
34 -----------------------------------------------------------------------------*/
35
36#include "irplib_slitpos.h"
37#include "irplib_flat.h"
38
39#include <cpl.h>
40
41#include <string.h>
42#include <math.h>
43#include <assert.h>
44
45/*-----------------------------------------------------------------------------
46 Defines
47 -----------------------------------------------------------------------------*/
48
49#ifndef IRPLIB_SLITPOS_KERNEL_SIZE_Y
50#define IRPLIB_SLITPOS_KERNEL_SIZE_Y 5
51#endif
52
53#ifndef IRPLIB_SLITPOS_MAX_EROSION
54#define IRPLIB_SLITPOS_MAX_EROSION 1024
55#endif
56
57/*-----------------------------------------------------------------------------
58 Functions prototypes
59 -----------------------------------------------------------------------------*/
60
61static cpl_error_code
62irplib_slitpos_find_edges_one_line(const cpl_image *, int, int *, int *);
63static cpl_error_code
64irplib_slitpos_find_vert_slit_ends(const cpl_image *, int, int *, int *);
65static cpl_error_code
66irplib_slitpos_find_vert_pos(const cpl_image *, int, cpl_size *);
67static cpl_error_code irplib_image_filter_background_line(cpl_image *,
68 const cpl_image *,
69 int,
70 cpl_boolean);
71
72/*----------------------------------------------------------------------------*/
76/*----------------------------------------------------------------------------*/
77
79/*----------------------------------------------------------------------------*/
105/*----------------------------------------------------------------------------*/
106cpl_table *
107irplib_slitpos_analysis(const cpl_image *imslit,
108 int slit_max_width,
109 double *slit_flux)
110{
111 const int size_x = cpl_image_get_size_x(imslit);
112 const int size_y = cpl_image_get_size_y(imslit);
113 int slit_length;
114 cpl_size slit_pos;
115 cpl_image *filtered;
116 cpl_mask *mask;
117 cpl_image *thin_im;
118 int slit_top_y = 0; /* Avoid (false) uninit warning */
119 int slit_bot_y = 0; /* Avoid (false) uninit warning */
120 cpl_table *self;
121 double *slit_y, *slit_x_l, *slit_x_r;
122 double *coeff_r;
123 double *coeff_l;
124 int i;
125 cpl_error_code error = CPL_ERROR_NONE;
126
127 /* Initialize */
128 if (slit_flux != NULL)
129 *slit_flux = 0.0;
130
131 /* Median vertical filtering 3x3 */
132 mask = cpl_mask_new(3, 3);
133 cpl_mask_not(mask);
134 filtered = cpl_image_new(size_x, size_y, cpl_image_get_type(imslit));
135 error = cpl_image_filter_mask(filtered, imslit, mask, CPL_FILTER_MEDIAN,
136 CPL_BORDER_FILTER);
137 cpl_mask_delete(mask);
138
139 if (error) {
140 cpl_image_delete(filtered);
141 (void)cpl_error_set_where(cpl_func);
142 return NULL;
143 }
144
145 /* The background may vary strongly along the vertical line. */
146 /* Detect and remove background with a 1+2*Slit_max x 1 median filter */
147 error = irplib_image_filter_background_line(filtered, NULL, slit_max_width,
148 CPL_TRUE);
149
150 if (error) {
151 cpl_image_delete(filtered);
152 (void)cpl_error_set_where(cpl_func);
153 return NULL;
154 }
155
156 /* Find the position of the slit */
157 if (irplib_slitpos_find_vert_pos(filtered, slit_max_width / 2, &slit_pos)) {
158 cpl_image_delete(filtered);
159 cpl_msg_error(cpl_func, "Could not find the slit position");
160 (void)cpl_error_set_where(cpl_func);
161 return NULL;
162 }
163
164 /* Extract a thin image containing the slit */
165 thin_im = cpl_image_extract(filtered, slit_pos - slit_max_width / 2, 1,
166 slit_pos + slit_max_width / 2, size_y);
167 if (thin_im == NULL) {
168 cpl_msg_error(cpl_func,
169 "Could not extract the %d pixel thin image "
170 "around position %" CPL_SIZE_FORMAT,
171 slit_max_width, slit_pos);
172 cpl_image_delete(filtered);
173 (void)cpl_error_set_where(cpl_func);
174 return NULL;
175 }
176
177 /* Find the ends of the slit */
178 error = irplib_slitpos_find_vert_slit_ends(thin_im,
179 IRPLIB_SLITPOS_KERNEL_SIZE_Y,
180 &slit_bot_y, &slit_top_y);
181 cpl_image_delete(thin_im);
182 if (error) {
183 cpl_image_delete(filtered);
184 (void)cpl_error_set_where(cpl_func);
185 return NULL;
186 }
187
188 /* Extract an image with exactly the slit */
189 thin_im =
190 cpl_image_extract(filtered, slit_pos - slit_max_width / 2, slit_bot_y,
191 slit_pos + slit_max_width / 2, slit_top_y);
192 cpl_image_delete(filtered);
193
194 cpl_ensure(thin_im != NULL, cpl_error_get_code(), NULL);
195
196 slit_length = 1 + slit_top_y - slit_bot_y;
197
198 /* Allocate some arrays */
199 slit_y = cpl_malloc(slit_length * sizeof(double));
200 slit_x_l = cpl_malloc(slit_length * sizeof(double));
201 slit_x_r = cpl_malloc(slit_length * sizeof(double));
202
203 /* Find the edges of the slit */
204 for (i = 0; i < slit_length; i++) {
205 int right_pos = 0; /* Avoid (false) uninit warning */
206 int left_pos = 0; /* Avoid (false) uninit warning */
207
208 if (irplib_slitpos_find_edges_one_line(thin_im, i, &left_pos,
209 &right_pos)) {
210 cpl_msg_error(cpl_func, "cannot find the edges of the [%d]th line",
211 i + 1);
212 cpl_image_delete(thin_im);
213 cpl_free(slit_y);
214 cpl_free(slit_x_l);
215 cpl_free(slit_x_r);
216 return NULL;
217 }
218
219 /* Update the slit_flux */
220 if (slit_flux != NULL) {
221 *slit_flux +=
222 cpl_image_get_flux_window(thin_im, left_pos + 1, i + 1,
223 right_pos + 1, i + 1);
224 }
225
226 /* Store the edges for the fit */
227 slit_x_l[i] = (double)left_pos;
228 slit_x_r[i] = (double)right_pos;
229 slit_y[i] = (double)(i + slit_bot_y - 1);
230 }
231 cpl_image_delete(thin_im);
232
233 /* Linear regression to find the edges */
234 coeff_l = irplib_flat_fit_slope_robust(slit_y, slit_x_l, slit_length);
235 coeff_r = irplib_flat_fit_slope_robust(slit_y, slit_x_r, slit_length);
236 cpl_free(slit_y);
237 cpl_free(slit_x_l);
238 cpl_free(slit_x_r);
239
240 /* Allocate the table containing the results */
241 self = cpl_table_new(slit_length);
242 error |= cpl_table_new_column(self, "SLIT_Y", CPL_TYPE_INT);
243 error |= cpl_table_new_column(self, "SLIT_LEFT", CPL_TYPE_DOUBLE);
244 error |= cpl_table_new_column(self, "SLIT_CENTER", CPL_TYPE_DOUBLE);
245 error |= cpl_table_new_column(self, "SLIT_RIGHT", CPL_TYPE_DOUBLE);
246
247 error |= cpl_table_set_column_unit(self, "SLIT_Y", "pixel");
248 error |= cpl_table_set_column_unit(self, "SLIT_LEFT", "pixel");
249 error |= cpl_table_set_column_unit(self, "SLIT_CENTER", "pixel");
250 error |= cpl_table_set_column_unit(self, "SLIT_RIGHT", "pixel");
251
252 cpl_ensure(!error, cpl_error_get_code(), NULL);
253
254 /* Rewrite the edges in the out table, and write the center */
255 for (i = 0; i < slit_length; i++) {
256 const int islity = i + slit_bot_y;
257 const double dslit = slit_pos - slit_max_width / 2.0;
258 const double dleft = coeff_l[0] + coeff_l[1] * (double)islity + dslit;
259 const double dright = coeff_r[0] + coeff_r[1] * (double)islity + dslit;
260 const double dcent = 0.5 * (dleft + dright);
261
262 if (cpl_table_set_int(self, "SLIT_Y", i, islity))
263 break;
264 if (cpl_table_set_double(self, "SLIT_LEFT", i, dleft))
265 break;
266 if (cpl_table_set_double(self, "SLIT_RIGHT", i, dright))
267 break;
268 if (cpl_table_set_double(self, "SLIT_CENTER", i, dcent))
269 break;
270 }
271
272 cpl_free(coeff_r);
273 cpl_free(coeff_l);
274
275 if (i != slit_length) {
276 cpl_table_delete(self);
277 cpl_ensure(0, cpl_error_get_code(), NULL);
278 }
279
280 return self;
281}
282
284
285/*----------------------------------------------------------------------------*/
297/*----------------------------------------------------------------------------*/
298static cpl_error_code
299irplib_slitpos_find_edges_one_line(const cpl_image *self,
300 int line_pos,
301 int *left_pos,
302 int *right_pos)
303{
304 const int size_x = cpl_image_get_size_x(self);
305 const float *pself;
306 double threshold;
307 int i;
308
309 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
310 cpl_ensure_code(cpl_image_get_type(self) == CPL_TYPE_FLOAT,
311 CPL_ERROR_INVALID_TYPE);
312
313 pself = cpl_image_get_data_float_const(self);
314
315 /* Find the threshold */
316 threshold =
317 cpl_image_get_mean_window(self, 1, line_pos + 1, size_x, line_pos + 1);
318
319 /* Detect the left edge */
320 i = 0;
321 while (i < size_x && pself[line_pos * size_x + i] < threshold)
322 i++;
323 *left_pos = i;
324
325 /* Detect the right edge */
326 i = size_x - 1;
327 while (i >= 0 && pself[line_pos * size_x + i] < threshold)
328 i--;
329 *right_pos = i;
330
331 return CPL_ERROR_NONE;
332}
333
334/*----------------------------------------------------------------------------*/
345/*----------------------------------------------------------------------------*/
346static cpl_error_code
347irplib_slitpos_find_vert_slit_ends(const cpl_image *self,
348 int kernel_size,
349 int *bot_slit_y,
350 int *top_slit_y)
351{
352 cpl_mask *binary;
353 cpl_mask *copy = NULL;
354 cpl_mask *kernel;
355 cpl_image *label_image;
356 int erosions_nb;
357 cpl_size nobj;
358 const int size_x = cpl_image_get_size_x(self);
359 const int size_y = cpl_image_get_size_y(self);
360 const int npix = size_x * size_y;
361 const cpl_binary *pbinary;
362 const cpl_binary *pfind;
363 int i, itop, ibot;
364
365
366 cpl_ensure_code(size_x > 0, cpl_error_get_code());
367 cpl_ensure_code(kernel_size > 0, cpl_error_get_code());
368
369 /* Threshold to have a binary image */
370 binary = cpl_mask_threshold_image_create(self, cpl_image_get_mean(self),
371 cpl_image_get_max(self));
372 cpl_ensure_code(binary != NULL, cpl_error_get_code());
373
374 /* Erode until there is 1 object left in the image */
375 label_image = cpl_image_labelise_mask_create(binary, &nobj);
376 cpl_image_delete(label_image);
377
378 if (label_image == NULL) {
379 cpl_mask_delete(binary);
380 cpl_ensure_code(0, cpl_error_get_code());
381 }
382
383 /* Define the kernel for morpho operations */
384 kernel = cpl_mask_new(kernel_size, 1);
385 cpl_mask_not(kernel);
386 copy = cpl_mask_wrap(size_x, size_y,
387 cpl_malloc(size_x * size_y * sizeof(cpl_binary)));
388 for (erosions_nb = 0; erosions_nb < IRPLIB_SLITPOS_MAX_EROSION && nobj > 1;
389 erosions_nb++) {
390 /* Should not be possible to break from this loop */
391 cpl_mask_copy(copy, binary, 1, 1);
392 if (cpl_mask_filter(binary, copy, kernel, CPL_FILTER_EROSION,
393 CPL_BORDER_ZERO))
394 break;
395
396 label_image = cpl_image_labelise_mask_create(binary, &nobj);
397 if (label_image == NULL)
398 break; /* Assuming nobj was not set to 1 */
399 cpl_image_delete(label_image);
400 }
401
402 if (nobj > 1) {
403 cpl_mask_delete(binary);
404 cpl_mask_delete(copy);
405 cpl_mask_delete(kernel);
406 if (erosions_nb >= IRPLIB_SLITPOS_MAX_EROSION) {
407 cpl_msg_error(cpl_func,
408 "Number of erosions reached a limit of %d "
409 "with %" CPL_SIZE_FORMAT " possible slits left",
410 IRPLIB_SLITPOS_MAX_EROSION, nobj);
411 cpl_ensure_code(0, CPL_ERROR_CONTINUE);
412 }
413 cpl_ensure_code(0, cpl_error_get_code());
414 }
415 else if (nobj < 1) {
416 cpl_mask_delete(binary);
417 cpl_mask_delete(copy);
418 cpl_mask_delete(kernel);
419 if (erosions_nb == 0)
420 cpl_msg_error(cpl_func,
421 "No slit could be detected across %d "
422 "pixels",
423 size_x);
424 else
425 cpl_msg_error(cpl_func,
426 "The last of %d erosions removed all the "
427 "possible slits",
428 erosions_nb);
429 cpl_ensure_code(0, CPL_ERROR_DATA_NOT_FOUND);
430 }
431
432 /* Reconstruct the slit with dilations */
433 for (i = 0; i < erosions_nb; i++) {
434 cpl_mask_copy(copy, binary, 1, 1);
435 if (cpl_mask_filter(binary, copy, kernel, CPL_FILTER_DILATION,
436 CPL_BORDER_ZERO))
437 break;
438 }
439 cpl_mask_delete(copy);
440 cpl_mask_delete(kernel);
441
442 if (i != erosions_nb) {
443 cpl_msg_error(cpl_func, "Dilation number %d out of %d failed", i,
444 erosions_nb);
445 cpl_mask_delete(binary);
446 cpl_ensure_code(0, cpl_error_get_code());
447 }
448
449 /* Find the ends of the slit */
450 pbinary = cpl_mask_get_data(binary);
451 assert(pbinary != NULL);
452
453 pfind = memchr(pbinary, CPL_BINARY_1, (size_t)npix);
454 assert(pfind != NULL);
455
456 ibot = (int)(pfind - pbinary);
457
458#if defined HAVE_DECL_MEMRCHR && HAVE_DECL_MEMRCHR == 1
459 /* FIXME: Not tested */
460 pfind = memrchr(pfind, CPL_BINARY_1, (size_t)(npix - ibot));
461 assert(pfind != NULL);
462
463 itop = (int)(pfind - pbinary);
464#else
465
466 itop = npix - 1;
467 while (itop > ibot && pbinary[itop] == CPL_BINARY_0)
468 itop--;
469
470#endif
471
472 *bot_slit_y = 1 + ibot / size_x;
473 *top_slit_y = 1 + itop / size_x;
474
475 cpl_msg_info(cpl_func,
476 "Detected %" CPL_SIZE_FORMAT "-pixel slit from pixel %d to %d "
477 "using %d erosions/dilations",
478 cpl_mask_count(binary), *bot_slit_y, *top_slit_y, erosions_nb);
479
480 cpl_mask_delete(binary);
481
482 /* Should really be an assert() */
483 cpl_ensure_code(ibot <= itop, CPL_ERROR_DATA_NOT_FOUND);
484
485 return CPL_ERROR_NONE;
486}
487
488/*----------------------------------------------------------------------------*/
498/*----------------------------------------------------------------------------*/
499static cpl_error_code
500irplib_slitpos_find_vert_pos(const cpl_image *self,
501 int xwidth,
502 cpl_size *slit_pos)
503{
504 const int size_x = cpl_image_get_size_x(self);
505 cpl_image *image1D;
506 cpl_size yone;
507 cpl_error_code error;
508
509
510 /* Collapse the image to a horizontal 1D image */
511 image1D = cpl_image_collapse_create(self, 0);
512
513 cpl_ensure_code(image1D != NULL, cpl_error_get_code());
514
515 /* Search the max of the 1D image to identify the slit position */
516 error = cpl_image_get_maxpos_window(image1D, 1 + xwidth, 1, size_x - xwidth,
517 1, slit_pos, &yone);
518
519 cpl_image_delete(image1D);
520
521 cpl_ensure_code(!error, error);
522
523 return CPL_ERROR_NONE;
524}
525
526/*----------------------------------------------------------------------------*/
540/*----------------------------------------------------------------------------*/
541static cpl_error_code
542irplib_image_filter_background_line(cpl_image *self,
543 const cpl_image *other,
544 int hsize,
545 cpl_boolean vertical)
546{
547 const int nx = cpl_image_get_size_x(self);
548 const int ny = cpl_image_get_size_y(self);
549 const int msize = 1 + 2 * hsize;
550 cpl_mask *mask;
551 cpl_image *background;
552 cpl_error_code error = CPL_ERROR_NONE;
553
554 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
555 cpl_ensure_code(hsize >= 0, CPL_ERROR_ILLEGAL_INPUT);
556
557 if (other == NULL)
558 other = self;
559
560 mask = vertical ? cpl_mask_new(msize, 1) : cpl_mask_new(1, msize);
561
562 error |= cpl_mask_not(mask);
563
564 background = cpl_image_new(nx, ny, cpl_image_get_type(other));
565
566 error |= cpl_image_filter_mask(background, other, mask, CPL_FILTER_MEDIAN,
567 CPL_BORDER_FILTER);
568 cpl_mask_delete(mask);
569
570 if (self != other) {
571 error |= cpl_image_copy(self, other, 1, 1);
572 }
573
574 error |= cpl_image_subtract(self, background);
575 cpl_image_delete(background);
576
577 return error ? cpl_error_set_where(cpl_func) : CPL_ERROR_NONE;
578}
double * irplib_flat_fit_slope_robust(double *x, double *y, int np)
Fit a slope to a list of points (robust fit).
cpl_table * irplib_slitpos_analysis(const cpl_image *imslit, int slit_max_width, double *slit_flux)
Detect the slit position, detect its ends, extract a thin image containing only the slit and find its...