DETMON Pipeline Reference Manual  1.2.4
irplib_distortion.c
1 /* $Id: irplib_distortion.c,v 1.52 2013/01/29 08:43:33 jtaylor Exp $
2  *
3  * This file is part of the irplib package
4  * Copyright (C) 2002,2003 European Southern Observatory
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307 USA
19  */
20 
21 /*
22  * $Author: jtaylor $
23  * $Date: 2013/01/29 08:43:33 $
24  * $Revision: 1.52 $
25  * $Name: detmon-1_2_4 $
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 
32 /*-----------------------------------------------------------------------------
33  Includes
34  -----------------------------------------------------------------------------*/
35 
36 /* TEMPORARY SUPPORT OF CPL 5.x */
37 #include <cpl.h>
38 
39 #ifndef CPL_SIZE_FORMAT
40 #define CPL_SIZE_FORMAT "d"
41 #define cpl_size int
42 #endif
43 
44 #if defined CPL_VERSION_CODE && CPL_VERSION_CODE <= CPL_VERSION(5, 92, 0)
45 #define cpl_apertures_get_pos_x cpl_apertures_get_max_x
46 #endif
47 
48 /* END TEMPORARY SUPPORT OF CPL 5.x */
49 
50 #include "irplib_distortion.h"
51 
52 #include "irplib_flat.h"
53 #include "irplib_utils.h"
54 #include "irplib_polynomial.h"
55 
56 #include <math.h>
57 #include <float.h>
58 
59 /*-----------------------------------------------------------------------------
60  Define
61  -----------------------------------------------------------------------------*/
62 
63 #define IRPLIB_MAX(A,B) ((A) > (B) ? (A) : (B))
64 #define IRPLIB_MIN(A,B) ((A) < (B) ? (A) : (B))
65 
66 #define ARC_MINGOODPIX 100
67 #define ARC_MINARCLENFACT 2.0
68 #define ARC_MINNBARCS 4
69 #define ARC_RANGE_FACT 3.0
70 #define ARC_WINDOWSIZE 32
71 
72 #define TRESH_MEDIAN_MIN 0.0
73 #define TRESH_SIGMA_MAX 200.0
74 
75 /*----------------------------------------------------------------------------*/
79 /*----------------------------------------------------------------------------*/
80 
81 /*-----------------------------------------------------------------------------
82  Functions prototypes
83  -----------------------------------------------------------------------------*/
84 
85 static cpl_apertures * irplib_distortion_detect_arcs(cpl_image *,
86  cpl_image **, int, int, double, int, int, int, int);
87 static cpl_error_code irplib_distortion_fill_border(cpl_image *, int, int,
88  int, int, double);
89 static int irplib_distortion_threshold1d(cpl_image *, double, cpl_image *,
90  double);
91 static cpl_error_code irplib_distortion_purge_arcs(cpl_apertures **, cpl_image *,
92  const cpl_image *, int, int,
93  double);
94 static cpl_error_code irplib_distortion_fill_arc_positions(cpl_bivector *,
95  cpl_vector *,
96  const cpl_image *,
97  const cpl_image *,
98  const cpl_apertures *);
99 
100 static double irplib_distortion_get_row_centroid(const cpl_image *,
101  const cpl_image *, int, int);
102 
103 static int irplib_distortion_sub_hor_lowpass(cpl_image *, int);
104 static cpl_image * irplib_distortion_remove_ramp(const cpl_image *);
105 
106 static cpl_error_code irplib_image_filter_background_line(cpl_image *,
107  const cpl_image *, int, cpl_boolean) ;
108 
109 static cpl_error_code irplib_polynomial_fit_2d(cpl_polynomial *,
110  const cpl_bivector *,
111  const cpl_vector *, int,
112  double, double *);
113 
114 static cpl_matrix * irplib_matrix_product_normal_create(const cpl_matrix *);
115 
116 /*-----------------------------------------------------------------------------
117  Functions code
118  -----------------------------------------------------------------------------*/
119 
122 /*----------------------------------------------------------------------------*/
151 /*----------------------------------------------------------------------------*/
152 cpl_polynomial * irplib_distortion_estimate(
153  const cpl_image * org,
154  int xmin,
155  int ymin,
156  int xmax,
157  int ymax,
158  int auto_ramp_sub,
159  int arc_sat,
160  int max_arc_width,
161  double kappa,
162  int degree,
163  cpl_apertures ** arcs)
164 {
165  cpl_image * local_im;
166  cpl_image * filtered;
167  cpl_image * label_image;
168  double rightmost, leftmost;
169  cpl_bivector * grid;
170  cpl_vector * values_to_fit;
171  int n_arcs;
172  cpl_polynomial * poly2d;
173  double mse = 0.0;
174  const int nx = cpl_image_get_size_x(org);
175  const int ny = cpl_image_get_size_y(org);
176  const int min_arc_range = (int)(nx / ARC_RANGE_FACT);
177  int i;
178 
179  /* Check entries */
180  cpl_ensure(org != NULL, CPL_ERROR_NULL_INPUT, NULL);
181  cpl_ensure(kappa >= 0.0, CPL_ERROR_ILLEGAL_INPUT, NULL);
182  cpl_ensure(max_arc_width > 0, CPL_ERROR_ILLEGAL_INPUT, NULL);
183 
184  /* The background may vary strongly along the vertical line. */
185  /* Detect and rm background with a 1+2*max_arc_width x 1 median filter */
186 
187  filtered = cpl_image_new(nx, ny, cpl_image_get_type(org));
188 
189  irplib_image_filter_background_line(filtered, org, max_arc_width, CPL_TRUE);
190 
191  if (auto_ramp_sub) {
192  local_im = irplib_distortion_remove_ramp(filtered);
193  cpl_image_delete(filtered);
194  } else {
195  local_im = filtered;
196  }
197 
198  cpl_error_ensure(local_im != NULL, cpl_error_get_code(),
199  return(NULL), "Cannot clean the image");
200 
201  /* Detect the arcs in the input image */
202  *arcs = irplib_distortion_detect_arcs(local_im, &label_image, arc_sat,
203  max_arc_width, kappa, xmin, ymin,
204  xmax, ymax);
205  if (*arcs == NULL) {
206  cpl_image_delete(local_im);
207  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
208  "Cannot detect the arcs");
209  return NULL;
210  }
211  n_arcs = cpl_apertures_get_size(*arcs);
212  cpl_msg_info(cpl_func, "%d detected arcs", n_arcs);
213 
214  /* Check that the arcs are not concentrated in the same zone */
215  rightmost = leftmost = cpl_apertures_get_pos_x(*arcs, 1);
216  for (i=1; i<n_arcs; i++) {
217  if (cpl_apertures_get_pos_x(*arcs, i+1) < leftmost)
218  leftmost = cpl_apertures_get_pos_x(*arcs, i+1);
219  if (cpl_apertures_get_pos_x(*arcs, i+1) > rightmost)
220  rightmost = cpl_apertures_get_pos_x(*arcs, i+1);
221  }
222  if ((int)(rightmost-leftmost) < min_arc_range) {
223 #if defined CPL_HAVE_VA_ARGS && CPL_HAVE_VA_ARGS != 0
224  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
225  "too narrow range (%g-%g)<%d",
226  rightmost, leftmost, min_arc_range);
227 #else
228  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
229  "too narrow range");
230 #endif
231  cpl_apertures_delete(*arcs);
232  cpl_image_delete(local_im);
233  cpl_image_delete(label_image);
234  *arcs = NULL;
235  return NULL;
236  }
237 
238  /* Create a 2-D deformation grid with detected arcs */
239  cpl_msg_info(cpl_func, "Create deformation grid");
240  grid = cpl_bivector_new(n_arcs * ny);
241  values_to_fit = cpl_vector_new(n_arcs * ny);
242 
243  if (irplib_distortion_fill_arc_positions(grid, values_to_fit, local_im,
244  label_image, *arcs)){
245  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
246  "cannot get arcs positions");
247  cpl_apertures_delete(*arcs);
248  cpl_image_delete(local_im);
249  cpl_image_delete(label_image);
250  *arcs = NULL;
251  return NULL;
252  }
253  cpl_image_delete(label_image);
254  cpl_image_delete(local_im);
255 
256  /* Apply the fitting */
257  poly2d = cpl_polynomial_new(2);
258  if (irplib_polynomial_fit_2d(poly2d, grid, values_to_fit, degree,
259  0.5*(ny+1), &mse)) {
260  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
261  "cannot apply the 2d fit");
262  cpl_bivector_delete(grid);
263  cpl_vector_delete(values_to_fit);
264  cpl_apertures_delete(*arcs);
265  *arcs = NULL;
266  return NULL;
267  }
268 
269  cpl_msg_info(cpl_func,
270  "Fitted a %d. degree 2D-polynomial to %"CPL_SIZE_FORMAT" points "
271  "with mean-square error: %g", degree,
272  cpl_vector_get_size(values_to_fit), mse);
273 
274  /* Free and return */
275  cpl_bivector_delete(grid);
276  cpl_vector_delete(values_to_fit);
277  return poly2d;
278 }
279 
282 /*----------------------------------------------------------------------------*/
298 /*----------------------------------------------------------------------------*/
299 static cpl_apertures * irplib_distortion_detect_arcs(
300  cpl_image * im,
301  cpl_image ** label_im,
302  int arc_sat,
303  int max_arc_width,
304  double kappa,
305  int xmin,
306  int ymin,
307  int xmax,
308  int ymax)
309 {
310  const int ny = cpl_image_get_size_y(im);
311  /* Set min_arclen */
312  const int min_arclen = (int)(ny / ARC_MINARCLENFACT);
313  cpl_image * filt_im;
314  cpl_mask * filter;
315  cpl_image * collapsed;
316  cpl_mask * bin_im;
317  double threshold, fillval, median_val, sigma;
318  cpl_apertures * det;
319  cpl_size nobj;
320  int ngoodpix;
321 
322  /* Default values for output parameters */
323  *label_im = NULL;
324 
325  /* Clear zones to be ignored (to avoid false detections) */
326  median_val = cpl_image_get_median_dev(im, &sigma);
327  fillval = median_val-sigma/2.0;
328  if (irplib_distortion_fill_border(im, xmin, ymin, xmax, ymax, fillval)) {
329  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
330  "cannot fill bad zones");
331  return NULL;
332  }
333 
334  /* Subtract a low-pass */
335  filt_im = cpl_image_duplicate(im);
336  if (irplib_distortion_sub_hor_lowpass(filt_im, ARC_WINDOWSIZE) == -1) {
337  cpl_image_delete(filt_im);
338  return NULL;
339  }
340 
341  /* Get relevant stats for thresholding */
342  median_val = cpl_image_get_median_dev(filt_im, &sigma);
343 
344  /* Correct median_val and sigma if necessary */
345  if (median_val < TRESH_MEDIAN_MIN) median_val = TRESH_MEDIAN_MIN;
346  if (sigma > TRESH_SIGMA_MAX) sigma = TRESH_SIGMA_MAX;
347 
348  /* Set the threshold */
349  threshold = median_val + sigma * kappa;
350 
351  /* Collapse the image */
352  collapsed = cpl_image_collapse_median_create(filt_im, 0, 0, 0);
353 
354  /* Threshold to keep only the arcs - use of the collapsed image */
355  if (irplib_distortion_threshold1d(filt_im, median_val, collapsed, 0.0)==-1) {
356  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
357  "cannot threshold the filtered image");
358  cpl_image_delete(filt_im);
359  cpl_image_delete(collapsed);
360  return NULL;
361  }
362  cpl_image_delete(collapsed);
363 
364  /* Binarize the image */
365  bin_im = cpl_mask_threshold_image_create(filt_im, threshold,
366  DBL_MAX);
367  cpl_image_delete(filt_im);
368  if (bin_im == NULL) {
369  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
370  "cannot binarise the image");
371  return NULL;
372  }
373 
374  /* Test if there are enough good pixels */
375  ngoodpix = cpl_mask_count(bin_im);
376  if (ngoodpix < ARC_MINGOODPIX) {
377 #if defined CPL_HAVE_VA_ARGS && CPL_HAVE_VA_ARGS != 0
378  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
379  "Too few (%d) white pixels", ngoodpix);
380 #else
381  cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
382  "Too few white pixels");
383 #endif
384  cpl_mask_delete(bin_im);
385  return NULL;
386  }
387 
388  /* Apply a morphological opening to clean the isolated pixels */
389  filter = cpl_mask_new(3, 3);
390  cpl_mask_not(filter);
391  cpl_mask_filter(bin_im, bin_im, filter, CPL_FILTER_OPENING,
392  CPL_BORDER_ZERO);
393  cpl_mask_delete(filter);
394 
395  /* Labelize pixel map to a label image */
396  *label_im = cpl_image_labelise_mask_create(bin_im, &nobj);
397  cpl_mask_delete(bin_im);
398 
399  /* Compute statistics on objects */
400  if ((det = cpl_apertures_new_from_image(im, *label_im)) == NULL) {
401  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
402  "Cannot compute arcs stats");
403  cpl_image_delete(*label_im);
404  *label_im = NULL;
405  return NULL;
406  }
407 
408  /* Purge non-relevant arcs */
409  if (irplib_distortion_purge_arcs(&det, *label_im, im, min_arclen,
410  max_arc_width, arc_sat)) {
411  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
412  "Cannot purge the arcs");
413  cpl_image_delete(*label_im);
414  *label_im = NULL;
415  cpl_apertures_delete(det);
416  return NULL;
417  }
418  if (cpl_apertures_get_size(det) < ARC_MINNBARCS) {
419 #if defined CPL_HAVE_VA_ARGS && CPL_HAVE_VA_ARGS != 0
420  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
421  "Not enough valid arcs (%"CPL_SIZE_FORMAT" < %d)",
422  cpl_apertures_get_size(det), ARC_MINNBARCS);
423 #else
424  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
425  "Not enough valid arcs, min="
426  IRPLIB_STRINGIFY(ARC_MINNBARCS));
427 #endif
428  cpl_image_delete(*label_im);
429  *label_im = NULL;
430  cpl_apertures_delete(det);
431  return NULL;
432  }
433 
434  /* Return */
435  return det;
436 }
437 
438 /*----------------------------------------------------------------------------*/
448 /*----------------------------------------------------------------------------*/
449 static cpl_error_code irplib_distortion_fill_border(cpl_image * self,
450  int xmin,
451  int ymin,
452  int xmax,
453  int ymax,
454  double fillval)
455 {
456  const int nx = cpl_image_get_size_x(self);
457  const int ny = cpl_image_get_size_y(self);
458  float * pfi = cpl_image_get_data_float(self);
459  const float fvalue = (float)fillval;
460  int i, j;
461 
462 
463  cpl_ensure_code(pfi != NULL, cpl_error_get_code());
464 
465  /* Ensure validity of pixel buffer access */
466  xmin = IRPLIB_MIN(xmin, nx+1);
467  ymax = IRPLIB_MIN(ymax, ny);
468 
469  /* - and avoid double access */
470  xmax = IRPLIB_MAX(xmax, xmin - 1);
471  ymin = IRPLIB_MIN(ymin, ymax + 1);
472 
473  /* Fill the zone */
474 
475  for (j = 0; j < ymin-1; j++) {
476  for (i = 0; i < nx; i++) {
477  pfi[i+j*nx] = fvalue;
478  }
479  }
480  /* assert( j == IRPLIB_MAX(0, ymin-1) ); */
481 
482  for (; j < ymax; j++) {
483  for (i = 0; i < xmin-1; i++) {
484  pfi[i+j*nx] = fvalue;
485  }
486  for (i = xmax; i < nx; i++) {
487  pfi[i+j*nx] = fvalue;
488  }
489  }
490  /* assert( j == IRPLIB_MAX(0, ymax) ); */
491 
492  for (; j < ny; j++) {
493  for (i = 0; i < nx; i++) {
494  pfi[i+j*nx] = fvalue;
495  }
496  }
497 
498  return CPL_ERROR_NONE;
499 }
500 
501 static int irplib_distortion_threshold1d(
502  cpl_image * im,
503  double threshold,
504  cpl_image * im1d,
505  double newval)
506 {
507  float * pim;
508  float * pim1d;
509  int nx, ny;
510  int i, j;
511 
512  /* Check entries */
513  if (im == NULL) return -1;
514  if (im1d == NULL) return -1;
515  if (cpl_image_get_type(im) != CPL_TYPE_FLOAT) return -1;
516  if (cpl_image_get_type(im1d) != CPL_TYPE_FLOAT) return -1;
517 
518  /* Get access to the im / im1d data */
519  pim = cpl_image_get_data_float(im);
520  pim1d = cpl_image_get_data_float(im1d);
521  nx = cpl_image_get_size_x(im);
522  ny = cpl_image_get_size_y(im);
523 
524  /* Apply the thresholding */
525  for (i=0; i<nx; i++)
526  if (pim1d[i] < threshold) {
527  for (j=0; j<ny; j++) pim[i+j*nx] = (float)newval;
528  }
529 
530  /* Return */
531  return 0;
532 }
533 
534 static int irplib_distortion_sub_hor_lowpass(
535  cpl_image * im,
536  int filt_size)
537 {
538  cpl_vector * linehi;
539  cpl_vector * linelo;
540  cpl_vector * avglinehi;
541  cpl_vector * avglinelo;
542  double * pavglinehi;
543  float * pim;
544  int lopos, hipos, nx, ny;
545  int i, j;
546 
547  /* Test entries */
548  if (im == NULL) return -1;
549  if (filt_size <= 0) return -1;
550 
551  /* Initialise */
552  nx = cpl_image_get_size_x(im);
553  ny = cpl_image_get_size_y(im);
554  lopos = (int)(ny/4);
555  hipos = (int)(3*ny/4);
556 
557  /* Get the vectors out of the image */
558  if ((linehi = cpl_vector_new_from_image_row(im, hipos)) == NULL) {
559  return -1;
560  }
561  if ((linelo = cpl_vector_new_from_image_row(im, lopos)) == NULL) {
562  cpl_vector_delete(linehi);
563  return -1;
564  }
565 
566  /* Filter the vectors */
567  if ((avglinehi = cpl_vector_filter_median_create(linehi,
568  filt_size)) == NULL) {
569  cpl_vector_delete(linehi);
570  cpl_vector_delete(linelo);
571  return -1;
572  }
573  cpl_vector_delete(linehi);
574 
575  if ((avglinelo = cpl_vector_filter_median_create(linelo,
576  filt_size)) == NULL) {
577  cpl_vector_delete(linelo);
578  cpl_vector_delete(avglinehi);
579  return -1;
580  }
581  cpl_vector_delete(linelo);
582 
583  /* Average the filtered vectors to get the low freq signal */
584  cpl_vector_add(avglinehi, avglinelo);
585  cpl_vector_delete(avglinelo);
586  cpl_vector_divide_scalar(avglinehi, 2.0);
587 
588  /* Subtract the low frequency signal */
589  pavglinehi = cpl_vector_get_data(avglinehi);
590  pim = cpl_image_get_data_float(im);
591  for (i=0; i<nx; i++) {
592  for (j=0; j<ny; j++) {
593  pim[i+j*nx] -= pavglinehi[i];
594  }
595  }
596  cpl_vector_delete(avglinehi);
597 
598  return 0;
599 }
600 
601 /*----------------------------------------------------------------------------*/
612 /*----------------------------------------------------------------------------*/
613 static
614 cpl_error_code irplib_distortion_purge_arcs(cpl_apertures ** self,
615  cpl_image * lab_im,
616  const cpl_image * arc_im,
617  int min_arclen,
618  int max_arcwidth,
619  double arc_sat)
620 {
621  const double ycenter = 0.5 * (1 + cpl_image_get_size_y(arc_im));
622  int narcs;
623  int nkeep = 0;
624  int ifirst = 1;
625  int * relabel;
626  int i;
627 
628  /* Check entries */
629  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
630 
631  /* Get number of arcs */
632  narcs = cpl_apertures_get_size(*self);
633 
634  cpl_ensure_code(narcs > 0, CPL_ERROR_DATA_NOT_FOUND);
635  cpl_ensure_code(cpl_image_get_type(lab_im) == CPL_TYPE_INT,
636  CPL_ERROR_ILLEGAL_INPUT);
637 
638  /* Allocate relabel array with default relabelling to zero */
639  relabel = cpl_calloc(narcs, sizeof(int));
640 
641  /* Loop on the different arcs candidates */
642  for (i = 0; i < narcs; i++) {
643  /* Test if the current object is a valid arc */
644  const int arclen = 1
645  + cpl_apertures_get_top(*self, i+1)
646  - cpl_apertures_get_bottom(*self, i+1);
647 
648  if (cpl_apertures_get_top(*self, i+1) < ycenter) continue;
649  if (cpl_apertures_get_bottom(*self, i+1) > ycenter) continue;
650 
651  if (arclen > min_arclen) {
652  const int arcwidth = 1
653  + cpl_apertures_get_right(*self, i+1)
654  - cpl_apertures_get_left(*self, i+1);
655  if (arcwidth < max_arcwidth) {
656  const int edge = cpl_apertures_get_left_y(*self, i+1);
657  if (edge > 0) {
658  const double mean = cpl_apertures_get_mean(*self, i+1);
659  if (mean < arc_sat) {
660  relabel[i] = ++nkeep;
661  /* Relabeling, if any, starts with ifirst */
662  if (nkeep == i+1) ifirst = nkeep;
663  }
664  }
665  }
666  }
667  }
668 
669  if (nkeep < narcs) {
670  /* Update the labelised image by erasing non valid arcs */
671  int * plabim = cpl_image_get_data_int(lab_im);
672  const int npix = cpl_image_get_size_x(lab_im)
673  * cpl_image_get_size_y(lab_im);
674 
675  if (nkeep == 0) {
676  cpl_free(relabel);
677 #if defined CPL_HAVE_VA_ARGS && CPL_HAVE_VA_ARGS != 0
678  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
679  "All %d arc(s) are invalid", narcs);
680 #else
681  return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
682  "All arcs are invalid");
683 #endif
684  }
685 
686  for (i = 0; i < npix; i++) {
687  const int label = plabim[i];
688 
689  if (label < 0 || label > narcs) break;
690  if (label >= ifirst) plabim[i] = relabel[label-1];
691  }
692 
693  if (i < npix) {
694  /* lab_im is not a valid label image */
695  cpl_free(relabel);
696  return cpl_error_set(cpl_func, plabim[i] < 0
697  ? CPL_ERROR_ILLEGAL_INPUT
698  : CPL_ERROR_INCOMPATIBLE_INPUT);
699  }
700 
701  /* Purge the bad arcs */
702  cpl_apertures_delete(*self);
703  *self = cpl_apertures_new_from_image(arc_im, lab_im);
704 
705  }
706 
707  cpl_free(relabel);
708 
709  cpl_msg_info(cpl_func, "Purged %d of %d arcs (1st purged=%d)", narcs - nkeep,
710  narcs, ifirst);
711 
712  /* arc_im may be invalid */
713  cpl_ensure_code(*self != NULL, cpl_error_get_code());
714 
715  return CPL_ERROR_NONE;
716 }
717 
718 
719 /*----------------------------------------------------------------------------*/
733 /*----------------------------------------------------------------------------*/
734 static cpl_error_code
735 irplib_distortion_fill_arc_positions(cpl_bivector * grid,
736  cpl_vector * fitvalues,
737  const cpl_image * in,
738  const cpl_image * label_im,
739  const cpl_apertures * det)
740 {
741  const int narcs = cpl_apertures_get_size(det);
742  int nfitvals = cpl_vector_get_size(fitvalues);
743  const int nx = cpl_image_get_size_x(label_im);
744  const int ny = cpl_image_get_size_y(label_im);
745  cpl_image * filt_img;
746  cpl_mask * kernel;
747  cpl_vector * gridx = cpl_bivector_get_x(grid);
748  cpl_vector * gridy = cpl_bivector_get_y(grid);
749  cpl_polynomial* dist1d;
750  cpl_matrix * dist1dx = NULL;
751  cpl_vector * dist1dy = NULL;
752  double * dgridx;
753  double * dgridy;
754  double * dfitv;
755  int ndone = 0;
756  int i, obj;
757 
758  cpl_ensure_code(nfitvals > 0, CPL_ERROR_DATA_NOT_FOUND);
759  cpl_ensure_code(narcs > 0, CPL_ERROR_DATA_NOT_FOUND);
760  cpl_ensure_code(cpl_image_get_type(label_im) == CPL_TYPE_INT,
761  CPL_ERROR_TYPE_MISMATCH);
762 
763  /* Ensure space for output */
764  if (nfitvals < narcs * ny) {
765  nfitvals = narcs * ny;
766  cpl_vector_set_size(fitvalues, nfitvals);
767  }
768  if (cpl_vector_get_size(gridx) < nfitvals ||
769  cpl_vector_get_size(gridy) < nfitvals) {
770  cpl_vector_set_size(gridx, nfitvals);
771  cpl_vector_set_size(gridy, nfitvals);
772  }
773 
774  /* Get data after resizing */
775  dgridx = cpl_vector_get_data(gridx);
776  dgridy = cpl_vector_get_data(gridy);
777  dfitv = cpl_vector_get_data(fitvalues);
778 
779  /* Median filter on input image */
780  kernel = cpl_mask_new(3, 3);
781  cpl_mask_not(kernel);
782  filt_img = cpl_image_new(nx, ny, cpl_image_get_type(in));
783  cpl_image_filter_mask(filt_img, in, kernel, CPL_FILTER_MEDIAN,
784  CPL_BORDER_FILTER);
785  cpl_mask_delete(kernel);
786 
787  dist1d = cpl_polynomial_new(1);
788 
789  for (obj = 0; obj < narcs; obj++) {
790  /* Find the reference X-coordinate for the arc */
791  const int * plabel_im = cpl_image_get_data_int_const(label_im);
792  const int ndist1d = cpl_apertures_get_top(det, obj+1)
793  - cpl_apertures_get_bottom(det, obj+1) + 1;
794  cpl_boolean sampsym = CPL_TRUE;
795  int j, prevj = 0;
796  int k = 0;
797 
798  (void)cpl_matrix_unwrap(dist1dx);
799  (void)cpl_vector_unwrap(dist1dy);
800  dist1dx = cpl_matrix_wrap(1, ndist1d, dgridy + ndone);
801  dist1dy = cpl_vector_wrap(ndist1d, dfitv + ndone);
802 
803  /* Find out the X coord. at all Y positions on the arc */
804 
805  for (j = cpl_apertures_get_bottom(det, obj+1)-1;
806  j < cpl_apertures_get_top(det, obj+1); j++) {
807 
808  for (i = 0; i < nx; i++) {
809  if (plabel_im[i + j * nx] == obj + 1) break;
810  }
811  if (i < nx) {
812  /* Found 1st pixel of aperture obj+1 in row j+1 */
813  cpl_errorstate prestate = cpl_errorstate_get();
814 
815  const double x_finepos
816  = irplib_distortion_get_row_centroid(filt_img, label_im,
817  i, j);
818  if (!cpl_errorstate_is_equal(prestate)) {
819  irplib_error_recover(prestate, "Could not find X-position "
820  "for line %d at y=%d (x=%d)",
821  obj+1, j+1, i+1);
822  } else if (x_finepos >= 0.0) {
823  cpl_matrix_set(dist1dx, 0, k, 1.0 + j);
824  cpl_vector_set(dist1dy, k, 1.0 + x_finepos);
825  if (k > 0 && j != 1 + prevj) sampsym = CPL_FALSE;
826  prevj = j;
827  k++;
828  }
829  }
830  }
831  if (k > 0) {
832  double ref_xpos, grad;
833  cpl_error_code error;
834  const cpl_boolean did_drop = k != ndist1d;
835  const cpl_size mindeg = 0;
836  const cpl_size maxdeg = 2;
837 
838  if (did_drop) {
839  /* Set correct size */
840  dist1dx = cpl_matrix_wrap(1, k, cpl_matrix_unwrap(dist1dx));
841  dist1dy = cpl_vector_wrap(k, cpl_vector_unwrap(dist1dy));
842  }
843 
844  error = cpl_polynomial_fit(dist1d, dist1dx, &sampsym, dist1dy, NULL,
845  CPL_FALSE, &mindeg, &maxdeg);
846  if (error) {
847  cpl_msg_error(cpl_func, "1D-fit failed");
848  break;
849  }
850 
851  ref_xpos = cpl_polynomial_eval_1d(dist1d, 0.5 * (ny + 1), &grad);
852 
853  for (j = cpl_apertures_get_bottom(det, obj+1)-1;
854  j < cpl_apertures_get_top(det, obj+1); j++) {
855  const double xpos = cpl_polynomial_eval_1d(dist1d, j+1.0, NULL);
856 
857  dfitv [ndone] = xpos;
858  dgridx[ndone] = ref_xpos;
859  /* Wrapping dist1dx does _not_ take care of dgridy,
860  in case of "Could not find X-position " */
861  if (did_drop)
862  dgridy[ndone] = 1.0 + j;
863  ndone++;
864  }
865  cpl_msg_info(cpl_func, "Line %d has center gradient %g", obj+1,
866  grad);
867  }
868  }
869 
870  cpl_image_delete(filt_img);
871  cpl_polynomial_delete(dist1d);
872  (void)cpl_matrix_unwrap(dist1dx);
873  (void)cpl_vector_unwrap(dist1dy);
874 
875  cpl_msg_info(cpl_func, "Found %d fitting points ("
876  "expected up to %d points)", ndone, nfitvals);
877 
878  cpl_ensure_code(obj == narcs, cpl_error_get_code());
879 
880  cpl_ensure_code(ndone > 0, CPL_ERROR_DATA_NOT_FOUND);
881 
882  cpl_vector_set_size(fitvalues, ndone);
883  cpl_vector_set_size(gridx, ndone);
884  cpl_vector_set_size(gridy, ndone);
885 
886  return CPL_ERROR_NONE;
887 }
888 
889 /*----------------------------------------------------------------------------*/
899 /*----------------------------------------------------------------------------*/
900 static double irplib_distortion_get_row_centroid(const cpl_image * im,
901  const cpl_image * label_im,
902  int x,
903  int y)
904 {
905  const int nx = cpl_image_get_size_x(im);
906  const int ny = cpl_image_get_size_y(im);
907  const int ynx = y * nx;
908  const float * pim = cpl_image_get_data_float_const(im);
909  const int * plabel_im = cpl_image_get_data_int_const(label_im);
910  int firstpos = -1;
911  int lastpos = -1;
912  int maxpos = x;
913  int objnum;
914  double wsum = 0.0;
915  double sum = 0.0;
916  double max = 0.0;
917 
918  cpl_ensure(pim != NULL, cpl_error_get_code(), -1.0);
919  cpl_ensure(plabel_im != NULL, cpl_error_get_code(), -2.0);
920  cpl_ensure(x >= 0, CPL_ERROR_ILLEGAL_INPUT, -3.0);
921  cpl_ensure(y >= 0, CPL_ERROR_ILLEGAL_INPUT, -4.0);
922  cpl_ensure(x < nx, CPL_ERROR_ILLEGAL_INPUT, -5.0);
923  cpl_ensure(y < ny, CPL_ERROR_ILLEGAL_INPUT, -6.0);
924 
925  max = (double)pim[x + ynx];
926  objnum = plabel_im[x + ynx];
927 
928  /* While we stay in the same object... */
929  do {
930  const double val = (double)pim[x + ynx];
931 
932  if (val > 0.0) { /* FIXME: Handle this exception better */
933  wsum += x * val;
934  sum += val;
935 
936  if (firstpos < 0) firstpos = x;
937  lastpos = x;
938 
939  if (val > max) {
940  max = val;
941  maxpos = x;
942  }
943  }
944 
945 
946  /* Next point */
947  x++;
948 
949  } while (x < nx && objnum == plabel_im[x + ynx]);
950 
951  cpl_ensure(sum > 0.0, CPL_ERROR_DATA_NOT_FOUND, -7.0);
952 
953  /*
954  assert( 0 <= maxpos && maxpos < nx );
955  assert( objnum == plabel_im[maxpos + ynx] );
956  assert( wsum >= 0.0 );
957  */
958 
959  return (wsum < sum * firstpos || wsum > sum * lastpos)
960  ? maxpos : wsum / sum;
961 }
962 
963 /*----------------------------------------------------------------------------*/
969 /*----------------------------------------------------------------------------*/
970 #define IS_NB_TESTPOINTS 8
971 #define IS_MIN_SLOPE 0.01
972 #define IS_MAX_SLOPE_DIF 0.075
973 #define IS_MAX_FIT_EDGE_DIF 0.05
974 #define IS_MIN_RAMP 10.0
975 #define IS_MAX_MNERR 13.0
976 #define IS_MAX_MNERR_DIF 8.0
977 #define IS_MAX_INTER_DIF 20.0
978 #define IS_SKIPZONE 2.5
979 #define SQR(x) ((x)*(x))
980 static cpl_image * irplib_distortion_remove_ramp(const cpl_image * in)
981 {
982  int ramp_present;
983  const int nx = cpl_image_get_size_x(in);
984  const int ny = cpl_image_get_size_y(in);
985  const int yhi = (int)(ny/2);
986  const int ylo = yhi - 1;
987  int y;
988  cpl_vector * tmp_vector;
989  cpl_bivector * testpointlo;
990  double * testpointlo_x;
991  double * testpointlo_y;
992  cpl_bivector * testpointhi;
993  double * testpointhi_x;
994  double * testpointhi_y;
995  const int spacing = ny / (IS_SKIPZONE*IS_NB_TESTPOINTS);
996  double rampdif, fitslope;
997  double * pol_coefhi,
998  * pol_coeflo;
999  cpl_vector * median;
1000  double * median_data;
1001  double medianerrlo, medianerrhi;
1002  double slope;
1003  cpl_image * out;
1004  float * pout;
1005  float val;
1006  int i, j;
1007 
1008  cpl_ensure(cpl_image_get_type(in) == CPL_TYPE_FLOAT,
1009  CPL_ERROR_UNSUPPORTED_MODE, NULL);
1010 
1011  if (ny < IS_SKIPZONE * IS_NB_TESTPOINTS){
1012 #if defined CPL_HAVE_VA_ARGS && CPL_HAVE_VA_ARGS != 0
1013  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1014  "image has %d lines, min="
1015  IRPLIB_STRINGIFY(IS_SKIPZONE) "*"
1016  IRPLIB_STRINGIFY(IS_NB_TESTPOINTS), ny);
1017 #else
1018  cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1019  "image has too few lines, min="
1020  IRPLIB_STRINGIFY(IS_SKIPZONE) "*"
1021  IRPLIB_STRINGIFY(IS_NB_TESTPOINTS));
1022 #endif
1023  return NULL;
1024  }
1025 
1026  /* Fill the vectors */
1027  testpointhi = cpl_bivector_new(IS_NB_TESTPOINTS);
1028  testpointhi_x = cpl_bivector_get_x_data(testpointhi);
1029  testpointhi_y = cpl_bivector_get_y_data(testpointhi);
1030  testpointlo = cpl_bivector_new(IS_NB_TESTPOINTS);
1031  testpointlo_x = cpl_bivector_get_x_data(testpointlo);
1032  testpointlo_y = cpl_bivector_get_y_data(testpointlo);
1033  for (i=0; i<IS_NB_TESTPOINTS; i++) {
1034  y = yhi + i * spacing;
1035  tmp_vector = cpl_vector_new_from_image_row(in, y+1);
1036  testpointhi_x[i] = y - ny / 2;
1037  testpointhi_y[i] = cpl_vector_get_median_const(tmp_vector);
1038  cpl_vector_delete(tmp_vector);
1039  y = ylo - i * spacing;
1040  tmp_vector = cpl_vector_new_from_image_row(in, y+1);
1041  testpointlo_x[IS_NB_TESTPOINTS-i-1] = y;
1042  testpointlo_y[IS_NB_TESTPOINTS-i-1]=cpl_vector_get_median_const(tmp_vector);
1043  cpl_vector_delete(tmp_vector);
1044  }
1045 
1046  /* Apply the fit */
1047  pol_coefhi = irplib_flat_fit_slope_robust(testpointhi_x,
1048  testpointhi_y, IS_NB_TESTPOINTS);
1049  pol_coeflo = irplib_flat_fit_slope_robust(testpointlo_x,
1050  testpointlo_y, IS_NB_TESTPOINTS);
1051 
1052  /* Compute the errors */
1053  median = cpl_vector_new(IS_NB_TESTPOINTS);
1054  median_data = cpl_vector_get_data(median);
1055  for (i=0; i<IS_NB_TESTPOINTS; i++) {
1056  median_data[i]=SQR(testpointhi_y[i]
1057  - pol_coefhi[0] - pol_coefhi[1] * testpointhi_x[i]);
1058  }
1059  medianerrhi = cpl_vector_get_median(median);
1060  for (i=0; i<IS_NB_TESTPOINTS; i++) {
1061  median_data[i]=SQR(testpointlo_y[i]
1062  - pol_coeflo[0] - pol_coeflo[1] * testpointlo_x[i]);
1063  }
1064  medianerrlo = cpl_vector_get_median(median);
1065  cpl_vector_delete(median);
1066  rampdif = testpointlo_y[IS_NB_TESTPOINTS-1] - testpointhi_y[0];
1067  slope = rampdif / (ny/2.0);
1068  fitslope = (pol_coefhi[1] + pol_coeflo[1]) / 2.0;
1069 
1070  cpl_bivector_delete(testpointlo);
1071  cpl_bivector_delete(testpointhi);
1072 
1073  /* Decide if there is a ramp or not */
1074  if (fabs(rampdif)<IS_MIN_RAMP ||
1075  fabs(pol_coefhi[1]) < IS_MIN_SLOPE ||
1076  fabs(pol_coeflo[1]) < IS_MIN_SLOPE ||
1077  pol_coefhi[1]/pol_coeflo[1]<0.5 ||
1078  pol_coefhi[1]/pol_coeflo[1]>2.0 ||
1079  fabs(pol_coefhi[1]-pol_coeflo[1])>IS_MAX_SLOPE_DIF ||
1080  fabs(pol_coefhi[0]-pol_coeflo[0]) > IS_MAX_INTER_DIF ||
1081  medianerrlo> IS_MAX_MNERR ||
1082  medianerrhi> IS_MAX_MNERR ||
1083  fabs(medianerrlo-medianerrhi) >IS_MAX_MNERR_DIF ||
1084  fabs(slope-fitslope) > IS_MAX_FIT_EDGE_DIF ||
1085  slope/fitslope<0.5 ||
1086  slope/fitslope>2.0) ramp_present = 0;
1087  else ramp_present = 1;
1088 
1089  cpl_free(pol_coeflo);
1090  cpl_free(pol_coefhi);
1091 
1092  /* Correct the ramp if it is there */
1093  out = cpl_image_duplicate(in);
1094  pout = cpl_image_get_data_float(out);
1095  if (ramp_present == 1) {
1096  for (j=0; j<ny/2; j++) {
1097  val = slope * (j-ny/2);
1098  for (i=0; i<nx; i++)
1099  pout[i+j*nx] -= val;
1100  }
1101  for (j=ny/2; j<ny; j++) {
1102  val = slope * (j-ny);
1103  for (i=0; i<nx; i++)
1104  pout[i+j*nx] -= val;
1105  }
1106 
1107  }
1108 
1109  return out;
1110 }
1111 
1112 /*----------------------------------------------------------------------------*/
1126 /*----------------------------------------------------------------------------*/
1127 static cpl_error_code irplib_image_filter_background_line(cpl_image * self,
1128  const cpl_image * other,
1129  int hsize,
1130  cpl_boolean vertical)
1131 {
1132  const int nx = cpl_image_get_size_x(self);
1133  const int ny = cpl_image_get_size_y(self);
1134  const int msize = 1 + 2 * hsize;
1135  cpl_mask * mask;
1136  cpl_image * background;
1137  cpl_error_code error = CPL_ERROR_NONE;
1138 
1139  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1140  cpl_ensure_code(hsize >= 0, CPL_ERROR_ILLEGAL_INPUT);
1141 
1142  if (other == NULL) other = self;
1143 
1144  mask = vertical ? cpl_mask_new(msize, 1) : cpl_mask_new(1, msize);
1145 
1146  error |= cpl_mask_not(mask);
1147 
1148  background = cpl_image_new(nx, ny, cpl_image_get_type(other));
1149 
1150  error |= cpl_image_filter_mask(background, other, mask, CPL_FILTER_MEDIAN,
1151  CPL_BORDER_FILTER);
1152  cpl_mask_delete(mask);
1153 
1154  if (self != other) {
1155  error |= cpl_image_copy(self, other, 1, 1);
1156  }
1157 
1158  error |= cpl_image_subtract(self, background);
1159  cpl_image_delete(background);
1160 
1161  return error ? cpl_error_set_where(cpl_func) : CPL_ERROR_NONE;
1162 }
1163 
1164 
1165 
1191 static cpl_matrix * irplib_matrix_product_normal_create(const cpl_matrix * self)
1192 {
1193 
1194  double sum;
1195  cpl_matrix * product;
1196  const double * ai = cpl_matrix_get_data_const(self);
1197  const double * aj;
1198  double * bwrite;
1199  const int m = cpl_matrix_get_nrow(self);
1200  const int n = cpl_matrix_get_ncol(self);
1201  int i, j, k;
1202 
1203 
1204  cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
1205 
1206 #if 0
1207  /* Initialize all values to zero.
1208  This is done to avoid access of uninitilized memory, in case
1209  someone passes the matrix to for example cpl_matrix_dump(). */
1210  product = cpl_matrix_new(m, m);
1211  bwrite = cpl_matrix_get_data(product);
1212 #else
1213  bwrite = (double *) cpl_malloc(m * m * sizeof(double));
1214  product = cpl_matrix_wrap(m, m, bwrite);
1215 #endif
1216 
1217  /* The result at (i,j) is the dot-product of i'th and j'th row */
1218  for (i = 0; i < m; i++, bwrite += m, ai += n) {
1219  aj = ai; /* aj points to first entry in j'th row */
1220  for (j = i; j < m; j++, aj += n) {
1221  sum = 0.0;
1222  for (k = 0; k < n; k++) {
1223  sum += ai[k] * aj[k];
1224  }
1225  bwrite[j] = sum;
1226  }
1227  }
1228 
1229  return product;
1230 
1231 }
1232 
1233 /*----------------------------------------------------------------------------*/
1247 /*----------------------------------------------------------------------------*/
1248 static cpl_error_code irplib_polynomial_fit_2d(cpl_polynomial * self,
1249  const cpl_bivector * xy_pos,
1250  const cpl_vector * values,
1251  int degree, double fixy,
1252  double * mse)
1253 {
1254 
1255  const int np = cpl_bivector_get_size(xy_pos);
1256  /* Number of unknowns to determine in one dimension */
1257  const int nc1 = 1+degree;
1258  /* Number of unknowns to determine */
1259  /* P_{i,0} = 0, except P_{1,0} = 1 */
1260  const int nc = nc1 * (1 + nc1) / 2 - nc1;
1261  cpl_matrix * mv; /* The transpose of the Vandermonde matrix */
1262  cpl_matrix * mh; /* Block-Hankel matrix, V'*V */
1263  cpl_matrix * mb;
1264  cpl_matrix * mx;
1265 #ifdef IRPLIB_DISTORTION_ASSERT
1266  const double * coeffs1d;
1267 #endif
1268  double * dmv;
1269  cpl_vector * xhat;
1270  cpl_vector * yhat;
1271  cpl_vector * zhat;
1272  cpl_size powers[2];
1273  int degx, degy;
1274  int i, j;
1275  cpl_error_code error;
1276 
1277 
1278  cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1279  cpl_ensure_code(cpl_polynomial_get_dimension(self) == 2,
1280  CPL_ERROR_INVALID_TYPE);
1281  cpl_ensure_code(np > 0, cpl_error_get_code());
1282  cpl_ensure_code(values != NULL, CPL_ERROR_NULL_INPUT);
1283 
1284  cpl_ensure_code(cpl_vector_get_size(values) == np,
1285  CPL_ERROR_INCOMPATIBLE_INPUT);
1286 
1287  cpl_ensure_code(degree > 0, CPL_ERROR_ILLEGAL_INPUT);
1288  cpl_ensure_code(np >= nc, CPL_ERROR_DATA_NOT_FOUND);
1289 
1290  /* transform zero-point to fixy */
1291  yhat = cpl_vector_duplicate(cpl_bivector_get_y_const(xy_pos));
1292  cpl_vector_subtract_scalar(yhat, fixy);
1293 
1294  /* - and ensure P(y) = y on center line */
1295  xhat = cpl_vector_duplicate(cpl_bivector_get_x_const(xy_pos));
1296  zhat = cpl_vector_duplicate(values);
1297  cpl_vector_subtract(zhat, xhat);
1298 
1299  /* Initialize matrices */
1300  /* mv contains the polynomial terms in the order described */
1301  /* above in each row, for each input point. */
1302  dmv = (double*)cpl_malloc(nc*np*sizeof(double));
1303  mv = cpl_matrix_wrap(nc, np, dmv);
1304 
1305  /* Has redundant FLOPs, appears to improve accuracy */
1306  for (i=0; i < np; i++) {
1307  const double x = cpl_vector_get(xhat, i);
1308  const double y = cpl_vector_get(yhat, i);
1309  double xvalue;
1310  double yvalue = y;
1311  j = 0;
1312  for (degy = 1; degy <= degree; degy++) {
1313  xvalue = 1;
1314  for (degx = 0; degx <= degree-degy; degx++, j++) {
1315  dmv[np * j + i] = xvalue * yvalue;
1316  xvalue *= x;
1317  }
1318  yvalue *= y;
1319  }
1320  /* cx_assert( j == nc ); */
1321  }
1322  cpl_vector_delete(xhat);
1323  cpl_vector_delete(yhat);
1324 
1325  /* mb contains the values, it is not modified */
1326  mb = cpl_matrix_wrap(np, 1, cpl_vector_get_data(zhat));
1327 
1328  /* Form the right hand side of the normal equations */
1329  mx = cpl_matrix_product_create(mv, mb);
1330 
1331  cpl_matrix_unwrap(mb);
1332  cpl_vector_delete(zhat);
1333 
1334  /* Form the matrix of the normal equations */
1335  mh = irplib_matrix_product_normal_create(mv);
1336  cpl_matrix_delete(mv);
1337 
1338  /* Solve XA=B by a least-square solution (aka pseudo-inverse). */
1339  error = cpl_matrix_decomp_chol(mh) || cpl_matrix_solve_chol(mh, mx);
1340 
1341  cpl_matrix_delete(mh);
1342 
1343  if (error) {
1344  cpl_matrix_delete(mx);
1345  cpl_ensure_code(0, error);
1346  }
1347 
1348  /* Store coefficients for output */
1349 
1350 #ifdef IRPLIB_DISTORTION_ASSERT
1351  coeffs1d = cpl_matrix_get_data(mx);
1352 #endif
1353 
1354  j = 0;
1355  for (degy = 1; degy <= degree; degy++) {
1356  powers[1] = degy;
1357  for (degx = 0; degx <= degree-degy; degx++, j++) {
1358  powers[0] = degx;
1359  /* cx_assert( coeffs1d[j] == cpl_matrix_get(mx, j, 0) ); */
1360  cpl_polynomial_set_coeff(self, powers, cpl_matrix_get(mx, j, 0));
1361  }
1362  }
1363  /* cx_assert( j == nc ); */
1364 
1365  cpl_matrix_delete(mx);
1366 
1367  /* P_{1,0} = 1 */
1368  powers[0] = 1;
1369  powers[1] = 0;
1370  cpl_polynomial_set_coeff(self, powers, 1.0);
1371 
1372  /* Transform the polynomial back in Y */
1373  cpl_polynomial_shift_1d(self, 1, -fixy);
1374 
1375  /* If requested, compute mean squared error */
1376  if (mse != NULL) {
1377  const cpl_vector * x_pos = cpl_bivector_get_x_const(xy_pos);
1378  const cpl_vector * y_pos = cpl_bivector_get_y_const(xy_pos);
1379  cpl_vector * x_val = cpl_vector_new(2);
1380  double residue;
1381 
1382  *mse = 0;
1383  for (i=0; i<np; i++) {
1384  cpl_vector_set(x_val, 0, cpl_vector_get(x_pos, i));
1385  cpl_vector_set(x_val, 1, cpl_vector_get(y_pos, i));
1386  /* Subtract from the true value, square, accumulate */
1387  residue = cpl_vector_get(values, i)
1388  - cpl_polynomial_eval(self, x_val);
1389  *mse += residue * residue;
1390  }
1391  cpl_vector_delete(x_val);
1392  /* Average the error term */
1393  *mse /= np;
1394  }
1395 
1396  return CPL_ERROR_NONE;
1397 }
1398