18 from __future__
import division, print_function
24 import _SourceXtractorPy
as cpp
26 if sys.version_info.major < 3:
27 from StringIO
import StringIO
29 from io
import StringIO
32 measurement_images = {}
36 super(FitsFile, self).
__init__(str(filename))
50 d[a.key()] = headers[a.key()]
58 A MeasurementImage is the processing unit for SourceXtractor++. Measurements and model fitting can be done
59 over one, or many, of them. It models the image, plus its associated weight file, PSF, etc.
63 fits_file : str or FitsFile object
64 The path to a FITS image, or an instance of FitsFile
66 The path to a PSF. It can be either a FITS image, or a PSFEx model.
67 weight_file : str or FitsFile
68 The path to a FITS image with the pixel weights, or an instance of FitsFile
70 Image gain. If None, `gain_keyword` will be used instead.
72 Keyword for the header containing the gain.
74 Saturation value. If None, `saturation_keyword` will be used instead.
75 saturation_keyword : str
76 Keyword for the header containing the saturation value.
78 Flux scaling. Each pixel value will be multiplied by this. If None, `flux_scale_keyword` will be used
80 flux_scale_keyword : str
81 Keyword for the header containing the flux scaling.
83 The type of the weight image. It must be one of:
86 The image itself is used to compute internally a constant variance (default)
88 The image itself is used to compute internally a variance map
90 The weight image must contain a weight-map in units of absolute standard deviations
93 The weight image must contain a weight-map in units of relative variance.
95 The weight image must contain a weight-map in units of relative weights. The data are converted
97 weight_absolute : bool
98 If False, the weight map will be scaled according to an absolute variance map built from the image itself.
99 weight_scaling : float
100 Apply an scaling to the weight map.
101 weight_threshold : float
102 Pixels with weights beyond this value are treated just like pixels discarded by the masking process.
103 constant_background : float
104 If set a constant background of that value is assumed for the image instead of using automatic detection
106 For multi-extension FITS file specifies the HDU number for the image. Default 0 (primary HDU)
108 For multi-extension FITS file specifies the HDU number for the psf. Defaults to the same value as image_hdu
110 For multi-extension FITS file specifies the HDU number for the weight. Defaults to the same value as image_hdu
113 def __init__(self, fits_file, psf_file=None, weight_file=None, gain=None,
114 gain_keyword='GAIN', saturation=None, saturation_keyword='SATURATE',
115 flux_scale=None, flux_scale_keyword='FLXSCALE',
116 weight_type='none', weight_absolute=False, weight_scaling=1.,
117 weight_threshold=None, constant_background=None,
118 image_hdu=0, psf_hdu=None, weight_hdu=None
123 if isinstance(fits_file, FitsFile):
125 file_path = fits_file.filename
128 file_path = fits_file
130 if isinstance(weight_file, FitsFile):
131 weight_file = weight_file.filename
133 super(MeasurementImage, self).
__init__(os.path.abspath(file_path),
134 os.path.abspath(psf_file)
if psf_file
else '',
135 os.path.abspath(weight_file)
if weight_file
else '')
137 if image_hdu < 0
or (weight_hdu
is not None and weight_hdu < 0)
or (psf_hdu
is not None and psf_hdu < 0):
138 raise ValueError(
'HDU indices start at 0')
141 'IMAGE_FILENAME': self.file,
142 'PSF_FILENAME': self.psf_file,
143 'WEIGHT_FILENAME': self.weight_file
146 self.
meta.update(hdu_list.get_headers(image_hdu))
150 elif gain_keyword
in self.
meta:
151 self.
gain = float(self.
meta[gain_keyword])
155 if saturation
is not None:
157 elif saturation_keyword
in self.
meta:
162 if flux_scale
is not None:
164 elif flux_scale_keyword
in self.
meta:
172 if weight_threshold
is None:
178 if constant_background
is not None:
192 if weight_hdu
is None:
197 global measurement_images
198 measurement_images[self.id] = self
205 Human readable representation for the object
207 return 'Image {}: {} / {}, PSF: {} / {}, Weight: {} / {}'.format(
214 Print a human-readable representation of the configured measurement images.
219 Where to print the representation. Defaults to sys.stderr
221 print(
'Measurement images:', file=file)
222 for i
in measurement_images:
223 im = measurement_images[i]
224 print(
'Image {}'.format(i), file=file)
225 print(
' File: {}'.format(im.file), file=file)
226 print(
' PSF: {}'.format(im.psf_file), file=file)
227 print(
' Weight: {}'.format(im.weight_file), file=file)
232 Models the grouping of images. Measurement can *not* be made directly on instances of this type.
233 The configuration must be "frozen" before creating a MeasurementGroup
242 Constructor. It is not recommended to be used directly. Use instead load_fits_image or load_fits_images.
247 if len(kwargs) != 1
or (
'images' not in kwargs
and 'subgroups' not in kwargs):
248 raise ValueError(
'ImageGroup only takes as parameter one of "images" or "subgroups"')
249 key = list(kwargs.keys())[0]
251 if isinstance(kwargs[key], list):
255 if key ==
'subgroups':
269 How may subgroups or images are there in this group
278 Allows to iterate on the contained subgroups or images
295 Splits the group in various subgroups, applying a filter on the contained images. If the group has
296 already been split, applies the split to each subgroup.
300 grouping_method : callable
301 A callable that receives as a parameter the list of contained images, and returns
302 a list of tuples, with the grouping key value, and the list of grouped images belonging to the given key.
312 If some images have not been grouped by the callable.
317 sub_group.split(grouping_method)
319 subgrouped_images = grouping_method(self.
__images)
320 if sum(len(p[1])
for p
in subgrouped_images) != len(self.
__images):
322 raise ValueError(
'Some images were not grouped')
324 for k, im_list
in subgrouped_images:
332 Add new images to the group.
336 images : list of, or a single, MeasurementImage
341 If the group has been split, no new images can be added.
344 raise ValueError(
'ImageGroup is already subgrouped')
345 if isinstance(images, MeasurementImage):
352 Add a subgroup to a group.
357 The new of the new group
362 raise Exception(
'ImageGroup is not subgrouped yet')
364 raise Exception(
'Subgroup {} alread exists'.format(name))
373 True if the group is a leaf group
384 The name of the subgroup.
394 If the group has not been split.
396 If the group has not been found.
399 raise ValueError(
'ImageGroup is not subgrouped yet')
401 return next(x
for x
in self.
__subgroups if x[0] == name)[1]
402 except StopIteration:
403 raise KeyError(
'Group {} not found'.format(name))
405 def print(self, prefix='', show_images=False, file=sys.stderr):
407 Print a human-readable representation of the group.
412 Print each line with this prefix. Used internally for indentation.
414 Show the images belonging to a leaf group.
416 Where to print the representation. Defaults to sys.stderr
419 print(
'{}Image List ({})'.format(prefix, len(self.
__images)), file=file)
422 print(
'{} {}'.format(prefix, im), file=file)
424 print(
'{}Image sub-groups: {}'.format(prefix,
','.
join(str(x)
for x, _
in self.
__subgroups)), file=file)
426 print(
'{} {}:'.format(prefix, name), file=file)
427 group.print(prefix +
' ', show_images, file)
434 A human-readable representation of the group
437 self.
print(show_images=
True, file=string)
438 return string.getvalue()
441 """Creates an image group with the images of a (possibly multi-HDU) single FITS file.
443 If image is multi-hdu, psf and weight can either be multi hdu or lists of individual files.
445 In any case, they are matched in order and HDUs not containing images (two dimensional arrays) are ignored.
447 :param image: The filename of the FITS file containing the image(s)
448 :param psf: psf file or list of psf files
449 :param weight: FITS file for the weight image or a list of such files
451 :return: A ImageGroup representing the images
455 image_hdu_idx = image_hdu_list.hdu_list
458 if isinstance(psf, list):
459 if len(psf) != len(image_hdu_idx):
460 raise ValueError(
"The number of psf files must match the number of images!")
462 psf_hdu_idx = [0] * len(psf_list)
464 psf_list = [psf] * len(image_hdu_idx)
465 psf_hdu_idx = range(len(image_hdu_idx))
468 if isinstance(weight, list):
469 if len(weight) != len(image_hdu_idx):
470 raise ValueError(
"The number of weight files must match the number of images!")
472 weight_hdu_idx = [0] * len(weight_list)
474 weight_list = [
None] * len(image_hdu_idx)
475 weight_hdu_idx = [0] * len(weight_list)
478 weight_hdu_idx = weight_hdu_list.hdu_list
479 weight_list = [weight_hdu_list] * len(image_hdu_idx)
482 for hdu, psf_file, psf_hdu, weight_file, weight_hdu
in zip(
483 image_hdu_idx, psf_list, psf_hdu_idx, weight_list, weight_hdu_idx):
485 image_hdu=hdu, psf_hdu=psf_hdu, weight_hdu=weight_hdu, **kwargs))
490 """Creates an image group for the given images.
495 A list of relative paths to the images FITS files. Can also be single string in which case,
496 this function acts like load_fits_image
498 A list of relative paths to the PSF FITS files (optional). It must match the length of image_list or be None.
499 weights : list of str
500 A list of relative paths to the weight files (optional). It must match the length of image_list or be None.
505 A ImageGroup representing the images
510 In case of mismatched list of files
513 if isinstance(images, list):
515 raise ValueError(
"An empty list passed to load_fits_images")
517 psfs = psfs
or [
None] * len(images)
518 weights = weights
or [
None] * len(images)
520 if not isinstance(psfs, list)
or len(psfs) != len(images):
521 raise ValueError(
"The number of image files and psf files must match!")
523 if not isinstance(weights, list)
or len(weights) != len(images):
524 raise ValueError(
"The number of image files and weight files must match!")
527 for f, p, w
in zip(images, psfs, weights):
540 Callable that can be used to split an ImageGroup by a keyword value (i.e. FILTER).
545 FITS header keyword (i.e. FILTER)
562 images : list of MeasurementImage
563 List of images to group
567 list of tuples of str and list of MeasurementImage
569 (R, [frame_r_01.fits, frame_r_02.fits]),
570 (G, [frame_g_01.fits, frame_g_02.fits])
575 if self.
__key not in im.meta:
576 raise KeyError(
'The image {}[{}] does not contain the key {}'.format(
577 im.meta[
'IMAGE_FILENAME'], im.image_hdu, self.
__key
579 if im.meta[self.
__key]
not in result:
580 result[im.meta[self.
__key]] = []
581 result[im.meta[self.
__key]].append(im)
582 return [(k, result[k])
for k
in result]
587 Callable that can be used to split an ImageGroup by a keyword value (i.e. FILTER), applying a regular
588 expression and using the first matching group as key.
595 Regular expression. The first matching group will be used as grouping key.
613 images : list of MeasurementImage
614 List of images to group
618 list of tuples of str and list of MeasurementImage
622 if self.
__key not in im.meta:
623 raise KeyError(
'The image {}[{}] does not contain the key {}'.format(
624 im.meta[
'IMAGE_FILENAME'], im.image_hdu, self.
__key
627 if group
not in result:
629 result[group].append(im)
630 return [(k, result[k])
for k
in result]
635 Once an instance of this class is created from an ImageGroup, its configuration is "frozen". i.e.
636 no new images can be added, or no new grouping applied.
640 image_group : ImageGroup
645 def __init__(self, image_group, is_subgroup=False):
651 if image_group.is_leaf():
652 self.
__images = [im
for im
in image_group]
656 MeasurementGroup._all_groups.append(self)
671 The subgroup with the given name or image with the given index depending on whether this is a leaf group.
676 Subgroup name or image index
680 MeasurementGroup or MeasurementImage
685 If we can't find what we want
690 return next(x
for x
in self.
__subgroups if x[0] == index)[1]
691 except StopIteration:
692 raise KeyError(
'Group {} not found'.format(index))
697 raise KeyError(
'Image #{} not found'.format(index))
704 Number of subgroups, or images contained within the group
716 True if the group is a leaf group
720 def print(self, prefix='', show_images=False, file=sys.stderr):
722 Print a human-readable representation of the group.
727 Print each line with this prefix. Used internally for indentation.
729 Show the images belonging to a leaf group.
731 Where to print the representation. Defaults to sys.stderr
734 print(
'{}Image List ({})'.format(prefix, len(self.
__images)), file=file)
737 print(
'{} {}'.format(prefix, im), file=file)
739 print(
'{}Measurement sub-groups: {}'.format(prefix,
','.
join(
742 print(
'{} {}:'.format(prefix, name), file=file)
743 group.print(prefix +
' ', show_images, file=file)
750 A human-readable representation of the group
753 self.
print(show_images=
True, file=string)
754 return string.getvalue()