1 /*********************************************************************
2 Crop - Crop a given size from one or multiple images.
3 Crop 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 <ctype.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <error.h>
30 #include <float.h>
31 #include <string.h>
32 #include <stdlib.h>
33 
34 #include <gnuastro/box.h>
35 #include <gnuastro/fits.h>
36 #include <gnuastro/blank.h>
37 #include <gnuastro/polygon.h>
38 #include <gnuastro/pointer.h>
39 
40 #include <gnuastro-internal/timing.h>
41 #include <gnuastro-internal/checkset.h>
42 
43 #include "main.h"
44 
45 #include "onecrop.h"
46 #include "wcsmode.h"
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 /*******************************************************************/
71 /************     Set/correct first and last pixel    **************/
72 /*******************************************************************/
73 /* Read the section string and set the starting and ending pixels
74    based on that. */
75 void
onecrop_parse_section(struct cropparams * p,size_t * dsize,long * fpixel,long * lpixel)76 onecrop_parse_section(struct cropparams *p, size_t *dsize,
77                       long *fpixel, long *lpixel)
78 {
79   int add;
80   long read;
81   char *tailptr;
82   long naxes[MAXDIM];
83   char forl='f', *pt=p->section;
84   size_t i, ndim=p->imgs->ndim, dim=0;
85 
86 
87   /* When the user asks for a section of the dataset, then the cropped
88      region is not defined by its center. So it makes no sense to later
89      check if the center is blank or not. Hence, we will over-write it with
90      zero. */
91   p->checkcenter=0;
92 
93 
94   /* Initialize the fpixel and lpixel arrays (note that 'section' is only
95      defined in image mode, so there will only be one element in 'imgs'. */
96   for(i=0;i<ndim;++i)
97     {
98       fpixel[i] = 1;
99       lpixel[i] = naxes[i] = p->imgs->dsize[ ndim - i - 1 ];
100     }
101 
102 
103   /* Parse the string: 'forl': "first-or-last". */
104   while(*pt!='\0')
105     {
106       add=0;
107       switch(*pt)
108         {
109         case ',':
110           ++dim;
111           if(dim>=ndim)
112             error(EXIT_FAILURE, 0, "Extra ',' in '%s'", p->section);
113           forl='f';
114           ++pt;
115           break;
116         case ':':
117           forl='l';
118           ++pt;
119           break;
120         case '.':
121           error(EXIT_FAILURE, 0, "the numbers in the argument to "
122                 "'--section' ('-s') have to be integers. You input "
123                 "includes a float number: %s", p->section);
124           break;
125         case ' ': case '\t':
126           ++pt;
127           break;
128         case '*':
129           add=1;                /* If it is an asterisk, then add the */
130           ++pt;                 /* given value to the maximum size of */
131           break;                /* the image. */
132 
133         /* Numerical characters signify the start of a number, so we don't
134            need to increment the pointer and can just break out. */
135         case '0': case '1': case '2': case '3': case '4': case '5':
136         case '6': case '7': case '8': case '9': case '-':
137           break;
138 
139         /* An un-recognized character should crash the program. */
140         default:
141           error(EXIT_FAILURE, 0, "value to '--section' must only contain "
142                 "integer numbers and these special characters between them: "
143                 "',', ':', '*' when necessary. But it is '%s' (the first "
144                 "non-acceptable character is '%c').\n\n"
145                 "Please run the command below to learn more about this "
146                 "option in Gnuastro's Crop program:\n\n"
147                 "    $ info gnuastro \"Crop section syntax\"\n", p->section,
148                 *pt);
149           break;
150         }
151 
152       /* Read the number: */
153       read=strtol(pt, &tailptr, 0);
154 
155       /* Check if there actually was a number.
156       printf("\n\n------\n%c: %ld (%s)\n", *pt, read, tailptr);
157       */
158 
159       /* Make sure if a number was read at all? */
160       if(tailptr==pt)           /* No number was read!                 */
161         {
162           if(add) read=0;       /* We have a * followed by ':' or ','. */
163           else    continue;
164         }
165 
166       /* Put it in the correct array. */
167       if(forl=='f')
168         fpixel[dim] = add ? naxes[dim]+read : read;
169       else
170         lpixel[dim] = add ? naxes[dim]+read : read;
171       pt=tailptr;
172     }
173 
174 
175   /* Make sure the first pixel is located before/below the last pixel. */
176   for(i=0;i<ndim;++i)
177     if(fpixel[i]>lpixel[i])
178       error(EXIT_FAILURE, 0, "the bottom left corner coordinates "
179             "cannot be larger or equal to the top right's! Your section "
180             "string (%s) has been read as: bottom left coordinate "
181             "(%ld, %ld) to top right coordinate (%ld, %ld)",
182             p->section, fpixel[0], fpixel[1], lpixel[0], lpixel[1]);
183 
184   /* For a check:
185   printf("\n%s\n", p->section);
186   printf("fpixel: ("); for(i=0;i<ndim;++i) printf("%ld, ", fpixel[i]);
187   printf("\b\b)\n");
188   printf("lpixel: ("); for(i=0;i<ndim;++i) printf("%ld, ", lpixel[i]);
189   printf("\b\b)\n\n");
190   exit(0);
191   */
192 }
193 
194 
195 
196 
197 
198 static void
onecrop_ipolygon_fl(double * ipolygon,size_t nvertices,long * fpixel,long * lpixel)199 onecrop_ipolygon_fl(double *ipolygon, size_t nvertices, long *fpixel,
200                     long *lpixel)
201 {
202   size_t i;
203   double minx=FLT_MAX, miny=FLT_MAX;
204   double maxx=-FLT_MAX, maxy=-FLT_MAX;
205 
206   /* Find their minimum and maximum values. */
207   for(i=0;i<nvertices;++i)
208     {
209       if(ipolygon[i*2]>maxx) maxx=ipolygon[i*2];
210       if(ipolygon[i*2]<minx) minx=ipolygon[i*2];
211       if(ipolygon[i*2+1]>maxy) maxy=ipolygon[i*2+1];
212       if(ipolygon[i*2+1]<miny) miny=ipolygon[i*2+1];
213     }
214 
215   /* Set the first and last pixel. */
216   fpixel[0] = minx - (int)minx >=0.5 ? (int)minx + 1 : (int)minx;
217   fpixel[1] = miny - (int)miny >=0.5 ? (int)miny + 1 : (int)miny;
218   lpixel[0] = maxx - (int)maxx >=0.5 ? (int)maxx + 1 : (int)maxx;
219   lpixel[1] = maxy - (int)maxy >=0.5 ? (int)maxy + 1 : (int)maxy;
220 }
221 
222 
223 
224 
225 
226 #define POLYGON_MASK(CTYPE) {                                           \
227     CTYPE *ba=array, *bb=gal_blank_alloc_write(type);                   \
228     for(i=0;i<size;++i)                                                 \
229       {                                                                 \
230         point[0]=i%s1+1; point[1]=i/s1+1;                               \
231         if((*isinside)(ipolygon, point, nvertices)==polygonout)         \
232           ba[i]=*bb;                                                    \
233       }                                                                 \
234     free(bb);                                                           \
235   }
236 
237 
238 void
polygonmask(struct onecropparams * crp,void * array,long * fpixel_i,size_t s0,size_t s1)239 polygonmask(struct onecropparams *crp, void *array, long *fpixel_i,
240             size_t s0, size_t s1)
241 {
242   int type=crp->p->type;
243   double *ipolygon, point[2];
244   int polygonout=crp->p->polygonout;
245   int (*isinside)(double *, double *, size_t);
246   size_t i, *ordinds, size=s0*s1, nvertices=crp->p->nvertices;
247 
248   /* First of all, allocate enough space to put a copy of the input
249      coordinates (we will be using that after sorting in an
250      anti-clickwise manner.) */
251   errno=0; ipolygon=malloc(2*nvertices*sizeof *ipolygon);
252   if(ipolygon==NULL)
253     error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for ipolygon",
254           __func__, 2*nvertices*sizeof *ipolygon);
255   errno=0; ordinds=malloc(nvertices*sizeof *ordinds);
256   if(ordinds==NULL)
257     error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for ordinds",
258           __func__, nvertices*sizeof *ordinds);
259 
260   /* If the user wants to sort the edges, do it. If not, make sure its in
261      counter-clockwise orientation. */
262   if(crp->p->polygonsort)
263     gal_polygon_vertices_sort(crp->ipolygon, nvertices, ordinds);
264   else
265     {
266       /* Keep the original order of the vertices, just make it
267          counter-clockwise. */
268       for(i=0;i<nvertices;++i) ordinds[i]=i;
269       gal_polygon_to_counterclockwise(crp->ipolygon, nvertices);
270     }
271 
272   /* Fill the final polygon vertice positions within 'ipolygon' and also
273      the fpixel_i coordinates from all the vertices to bring them into the
274      crop image coordinates. */
275   for(i=0;i<nvertices;++i)
276     {
277       ipolygon[i*2  ] = crp->ipolygon[ordinds[i]*2]   - fpixel_i[0];
278       ipolygon[i*2+1] = crp->ipolygon[ordinds[i]*2+1] - fpixel_i[1];
279     }
280 
281   /* Print a warning if we done the sorting, _and_ the sorted polygon is
282      concave, _and_ the user hasn't activated the quiet mode. Note that we
283      could'n do this immediately after calling 'gal_polygon_vertices_sort'
284      because that function doesn't touch the actual vertice values, it only
285      fills 'ordinds'. */
286   if(crp->p->polygonsort
287      && !crp->p->cp.quiet
288      && !gal_polygon_is_convex(ipolygon, nvertices) )
289     error(0, 0, "%s: warning: the given vertices belong to a concave "
290           "polygon, but there is no unique solution to the sorting of "
291           "concave polygons. Please check the cropped image, if it doesn't "
292           "correspond to your desired polygon, sort the vertices yourself "
293           "in counter-clockwise order _and_ don't use the '--polygonsort' "
294           "option", __func__);
295 
296   /* Set the function for checking if a point is inside the polygon. For
297      concave polygons the process is more complex and thus
298      slower. Therefore when the polygon is convex, its better to use the
299      simpler/faster function. */
300   isinside = ( gal_polygon_is_convex(ipolygon, nvertices)
301                ? gal_polygon_is_inside_convex
302                : gal_polygon_is_inside );
303 
304   /* Go over all the pixels in the image and if they are within the
305      polygon keep them if the user has asked for it.*/
306   switch(type)
307     {
308     case GAL_TYPE_UINT8:    POLYGON_MASK(uint8_t);  break;
309     case GAL_TYPE_INT8:     POLYGON_MASK(int8_t);   break;
310     case GAL_TYPE_UINT16:   POLYGON_MASK(uint16_t); break;
311     case GAL_TYPE_INT16:    POLYGON_MASK(int16_t);  break;
312     case GAL_TYPE_UINT32:   POLYGON_MASK(uint32_t); break;
313     case GAL_TYPE_INT32:    POLYGON_MASK(int32_t);  break;
314     case GAL_TYPE_INT64:    POLYGON_MASK(int64_t);  break;
315     case GAL_TYPE_FLOAT32:  POLYGON_MASK(float);    break;
316     case GAL_TYPE_FLOAT64:  POLYGON_MASK(double);   break;
317     default:
318       error(EXIT_FAILURE, 0, "%s: a bug! Please contact us at %s, so we "
319             "can fix the problem. Type code %d is not recognized",
320             __func__, PACKAGE_BUGREPORT, type);
321     }
322 
323   /* Clean up: */
324   free(ordinds);
325   free(ipolygon);
326 }
327 
328 
329 
330 
331 
332 
333 
334 
335 
336 
337 
338 
339 
340 
341 
342 
343 
344 
345 
346 /*******************************************************************/
347 /******************          One crop.         *********************/
348 /*******************************************************************/
349 static void
onecrop_zero_to_nan(void * array,size_t size,int type)350 onecrop_zero_to_nan(void *array, size_t size, int type)
351 {
352   float *fp, *ffp;
353   double *dp, *fdp;
354 
355   switch(type)
356     {
357     case GAL_TYPE_FLOAT32:
358       ffp=(fp=array)+size;
359       do
360         if(*fp==0.0f) *fp=NAN;
361       while(++fp<ffp);
362       break;
363 
364     case GAL_TYPE_FLOAT64:
365       fdp=(dp=array)+size;
366       do
367         if(*dp==0.0f) *dp=NAN;
368       while(++dp<fdp);
369       break;
370 
371     default:
372       error(EXIT_FAILURE, 0, "%s: %d is not a recognized type",
373             __func__, type);
374     }
375 }
376 
377 
378 
379 
380 
381 
382 /* Set the output name and image output sizes. */
383 void
onecrop_name(struct onecropparams * crp)384 onecrop_name(struct onecropparams *crp)
385 {
386   char **strarr;
387   struct cropparams *p=crp->p;
388   struct gal_options_common_params *cp=&p->cp;
389 
390   /* Set the output name and crop sides: */
391   if(p->catname)
392     {
393       /* If a name column was set, use it, otherwise, use the ID of the
394          profile. */
395       if(p->name)
396         {
397           strarr=p->name;
398           if( asprintf(&crp->name, "%s%s%s", cp->output, strarr[crp->out_ind],
399                        p->suffix)<0 )
400             error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
401         }
402       else
403         if( asprintf(&crp->name, "%s%zu%s", cp->output, crp->out_ind+1,
404                      p->suffix)<0 )
405           error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
406 
407       /* Make sure the file doesn't exist. */
408       gal_checkset_writable_remove(crp->name, 0, cp->dontdelete);
409     }
410   else
411     {
412       /* Set the output name. */
413       if(p->outnameisfile)            /* An output file was specified. */
414         {
415           crp->name=cp->output;
416           gal_checkset_writable_remove(crp->name, 0, cp->dontdelete);
417         }
418       else          /* The output was a directory, use automatic output. */
419         crp->name=gal_checkset_automatic_output(cp,
420                                                 p->imgs[crp->in_ind].name,
421                                                 p->suffix);
422     }
423 }
424 
425 
426 
427 
428 
429 /* Find the first and last pixel of a crop. */
430 static void
onecrop_flpixel(struct onecropparams * crp)431 onecrop_flpixel(struct onecropparams *crp)
432 {
433   struct cropparams *p=crp->p;
434   size_t ndim=p->imgs->ndim;
435 
436   double center[MAXDIM];
437   int ncoord=1, status=0;
438   size_t i, *dsize=p->imgs[crp->in_ind].dsize;
439   long *fpixel=crp->fpixel, *lpixel=crp->lpixel;
440   double pixcrd[MAXDIM], imgcrd[MAXDIM], phi[1], theta[1];
441 
442   switch(p->mode)
443     {
444 
445     case IMGCROP_MODE_IMG:
446       if(p->section)            /* Defined by section.    */
447         onecrop_parse_section(p, dsize, fpixel, lpixel);
448       else if(p->polygon)       /* Defined by a polygon.  */
449         {
450           if(p->polygonout==0)
451             onecrop_ipolygon_fl(p->ipolygon, p->nvertices, fpixel, lpixel);
452         }
453       else                      /* Defined by its center. */
454         {
455           for(i=0;i<ndim;++i) center[i] = p->centercoords[i][crp->out_ind];
456           gal_box_border_from_center(center, ndim, p->iwidth, fpixel, lpixel);
457         }
458       break;
459 
460 
461     case IMGCROP_MODE_WCS: /* In wcsmode, crp->world is already filled.   */
462       if(p->polygon)       /* Note: p->iwidth was set based on p->wwidth. */
463         {
464           /* Fill crp->ipolygon in wcspolygonpixel, then set flpixel*/
465           fillcrpipolygon(crp);
466           if(p->polygonout==0)
467             onecrop_ipolygon_fl(crp->ipolygon, p->nvertices, fpixel, lpixel);
468         }
469       else
470         {
471           /* Convert 'crp->world' (in WCS) into 'pixcrd' (image coord). */
472           if(wcss2p(p->imgs[crp->in_ind].wcs, ncoord, ndim, crp->world,
473                     phi, theta, imgcrd, pixcrd, &status) )
474             if(status)
475               error(EXIT_FAILURE, 0, "%s: wcss2p error %d: %s", __func__,
476                     status, wcs_errmsg[status]);
477 
478           /* Find the first and last pixels of this crop. */
479           gal_box_border_from_center(pixcrd, ndim, p->iwidth, fpixel, lpixel);
480         }
481       break;
482 
483 
484     default:
485       error(EXIT_FAILURE, 0, "%s: a bug! The domain (WCS or image) are not "
486             "set. Please contact us at %s so we can see how it got to this "
487             "impossible place", __func__, PACKAGE_BUGREPORT);
488     }
489 
490 
491   /* If the user only wants regions outside to the polygon, then set
492      the fpixel and lpixel to cover the full input image. */
493   if(p->polygon && p->polygonout)
494     {
495       crp->fpixel[0]=crp->fpixel[1]=1;
496       crp->lpixel[0]=dsize[1];
497       crp->lpixel[1]=dsize[0];
498     }
499 }
500 
501 
502 
503 
504 
505 
506 /* Find the size of the final FITS image (irrespective of how many
507    crops will be needed for it) and make the image to keep the
508    data.
509 
510    NOTE: The fpixel and lpixel in crp keep the first and last pixel of
511    the total image for this crop, irrespective of the final keeping
512    blank areas or not. While the fpixel_i and lpixel_i arrays keep the
513    first and last pixels after the blank pixels have been removed.
514 */
515 static void
onecrop_make_array(struct onecropparams * crp,long * fpixel_i,long * lpixel_i,long * fpixel_c,long * lpixel_c)516 onecrop_make_array(struct onecropparams *crp, long *fpixel_i,
517                    long *lpixel_i, long *fpixel_c, long *lpixel_c)
518 {
519   double crpix;
520   fitsfile *ofp;
521   long naxes[MAXDIM];
522   char *outname=crp->name;
523   int status=0, type=crp->p->type;
524   char **strarr, cpname[FLEN_KEYWORD];
525   gal_data_t *rkey=gal_data_array_calloc(1);
526   size_t i, ndim=crp->p->imgs->ndim, totsize;
527   struct inputimgs *img=&crp->p->imgs[crp->in_ind];
528 
529 
530   /* Set the size of the output, in WCS mode, noblank==0. */
531   if(crp->p->noblank && crp->p->mode==IMGCROP_MODE_IMG)
532     for(i=0;i<ndim;++i)
533       {
534         fpixel_c[i] = 1;
535         lpixel_c[i] = naxes[i] = lpixel_i[i]-fpixel_i[i]+1;
536       }
537   else
538     for(i=0;i<ndim;++i)
539       naxes[i] = crp->lpixel[i]-crp->fpixel[i]+1;
540 
541 
542   /* Create the FITS file with a blank first extension, then close it, so
543      with the next 'fits_open_file', we build the image in the second
544      extension. This way, atleast for Gnuastro's outputs, we can
545      consistently use '-h1' (something like how you count columns, or
546      generally everything from 1). */
547   if(fits_create_file(&ofp, outname, &status))
548     gal_fits_io_error(status, "creating file");
549   if(crp->p->primaryimghdu==0)
550     {
551       fits_create_img(ofp, SHORT_IMG, 0, naxes, &status);
552       fits_close_file(ofp, &status);
553     }
554 
555 
556   /* Create the output crop image. */
557   fits_open_file(&crp->outfits, outname, READWRITE, &status);
558   fits_create_img(crp->outfits, gal_fits_type_to_bitpix(type),
559                   ndim, naxes, &status);
560   gal_fits_io_error(status, "creating image");
561   ofp=crp->outfits;
562 
563 
564   /* When CFITSIO creates a FITS extension it adds two comments linking to
565      the FITS paper. Since we are mentioning the version of CFITSIO and
566      only use its routines to read/write from/to FITS files, this is
567      redundant. If 'status!=0', then 'gal_fits_io_error' will abort, but in
568      case CFITSIO doesn't write the comments, status will become
569      non-zero. So we are resetting it to zero after these (because not
570      being able to delete them isn't an error). */
571   fits_delete_key(ofp, "COMMENT", &status);
572   fits_delete_key(ofp, "COMMENT", &status);
573   status=0;
574 
575 
576   /* Read the units of the input dataset and store them in the output. */
577   rkey->next=NULL;
578   rkey->name="BUNIT";
579   rkey->type=GAL_TYPE_STRING;
580   gal_fits_key_read_from_ptr(crp->infits, rkey, 1, 1);
581   if(rkey->status==0)           /* The BUNIT keyword was read. */
582     {
583       strarr=rkey->array;
584       fits_update_key(ofp, TSTRING, "BUNIT", strarr[0], "physical units",
585                       &status);
586       gal_fits_io_error(status, "writing BUNIT");
587     }
588   rkey->name=NULL;              /* 'name' wasn't allocated. */
589   gal_data_free(rkey);
590 
591 
592   /* Write the blank value as a FITS keyword if necessary. */
593   if( type!=GAL_TYPE_FLOAT32 && type!=GAL_TYPE_FLOAT64 )
594     if(fits_write_key(ofp, gal_fits_type_to_datatype(crp->p->type), "BLANK",
595                       crp->p->blankptrwrite, "Pixels with no data.",
596                       &status) )
597       gal_fits_io_error(status, "adding Blank");
598   totsize = naxes[0]*naxes[1] * (ndim==3?naxes[2]:1);
599   if(fits_write_null_img(ofp, 1, totsize, &status))
600     gal_fits_io_error(status, "writing null array");
601 
602 
603   /* Write the WCS header keywords in the output FITS image, then
604      update the header keywords. */
605   if(img->wcs)
606     {
607       /* Write the WCS title and common WCS information. */
608       gal_fits_key_write_wcsstr(ofp, img->wcs, img->wcstxt, img->nwcskeys);
609 
610       /* Correct the CRPIX keywords. */
611       for(i=0;i<ndim;++i)
612         {
613           sprintf(cpname, "CRPIX%zu", i+1);
614           crpix = img->wcs->crpix[i] - (fpixel_i[i]-1) + (fpixel_c[i]-1);
615           fits_update_key(ofp, TDOUBLE, cpname, &crpix, NULL, &status);
616           gal_fits_io_error(status, NULL);
617         }
618     }
619 
620 
621   /* Add the Crop information title, the actual keywords will be written
622      later. The keywords will contain the pixels of the is cropped region
623      from input image(s): */
624   gal_fits_key_write_title_in_ptr("Crop information", ofp);
625 }
626 
627 
628 
629 
630 
631 /* The starting and ending points are set in the onecropparams structure
632    for one crop from one image. Crop that region out of the input.
633 
634    On'basekeyname': To be safe, GCC 8.1 (and persumably later versions)
635    assumes that we are writing the full statically allocated space into
636    'regioinkey'! So it prints a warning that you may be writing outside the
637    allocated space! With these variables, we are ultimately just writing
638    the file counters, so we can never (with current techologies!!!) exceed
639    'FLEN_KEYWORD' (which is 75 characters). To avoid compiler warnings, we
640    are just removing a few characters ('FLEN_KEYWORD-5') to allow the
641    suffix and remove the warnings. */
642 int
onecrop(struct onecropparams * crp)643 onecrop(struct onecropparams *crp)
644 {
645   struct cropparams *p=crp->p;
646   struct inputimgs *img=&p->imgs[crp->in_ind];
647 
648   void *array;
649   int returnvalue=1;
650   int status=0, anynul=0;
651   fitsfile *ifp=crp->infits, *ofp;
652   char basekeyname[FLEN_KEYWORD-5];     /* '-5': avoid gcc 8.1+ warnings! */
653   gal_fits_list_key_t *headers=NULL;    /* See above comment for more.    */
654   size_t i, j, cropsize=1, ndim=img->ndim;
655   char region[FLEN_VALUE], regionkey[FLEN_KEYWORD];
656   long fpixel_o[MAXDIM], lpixel_o[MAXDIM], inc[MAXDIM];
657   long naxes[MAXDIM], fpixel_i[MAXDIM], lpixel_i[MAXDIM];
658 
659   /* Fill the 'naxes' and 'inc' arrays. */
660   for(i=0;i<ndim;++i)
661     {
662       inc[ i ]   = 1;
663       naxes[ i ] = img->dsize[ ndim - i - 1 ];
664     }
665 
666 
667   /* Find the first and last pixel of this crop box from this input
668      image. Then copy the first and last pixels into the '_i' arrays.*/
669   onecrop_flpixel(crp);
670   memcpy(fpixel_i, crp->fpixel, ndim*sizeof *fpixel_i);
671   memcpy(lpixel_i, crp->lpixel, ndim*sizeof *lpixel_i);
672 
673 
674   /* Find the overlap and apply it if there is any overlap. */
675   if( gal_box_overlap(naxes, fpixel_i, lpixel_i, fpixel_o, lpixel_o, ndim) )
676     {
677       /* Make the output FITS image and initialize it with an array of
678          NaN or BLANK values. */
679       if(crp->outfits==NULL)
680         onecrop_make_array(crp, fpixel_i, lpixel_i, fpixel_o, lpixel_o);
681       ofp=crp->outfits;
682 
683 
684       /* Allocate an array to keep the desired crop region, then read
685          the desired pixels into it. */
686       status=0;
687       for(i=0;i<ndim;++i) cropsize *= ( lpixel_i[i] - fpixel_i[i] + 1 );
688       array=gal_pointer_allocate(p->type, cropsize, 0, __func__, "array");
689       if(fits_read_subset(ifp, gal_fits_type_to_datatype(p->type),
690                           fpixel_i, lpixel_i, inc, p->blankptrread, array,
691                           &anynul, &status))
692         gal_fits_io_error(status, NULL);
693 
694 
695       /* If we have a floating point or double image, pixels with zero
696          value should actually be a NaN. Unless the user specificly
697          asks for it, make the conversion.*/
698       if(p->zeroisnotblank==0
699          && (p->type==GAL_TYPE_FLOAT32
700              || p->type==GAL_TYPE_FLOAT64) )
701         onecrop_zero_to_nan(array, cropsize, p->type);
702 
703 
704       /* If a polygon is given, remove all the pixels within or
705          outside of it.*/
706       if(p->polygon)
707         {
708           /* A small sanity check until this part supports 3D. */
709           if(ndim!=2)
710             error(EXIT_FAILURE, 0, "%s: polygons only implemented in 2D",
711                   __func__);
712 
713           /* In WCS mode, crp->ipolygon was allocated and filled in
714              wcspolygonflpixel (wcsmode.c). */
715           if(p->mode==IMGCROP_MODE_IMG) crp->ipolygon=p->ipolygon;
716           polygonmask(crp, array, fpixel_i, lpixel_i[1]-fpixel_i[1]+1,
717                       lpixel_i[0]-fpixel_i[0]+1);
718           if(p->mode==IMGCROP_MODE_WCS) free(crp->ipolygon);
719         }
720 
721 
722       /* Write the array into the image. */
723       status=0;
724       if( fits_write_subset(ofp, gal_fits_type_to_datatype(p->type),
725                             fpixel_o, lpixel_o, array, &status) )
726         gal_fits_io_error(status, NULL);
727 
728 
729       /* Write the selected region of this image as a string to include as
730          a FITS keyword. Then we want to delete the last coma ','.*/
731       j=0;
732       for(i=0;i<ndim;++i)
733         j += sprintf(&region[j], "%ld:%ld,", fpixel_i[i], lpixel_i[i]);
734       region[j-1]='\0';
735 
736 
737       /* A section has been added to the cropped image from this input
738          image, so save the information of this image. */
739       sprintf(basekeyname, "ICF%zu", crp->numimg);
740       gal_fits_key_write_filename(basekeyname, img->name, &headers, 0,
741                                   p->cp.quiet);
742       sprintf(regionkey, "%sPIX", basekeyname);
743       gal_fits_key_list_add_end(&headers, GAL_TYPE_STRING, regionkey,
744                                 0, region, 0, "Range of pixels used for "
745                                 "this output.", 0, NULL, 0);
746       gal_fits_key_write_in_ptr(&headers, ofp);
747 
748 
749       /* Free the allocated array. */
750       free(array);
751     }
752   else
753     {
754       returnvalue=0;
755       if(p->polygon && p->polygonout==0 && p->mode==IMGCROP_MODE_WCS)
756         free(crp->ipolygon);
757     }
758 
759   /* The crop is complete. */
760   return returnvalue;
761 }
762 
763 
764 
765 
766 
767 
768 
769 
770 
771 
772 
773 
774 
775 
776 
777 
778 
779 
780 
781 
782 /*******************************************************************/
783 /******************        Check center        *********************/
784 /*******************************************************************/
785 int
onecrop_center_filled(struct onecropparams * crp)786 onecrop_center_filled(struct onecropparams *crp)
787 {
788   struct cropparams *p=crp->p;
789 
790   void *array;
791   size_t size, ndim, *dsize;
792   fitsfile *ofp=crp->outfits;
793   int status=0, anynul=0, type;
794   long checkcenter=p->checkcenter;
795   long naxes[3], fpixel[3], lpixel[3], inc[3]={1,1,1};
796 
797   /* If checkcenter is zero, then don't check. */
798   if(checkcenter==0) return GAL_BLANK_UINT8;
799 
800   /* Get the final size of the output image. */
801   gal_fits_img_info(ofp, &type, &ndim, &dsize, NULL, NULL);
802   if(ndim==2)
803     {
804       naxes[0]=dsize[1];
805       naxes[1]=dsize[0];
806     }
807   else
808     {
809       naxes[0]=dsize[2];
810       naxes[1]=dsize[1];
811       naxes[2]=dsize[0];
812     }
813 
814   /* Get the size and range of the central region to check. The +1 is
815      because in FITS, counting begins from 1, not zero. It might happen
816      that the image is actually smaller than the width to check the center
817      (for example 1 or 2 pixels wide). In that case, we'll just use the
818      full image to check. */
819   size = ( (naxes[0]>checkcenter ? checkcenter : naxes[0])
820            * (naxes[1]>checkcenter ? checkcenter : naxes[1]) );
821   fpixel[0] = naxes[0]>checkcenter ? ((naxes[0]/2+1)-checkcenter/2) : 1;
822   fpixel[1] = naxes[1]>checkcenter ? ((naxes[1]/2+1)-checkcenter/2) : 1;
823   lpixel[0] = ( naxes[0]>checkcenter
824                 ? ((naxes[0]/2+1)+checkcenter/2) : naxes[0] );
825   lpixel[1] = ( naxes[1]>checkcenter
826                 ? ((naxes[1]/2+1)+checkcenter/2) : naxes[1] );
827 
828 
829   /* For the third dimension. */
830   if(ndim==3)
831     {
832       size *= (naxes[2]>checkcenter ? checkcenter : naxes[2]);
833       fpixel[2] = naxes[2]>checkcenter ? ((naxes[2]/2+1)-checkcenter/2) : 1;
834       lpixel[2] = ( naxes[2]>checkcenter
835                     ? ((naxes[2]/2+1)+checkcenter/2) : naxes[2] );
836     }
837 
838 
839   /* For a check:
840   printf("naxes: %ld, %ld\nfpixel: (%ld, %ld)\nlpixel: (%ld, %ld)\n"
841          "size: %zu\n", naxes[0], naxes[1], fpixel[0], fpixel[1],
842          lpixel[0], lpixel[1], size);
843   */
844 
845   /* Allocate the array and read in the pixels. */
846   array=gal_pointer_allocate(type, size, 0, __func__, "array");
847   if( fits_read_subset(ofp, gal_fits_type_to_datatype(type), fpixel, lpixel,
848                        inc, p->blankptrread, array, &anynul, &status) )
849     gal_fits_io_error(status, NULL);
850   free(array);
851 
852   /* CFITSIO already checks if there are any blank pixels. If there are,
853      then 'anynul' will be 1, if there aren't it will be 0. So the output
854      of this function is just the inverse of that number. */
855   return !anynul;
856 }
857