1 /********************************************************************
2 MakeProfiles - Create mock astronomical profiles.
3 MakeProfiles is part of GNU Astronomy Utilities (Gnuastro) package.
4 
5 Original author:
6      Mohammad Akhlaghi <mohammad@akhlaghi.org>
7 Contributing author(s):
8 Copyright (C) 2015-2021, Free Software Foundation, Inc.
9 
10 Gnuastro is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by the
12 Free Software Foundation, either version 3 of the License, or (at your
13 option) any later version.
14 
15 Gnuastro is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
22 **********************************************************************/
23 #include <config.h>
24 
25 #include <math.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <error.h>
29 #include <stdlib.h>
30 
31 #include <sys/time.h>            /* generate random seed */
32 #include <gsl/gsl_rng.h>         /* used in setrandoms   */
33 #include <gsl/gsl_randist.h>     /* To make noise.       */
34 #include <gsl/gsl_integration.h> /* gsl_integration_qng  */
35 
36 #include <gnuastro/fits.h>
37 #include <gnuastro/pointer.h>
38 #include <gnuastro/dimension.h>
39 #include <gnuastro/statistics.h>
40 
41 #include <gnuastro-internal/timing.h>
42 
43 #include "main.h"
44 
45 #include "mkprof.h"              /* Needs main.h and astrthreads.h */
46 #include "profiles.h"
47 #include "oneprofile.h"
48 
49 
50 
51 
52 
53 
54 /****************************************************************
55  **************          Radial distance       ******************
56  ****************************************************************/
57 /* Set the center position of the profile in the oversampled image. Note
58    that 'mkp->width' is in the non-oversampled scale. IMPORTANT: the
59    ordering is in FITS coordinate order. */
60 static void
oneprofile_center_oversampled(struct mkonthread * mkp)61 oneprofile_center_oversampled(struct mkonthread *mkp)
62 {
63   struct mkprofparams *p=mkp->p;
64 
65   long os=p->oversample;
66   double *dim, r=1000000;
67   size_t i, id=mkp->ibq->id;
68   double val, pixfrac, intpart;
69 
70   for(i=0;i<p->ndim;++i)
71     {
72       dim = i==0 ? p->x : (i==1 ? p->y : p->z);
73       pixfrac = modf(fabs(dim[id]), &intpart);
74       val     = ( os*(mkp->width[i]/2 + pixfrac)
75                   + (pixfrac<0.5f ? os/2 : -1*os/2-1) );
76       mkp->center[i] = round(val*r)/r;
77     }
78 }
79 
80 
81 
82 
83 
84 static void
oneprofile_set_coord(struct mkonthread * mkp,size_t index)85 oneprofile_set_coord(struct mkonthread *mkp, size_t index)
86 {
87   size_t i, coord_c[3];
88   uint8_t os=mkp->p->oversample;
89   size_t ndim=mkp->ibq->image->ndim, *dsize=mkp->ibq->image->dsize;
90 
91   /* Get the coordinates in C order. */
92   gal_dimension_index_to_coord(index, ndim, dsize, coord_c);
93 
94   /* Convert these coordinates to one where the profile center is at the
95      center and the image is not over-sampled. Note that only 'coord_c' is
96      in C order.*/
97   for(i=0;i<ndim;++i)
98     mkp->coord[i] = ( coord_c[ndim-i-1] - mkp->center[i] )/os;
99 }
100 
101 
102 
103 
104 
105 /* Convert cartesian coordinates to the rotated elliptical radius. See the
106    "Defining an ellipse and ellipsoid" section of the book for the full
107    derivation. */
108 static void
oneprofile_r_el(struct mkonthread * mkp)109 oneprofile_r_el(struct mkonthread *mkp)
110 {
111   double Xr, Yr, Zr;                   /* Rotated x, y, z. */
112   double q1=mkp->q[0],   q2=mkp->q[1];
113   double c1=mkp->c[0],   s1=mkp->s[0];
114   double c2=mkp->c[1],   s2=mkp->s[1];
115   double c3=mkp->c[2],   s3=mkp->s[2];
116   double x=mkp->coord[0], y=mkp->coord[1], z=mkp->coord[2];
117 
118   switch(mkp->p->ndim)
119     {
120     case 2:
121       /* The parenthesis aren't necessary, but help in readability and
122          avoiding human induced bugs. */
123       Xr = x * ( c1       )     +   y * ( s1 );
124       Yr = x * ( -1.0f*s1 )     +   y * ( c1 );
125       mkp->r = sqrt( Xr*Xr + Yr*Yr/q1/q1 );
126       break;
127 
128     case 3:
129       Xr = x*(  c3*c1   - s3*c2*s1 ) + y*( c3*s1   + s3*c2*c1) + z*( s3*s2 );
130       Yr = x*( -1*s3*c1 - c3*c2*s1 ) + y*(-1*s3*s1 + c3*c2*c1) + z*( c3*s2 );
131       Zr = x*(  s1*s2              ) + y*(-1*s2*c1           ) + z*( c2    );
132       mkp->r = sqrt( Xr*Xr + Yr*Yr/q1/q1 + Zr*Zr/q2/q2 );
133       break;
134 
135     default:
136       error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to fix "
137             "the problem. The value %zu is not recognized for "
138             "'mkp->p->ndim'", __func__, PACKAGE_BUGREPORT, mkp->p->ndim);
139     }
140 }
141 
142 
143 
144 
145 
146 /* Calculate the circular/spherical distance of a pixel to the profile
147    center. This is just used to add pixels in the stack. Later, when the
148    pixels are popped from the stack, the elliptical radius will be used to
149    give them a value.*/
150 static float
oneprofile_r_circle(size_t index,struct mkonthread * mkp)151 oneprofile_r_circle(size_t index, struct mkonthread *mkp)
152 {
153 
154   size_t i, c[3];
155   double d, sum=0.0f;
156   size_t ndim=mkp->ibq->image->ndim, *dsize=mkp->ibq->image->dsize;
157 
158   /* Convert the index into a coordinate. */
159   gal_dimension_index_to_coord(index, ndim, dsize, c);
160 
161   /* Find the distance to the center along each dimension (in FITS
162      order). */
163   for(i=0;i<ndim;++i)
164     {
165       d = c[ndim-i-1] - mkp->center[i];
166       sum += d*d;
167     }
168 
169   /* Return the distance. */
170   return sqrt(sum);
171 }
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187 
188 
189 
190 
191 /****************************************************************
192  **************          Random points         ******************
193  ****************************************************************/
194 /* Fill pixel with random values */
195 float
oneprofile_randompoints(struct mkonthread * mkp)196 oneprofile_randompoints(struct mkonthread *mkp)
197 {
198   double r_before=mkp->r;
199   double range[3], sum=0.0f;
200   size_t i, j, numrandom=mkp->p->numrandom, ndim=mkp->p->ndim;
201   double coord_before[3]={mkp->coord[0], mkp->coord[1], mkp->coord[2]};
202 
203   /* Set the range in each dimension. */
204   for(i=0;i<ndim;++i)
205     range[i] = mkp->higher[i] - mkp->lower[i];
206 
207   /* Find the sum of the profile on the random positions. */
208   for(i=0;i<numrandom;++i)
209     {
210       for(j=0;j<ndim;++j)
211         mkp->coord[j] = mkp->lower[j] + gsl_rng_uniform(mkp->rng) * range[j];
212       oneprofile_r_el(mkp);
213       sum+=mkp->profile(mkp);
214     }
215 
216   /* Reset the original distance and coordinate of the pixel and return the
217      average random value. The resetting is mostly redundant (only useful
218      in checks), but since it has a very negligible cost (compared to the
219      random checks above) cost, its good to reset it to help in debugging
220      when necessary (avoid confusion when un-commenting the checks in
221      'oneprofile_pix_by_pix'). */
222   mkp->r=r_before;
223   mkp->coord[0]=coord_before[0];
224   mkp->coord[1]=coord_before[1];
225   mkp->coord[2]=coord_before[2];
226   return sum/numrandom;
227 }
228 
229 
230 
231 
232 
233 
234 
235 
236 
237 
238 
239 
240 
241 
242 
243 
244 
245 
246 
247 
248 /****************************************************************
249  *****************      2D integration       ********************
250  ****************************************************************/
251 /* This is an old implementation which we are not using now. But it is kept
252    here in case it can be useful */
253 #if 0
254 double
255 twod_over_x(double x, void *params)
256 {
257   struct mkonthread *mkp=(struct mkonthread *) params;
258 
259   mkp->x=x;
260   oneprofile_r_el(mkp);
261   return mkp->profile(mkp);
262 }
263 
264 
265 
266 
267 
268 /* Find the 2d integration over the region. */
269 double
270 twod_over_xy(double y, void *params)
271 {
272   gsl_function F;
273   static double abserr;
274   static size_t neval=0;
275   double epsabs=0, epsrel=EPSREL_FOR_INTEG, result;
276   struct mkonthread *mkp=(struct mkonthread *) params;
277 
278   F.function = &twod_over_x;
279   F.params = params;
280 
281   mkp->y=y;
282   gsl_integration_qng(&F, mkp->xl, mkp->xh, epsabs, epsrel,
283                       &result, &abserr, &neval);
284   return result;
285 }
286 
287 
288 
289 
290 /* 2D integration of a profile.*/
291 double
292 integ2d(struct mkonthread *mkp)
293 {
294   gsl_function F;
295   static double abserr;
296   static size_t neval=0;
297   double epsabs=0, epsrel=EPSREL_FOR_INTEG, result;
298 
299   F.function = &twod_over_xy;
300   F.params = mkp;
301   gsl_integration_qng(&F, mkp->yl, mkp->yh, epsabs,
302                       epsrel, &result, &abserr, &neval);
303   return result;
304 }
305 #endif
306 
307 
308 
309 
310 
311 
312 
313 
314 
315 
316 
317 
318 
319 
320 
321 
322 
323 
324 /**************************************************************/
325 /************       Pixel by pixel building       *************/
326 /*********        Positions are in C not FITS         *********/
327 /**************************************************************/
328 /* 'oneprofile_center_oversampled' stored the center of the profile in
329    floating point coordinates. This function will convert that into a
330    pixel index. */
331 static size_t
oneprofile_center_pix_index(struct mkonthread * mkp)332 oneprofile_center_pix_index(struct mkonthread *mkp)
333 {
334   double pixfrac, intpart;
335   size_t *dsize=mkp->ibq->image->dsize;
336   size_t i, coord[3], ndim=mkp->p->ndim;
337 
338   /* Find the coordinates of the center point. Note 'mkp->center' is in
339      FITS coordinates, while coord must be in C coordinates (to be used in
340      'gal_dimension_coord_to_index'). */
341   for(i=0;i<ndim;++i)
342     {
343       pixfrac = modf(mkp->center[i], &intpart);
344       coord[ndim-i-1] = (long)(mkp->center[i]) + ( pixfrac<0.5f ? 0 : 1 );
345     }
346 
347   /* Retun the pixel index of this coordinate. */
348   return gal_dimension_coord_to_index(ndim, dsize, coord);
349 }
350 
351 
352 
353 
354 
355 static void
oneprofile_pix_by_pix(struct mkonthread * mkp)356 oneprofile_pix_by_pix(struct mkonthread *mkp)
357 {
358   struct builtqueue *ibq=mkp->ibq;
359   size_t ndim=ibq->image->ndim, *dsize=ibq->image->dsize;
360 
361   uint8_t *byt;
362   gal_list_sizet_t *Q=NULL;
363   int use_rand_points=1, ispeak=1;
364   double tolerance=mkp->p->tolerance;
365   float circ_r, *array=mkp->ibq->image->array;
366   double (*profile)(struct mkonthread *)=mkp->profile;
367   double truncr=mkp->truncr, approx, hp=0.5f/mkp->p->oversample;
368   size_t i, p, *dinc=gal_dimension_increment(ndim, dsize);
369 
370   /* lQ: Largest. sQ: Smallest in queue */
371   gal_list_dosizet_t *lQ=NULL, *sQ;
372 
373   /* Find the nearest pixel to the profile center and add it to the
374      queue. */
375   p=oneprofile_center_pix_index(mkp);
376 
377   /* If this is a point source, just fill that one pixel and leave this
378      function. */
379   if(mkp->func==PROFILE_POINT)
380     { array[p]=1; return; }
381 
382   /* Allocate the 'byt' array. It is used as a flag to make sure that we
383      don't re-calculate the profile value on a pixel more than once. */
384   byt = gal_pointer_allocate(GAL_TYPE_UINT8,
385                              gal_dimension_total_size(ndim, dsize), 1,
386                              __func__, "byt");
387 
388   /* Start the queue: */
389   byt[p]=1;
390   gal_list_dosizet_add( &lQ, &sQ, p, oneprofile_r_circle(p, mkp) );
391 
392   /* If random points are necessary, then do it: */
393   switch(mkp->func)
394     {
395     case PROFILE_SERSIC:
396     case PROFILE_MOFFAT:
397     case PROFILE_GAUSSIAN:
398       while(sQ)
399         {
400           /* In case you want to see the status of the twosided ordered
401              queue, increasing and decreasing side by side, uncomment this
402              line. Note that there will be a lot of lines printed! */
403           /*print_tossll(lQ, sQ);*/
404 
405           /* Pop a pixel from the queue, convert its index into coordinates
406              and use them to estimate the elliptical radius of the
407              pixel. If the pixel is outside the truncation radius, ignore
408              it. */
409           p=gal_list_dosizet_pop_smallest(&lQ, &sQ, &circ_r);
410           oneprofile_set_coord(mkp, p);
411           oneprofile_r_el(mkp);
412           if(mkp->r > truncr) continue;
413 
414           /* Set the range for this pixel. */
415           for(i=0;i<ndim;++i)
416             {
417               mkp->lower[i]  = mkp->coord[i] - hp;
418               mkp->higher[i] = mkp->coord[i] + hp;
419             }
420 
421           /* Find the random points and profile center. */
422           array[p]=oneprofile_randompoints(mkp);
423           approx=profile(mkp);
424           if (fabs(array[p]-approx)/array[p] < tolerance)
425             use_rand_points=0;
426 
427           /* For a check:
428           printf("coord: %g, %g\n", mkp->coord[0], mkp->coord[1]);
429           printf("r_rand: %g (rand: %g, center: %g)\n\n", mkp->r, array[p],
430                  approx);
431           */
432 
433           /* Save the peak flux if this is the first pixel: */
434           if(ispeak) { mkp->peakflux=array[p]; ispeak=0; }
435 
436           /* For the log file: */
437           ++ibq->numaccu;
438           ibq->accufrac+=array[p];
439 
440           /* Go over the neighbors and add them to queue of elements to
441              check if they haven't been done already. */
442           GAL_DIMENSION_NEIGHBOR_OP(p, ndim, dsize, 1, dinc,
443             {
444               if(byt[nind]==0)
445                 {
446                   byt[nind]=1;
447                   gal_list_dosizet_add( &lQ, &sQ, nind,
448                                         oneprofile_r_circle(nind, mkp) );
449                 }
450             } );
451 
452           if(use_rand_points==0) break;
453         }
454     }
455 
456 
457   /* All the pixels that required integration or random points are now
458      done, so we don't need an ordered array any more. */
459   gal_list_dosizet_to_sizet(lQ, &Q);
460 
461 
462   /* Order doesn't matter any more, add all the pixels you find. */
463   while(Q)
464     {
465       p=gal_list_sizet_pop(&Q);
466       oneprofile_set_coord(mkp, p);
467       oneprofile_r_el(mkp);
468 
469       /* See if this pixel's radial distance is larger than the truncation
470          radius. If so, then don't add its neighbors to the queue and
471          continue to the next pixel in the queue. */
472       if(mkp->r>truncr)
473         {
474           /* For the circumference, if the profile is too elongated
475              and circumwidth is too small, then some parts of the
476              circumference will not be shown without this condition. */
477           if(mkp->func==PROFILE_CIRCUMFERENCE) array[p]=profile(mkp);
478           continue;
479         }
480 
481       /* Find the value for this pixel: */
482       array[p]=profile(mkp);
483 
484       /* For a check:
485       printf("r_center: %g\n", mkp->r);
486       */
487 
488       /* Save the peak flux if this is the first pixel: */
489       if(ispeak) { mkp->peakflux=array[p]; ispeak=0; }
490 
491       /* Go over the neighbours and add them to queue of elements
492          to check. */
493       GAL_DIMENSION_NEIGHBOR_OP(p, ndim, dsize, 1, dinc,
494         {
495           if(byt[nind]==0)
496             {
497               byt[nind]=1;
498               gal_list_sizet_add(&Q, nind);
499             }
500         } );
501     }
502 
503   /* Clean up. */
504   free(byt);
505   free(dinc);
506 }
507 
508 
509 
510 
511 
512 
513 
514 
515 
516 
517 
518 
519 
520 
521 
522 
523 
524 
525 
526 /**************************************************************/
527 /************        Set profile parameters       *************/
528 /**************************************************************/
529 int
oneprofile_ispsf(uint8_t fcode)530 oneprofile_ispsf(uint8_t fcode)
531 {
532   return fcode==PROFILE_MOFFAT || fcode==PROFILE_GAUSSIAN;
533 }
534 
535 
536 
537 
538 
539 /* Prepare all the parameters for any type of profile. */
540 void
oneprofile_set_prof_params(struct mkonthread * mkp)541 oneprofile_set_prof_params(struct mkonthread *mkp)
542 {
543   struct mkprofparams *p=mkp->p;
544 
545   double sigma;
546   int tp=p->tunitinp;
547   size_t id=mkp->ibq->id, ndim=p->ndim;
548 
549   /* Fill the most basic profile agnostic parameters. */
550   mkp->brightness = ( p->mcolisbrightness
551                       ? p->m[id]
552                       : pow( 10, (p->zeropoint - p->m[id]) / 2.5f ) );
553   mkp->ibq->ispsf = p->kernel ? 1 : oneprofile_ispsf(p->f[id]);
554   mkp->func       = mkp->ibq->func = p->f[id];
555 
556 
557   /* Fill in the dimension-dependent parameters. */
558   switch(ndim)
559     {
560     case 2:
561       /* Shifts were already multiplied with oversample. Just note that
562          p->x and p->y are in the FITS ordering, while p->shift is in C
563          ordering. */
564       mkp->q[0]       = p->q1[id];
565       p->x[id]       += p->shift[1]/p->oversample;
566       p->y[id]       += p->shift[0]/p->oversample;
567       mkp->c[0]       = cos( p->p1[id] * DEGREESTORADIANS );
568       mkp->s[0]       = sin( p->p1[id] * DEGREESTORADIANS );
569       break;
570 
571     case 3:
572       /* See comments for 2D. */
573       mkp->q[0]       = p->q1[id];
574       mkp->q[1]       = p->q2[id];
575       p->x[id]       += p->shift[2]/p->oversample;
576       p->y[id]       += p->shift[1]/p->oversample;
577       p->z[id]       += p->shift[0]/p->oversample;
578       mkp->c[0]       = cos( p->p1[id] * DEGREESTORADIANS );
579       mkp->s[0]       = sin( p->p1[id] * DEGREESTORADIANS );
580       mkp->c[1]       = cos( p->p2[id] * DEGREESTORADIANS );
581       mkp->s[1]       = sin( p->p2[id] * DEGREESTORADIANS );
582       mkp->c[2]       = cos( p->p3[id] * DEGREESTORADIANS );
583       mkp->s[2]       = sin( p->p3[id] * DEGREESTORADIANS );
584       break;
585 
586     default:
587       error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s to "
588             "address the problem. The value '%zu' is not recognized for "
589             "'ndim'", __func__, PACKAGE_BUGREPORT, ndim);
590     }
591 
592 
593   /* Fill the profile-dependent parameters. */
594   switch (mkp->func)
595     {
596     case PROFILE_SERSIC:
597       mkp->correction       = 1;
598       mkp->profile          = &profiles_sersic;
599       mkp->sersic_re        = p->r[id];
600       mkp->sersic_inv_n     = 1.0f/p->n[id];
601       mkp->sersic_nb        = -1.0f*profiles_sersic_b(p->n[id]);
602       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
603       break;
604 
605 
606 
607     case PROFILE_MOFFAT:
608       mkp->correction       = 1;
609       mkp->profile          = &profiles_moffat;
610       mkp->moffat_nb        = -1.0f*p->n[id];
611       mkp->moffat_alphasq   = profiles_moffat_alpha(p->r[id], p->n[id]);
612       mkp->moffat_alphasq  *= mkp->moffat_alphasq;
613       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id]/2;
614       if(p->psfinimg==0 && p->individual==0)
615         {
616           mkp->brightness   = 1.0f; /* When the PSF is a separate image, */
617           p->x[id]          = 0.0f; /* it should be centered and have a  */
618           p->y[id]          = 0.0f; /* total brightness of 1.0f. */
619           if(ndim==3)
620             p->z[id]        = 0.0f;
621         }
622       break;
623 
624 
625 
626     case PROFILE_GAUSSIAN:
627       mkp->correction       = 1;
628       mkp->profile          = &profiles_gaussian;
629       sigma                 = p->r[id]/2.35482f;
630       mkp->gaussian_c       = -1.0f/(2.0f*sigma*sigma);
631       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id]/2;
632       if(p->psfinimg==0 && p->individual==0)
633         {
634           mkp->brightness   = 1.0f; /* Same as the explanations for    */
635           p->x[id]          = 0.0f; /* The Moffat profile. */
636           p->y[id]          = 0.0f;
637           if(ndim==3)
638             p->z[id]        = 0.0f;
639         }
640       break;
641 
642 
643 
644     case PROFILE_POINT:
645       mkp->correction       = 1;
646       mkp->fixedvalue       = 1.0f;
647       mkp->profile          = &profiles_flat;
648       break;
649 
650 
651 
652     case PROFILE_FLAT:
653       mkp->profile          = &profiles_flat;
654       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
655       if(p->mforflatpix)
656         {
657           mkp->correction   = 0;
658           mkp->fixedvalue   = p->m[id];
659         }
660       else
661         {
662           mkp->correction   = 1;
663           mkp->fixedvalue   = 1.0f;
664         }
665       break;
666 
667 
668 
669     case PROFILE_CIRCUMFERENCE:
670       mkp->profile          = &profiles_circumference;
671       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
672       mkp->intruncr         = mkp->truncr - p->circumwidth;
673       if(p->mforflatpix)
674         {
675           mkp->correction   = 0;
676           mkp->fixedvalue   = p->m[id];
677         }
678       else
679         {
680           mkp->correction   = 1;
681           mkp->fixedvalue   = 1.0f;
682         }
683       if(mkp->intruncr<0.0f)
684         mkp->intruncr       = 0.0f;
685       break;
686 
687 
688 
689     case PROFILE_DISTANCE:
690       mkp->profile          = profiles_radial_distance;
691       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
692       mkp->correction       = 0;
693       break;
694 
695 
696 
697 
698 
699     case PROFILE_AZIMUTH:
700       mkp->profile          = profiles_azimuth;
701       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
702       mkp->correction       = 0;
703       break;
704 
705 
706 
707     case PROFILE_CUSTOM:
708       mkp->profile          = profiles_custom_table;
709       mkp->truncr           = tp ? p->t[id] : p->t[id]*p->r[id];
710       mkp->correction       = 0;
711       break;
712 
713 
714 
715     default:
716       error(EXIT_FAILURE, 0, "%s: a bug! Please contact us so we can "
717             "correct this problem. The profile code %u is not recognized.",
718             __func__, mkp->func);
719     }
720 }
721 
722 
723 
724 
725 
726 
727 
728 
729 
730 
731 
732 
733 
734 
735 
736 
737 
738 
739 
740 
741 /**************************************************************/
742 /************          Outside functions          *************/
743 /**************************************************************/
744 void
oneprofile_make(struct mkonthread * mkp)745 oneprofile_make(struct mkonthread *mkp)
746 {
747   struct mkprofparams *p=mkp->p;
748 
749   double sum;
750   float *f, *ff;
751   size_t i, dsize[3], ndim=p->ndim;
752 
753 
754   /* Find the profile center in the over-sampled image in C
755      coordinates. IMPORTANT: width must not be oversampled.*/
756   oneprofile_center_oversampled(mkp);
757 
758 
759   /* From this point on, the widths will be in the actual pixel widths
760      (with oversampling). */
761   for(i=0;i<ndim;++i)
762     {
763       mkp->width[i]  *= p->oversample;
764       dsize[ndim-i-1] = mkp->width[i];
765     }
766 
767 
768   /* Allocate and clear the array for this one profile. */
769   mkp->ibq->image=gal_data_alloc(NULL, GAL_TYPE_FLOAT32, ndim, dsize,
770                                  NULL, 1, p->cp.minmapsize, p->cp.quietmmap,
771                                  "MOCK", "Brightness", NULL);
772 
773 
774   /* Build the profile in the image. */
775   oneprofile_pix_by_pix(mkp);
776 
777 
778   /* Correct the sum of pixels in the profile so it has the fixed total
779      magnitude or pixel value, mkp->correction was set in
780      setprofparams. Note that the profiles were not normalized during the
781      building.*/
782   if(mkp->correction)
783     {
784       /* First get the sum of all the pixels in the profile. */
785       ff=(f=mkp->ibq->image->array) + mkp->ibq->image->size;
786       sum=0.0f; do sum+=*f++; while(f<ff);
787 
788       /* Correct the fraction of brightness that was calculated
789          accurately (not using the pixel center). */
790       mkp->ibq->accufrac /= sum;
791 
792       /* Correct all the profile pixels. Note that ideally, if a user wants
793          a NaN valued profile, they should use the 'flat' profile with
794          '--mforflatpix', which won't need this correction. However, it
795          might happen that they forget the later, or the catalog might be
796          generated by a script that gives a NaN value for the magnitude
797          with any kind of profile. In such cases if we don't check the NaN
798          value, then the whole profile's box is going to be NaN values,
799          which is inconvenient and with the simple check here we can avoid
800          it (only have the profile's pixels set to NaN. */
801       ff = (f=mkp->ibq->image->array) + mkp->ibq->image->size;
802       if(isnan(mkp->brightness))
803         do *f = *f ? NAN : *f ; while(++f<ff);
804       else
805         {
806           if(p->magatpeak)
807             do *f++ *= mkp->brightness/mkp->peakflux; while(f<ff);
808           else
809             do *f++ *= mkp->brightness/sum; while(f<ff);
810         }
811     }
812 }
813