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(®ion[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