18 from __future__
import division, print_function
24 from astropy.io
import fits
26 import _SourceXtractorPy
as cpp
28 if sys.version_info.major < 3:
29 from StringIO
import StringIO
31 from io
import StringIO
34 measurement_images = {}
39 A MeasurementImage is the processing unit for SourceXtractor++. Measurements and model fitting can be done 40 over one, or many, of them. It models the image, plus its associated weight file, PSF, etc. 44 fits_file : str or astropy.fits.HDUList 45 The path to a FITS image, or an instance of astropy.fits.HDUList 47 The path to a PSF. It can be either a FITS image, or a PSFEx model. 48 weight_file : str or astropy.fits.HDUList 49 The path to a FITS image with the pixel weights, or an instance of astropy.fits.HDUList 51 Image gain. If None, `gain_keyword` will be used instead. 53 Keyword for the header containing the gain. 55 Saturation value. If None, `saturation_keyword` will be used instead. 56 saturation_keyword : str 57 Keyword for the header containing the saturation value. 59 Flux scaling. Each pixel value will be multiplied by this. If None, `flux_scale_keyword` will be used 61 flux_scale_keyword : str 62 Keyword for the header containing the flux scaling. 64 The type of the weight image. It must be one of: 67 The image itself is used to compute internally a constant variance (default) 69 The image itself is used to compute internally a variance map 71 The weight image must contain a weight-map in units of absolute standard deviations 74 The weight image must contain a weight-map in units of relative variance. 76 The weight image must contain a weight-map in units of relative weights. The data are converted 78 weight_absolute : bool 79 If False, the weight map will be scaled according to an absolute variance map built from the image itself. 80 weight_scaling : float 81 Apply an scaling to the weight map. 82 weight_threshold : float 83 Pixels with weights beyond this value are treated just like pixels discarded by the masking process. 84 constant_background : float 85 If set a constant background of that value is assumed for the image instead of using automatic detection 87 For multi-extension FITS file specifies the HDU number for the image. Default 1 (primary HDU) 89 For multi-extension FITS file specifies the HDU number for the psf. Defaults to the same value as image_hdu 91 For multi-extension FITS file specifies the HDU number for the weight. Defaults to the same value as image_hdu 94 def __init__(self, fits_file, psf_file=None, weight_file=None, gain=None,
95 gain_keyword='GAIN', saturation=None, saturation_keyword='SATURATE',
96 flux_scale=None, flux_scale_keyword='FLXSCALE',
97 weight_type='none', weight_absolute=False, weight_scaling=1.,
98 weight_threshold=None, constant_background=None,
99 image_hdu=1, psf_hdu=None, weight_hdu=None
104 if isinstance(fits_file, fits.HDUList):
106 file_path = fits_file.filename()
108 hdu_list = fits.open(fits_file)
109 file_path = fits_file
111 if isinstance(weight_file, fits.HDUList):
112 weight_file = weight_file.filename()
114 super(MeasurementImage, self).
__init__(os.path.abspath(file_path),
115 os.path.abspath(psf_file)
if psf_file
else '',
116 os.path.abspath(weight_file)
if weight_file
else '')
118 if image_hdu <= 0
or (weight_hdu
is not None and weight_hdu <= 0)
or (psf_hdu
is not None and psf_hdu <= 0):
119 raise ValueError(
'HDU indexes start at 1')
122 'IMAGE_FILENAME': self.file,
123 'PSF_FILENAME': self.psf_file,
124 'WEIGHT_FILENAME': self.weight_file
127 self.
meta.update(hdu_list[image_hdu-1].header)
132 elif gain_keyword
in self.
meta:
133 self.
gain = float(self.
meta[gain_keyword])
137 if saturation
is not None:
139 elif saturation_keyword
in self.
meta:
144 if flux_scale
is not None:
146 elif flux_scale_keyword
in self.
meta:
154 if weight_threshold
is None:
160 if constant_background
is not None:
174 if weight_hdu
is None:
179 global measurement_images
180 measurement_images[self.id] = self
184 pre, ext = os.path.splitext(filename)
185 header_file = pre +
".head" 188 if os.path.exists(header_file):
189 print(
"processing ascii header file: " + header_file)
191 with open(header_file)
as f:
193 line = re.sub(
"\\s*$",
"", line)
197 if line.upper() ==
"END":
199 elif current_hdu == hdu:
200 m = re.match(
"([^=]{1,8})=([^\\/]*)(.*)", line)
202 keyword = m.group(1).strip().upper()
203 value = m.group(2).strip()
204 self.
meta[keyword] = value
211 Human readable representation for the object 213 return 'Image {}: {} / {}, PSF: {} / {}, Weight: {} / {}'.format(
220 Print a human-readable representation of the configured measurement images. 225 Where to print the representation. Defaults to sys.stderr 227 print(
'Measurement images:', file=file)
228 for i
in measurement_images:
229 im = measurement_images[i]
230 print(
'Image {}'.format(i), file=file)
231 print(
' File: {}'.format(im.file), file=file)
232 print(
' PSF: {}'.format(im.psf_file), file=file)
233 print(
' Weight: {}'.format(im.weight_file), file=file)
238 Models the grouping of images. Measurement can *not* be made directly on instances of this type. 239 The configuration must be "frozen" before creating a MeasurementGroup 248 Constructor. It is not recommended to be used directly. Use instead load_fits_image or load_fits_images. 253 if len(kwargs) != 1
or (
'images' not in kwargs
and 'subgroups' not in kwargs):
254 raise ValueError(
'ImageGroup only takes as parameter one of "images" or "subgroups"')
255 key = list(kwargs.keys())[0]
257 if isinstance(kwargs[key], list):
261 if key ==
'subgroups':
275 How may subgroups or images are there in this group 284 Allows to iterate on the contained subgroups or images 301 Splits the group in various subgroups, applying a filter on the contained images. If the group has 302 already been split, applies the split to each subgroup. 306 grouping_method : callable 307 A callable that receives as a parameter the list of contained images, and returns 308 a list of tuples, with the grouping key value, and the list of grouped images belonging to the given key. 318 If some images have not been grouped by the callable. 323 sub_group.split(grouping_method)
325 subgrouped_images = grouping_method(self.
__images)
326 if sum(len(p[1])
for p
in subgrouped_images) != len(self.
__images):
328 raise ValueError(
'Some images were not grouped')
330 for k, im_list
in subgrouped_images:
338 Add new images to the group. 342 images : list of, or a single, MeasurementImage 347 If the group has been split, no new images can be added. 350 raise ValueError(
'ImageGroup is already subgrouped')
351 if isinstance(images, MeasurementImage):
358 Add a subgroup to a group. 363 The new of the new group 368 raise Exception(
'ImageGroup is not subgrouped yet')
370 raise Exception(
'Subgroup {} alread exists'.format(name))
379 True if the group is a leaf group 390 The name of the subgroup. 400 If the group has not been split. 402 If the group has not been found. 405 raise ValueError(
'ImageGroup is not subgrouped yet')
407 return next(x
for x
in self.
__subgroups if x[0] == name)[1]
408 except StopIteration:
409 raise KeyError(
'Group {} not found'.format(name))
411 def print(self, prefix='', show_images=False, file=sys.stderr):
413 Print a human-readable representation of the group. 418 Print each line with this prefix. Used internally for indentation. 420 Show the images belonging to a leaf group. 422 Where to print the representation. Defaults to sys.stderr 425 print(
'{}Image List ({})'.format(prefix, len(self.
__images)), file=file)
428 print(
'{} {}'.format(prefix, im), file=file)
430 print(
'{}Image sub-groups: {}'.format(prefix,
','.
join(str(x)
for x, _
in self.
__subgroups)), file=file)
432 print(
'{} {}:'.format(prefix, name), file=file)
433 group.print(prefix +
' ', show_images, file)
440 A human-readable representation of the group 443 self.
print(show_images=
True, file=string)
444 return string.getvalue()
454 for key, value
in kwargs.items():
455 if key
not in self.
kwargs:
456 mismatch.append(
'{} {} != undefined'.format(key, value))
457 elif self.
kwargs[key] != value:
458 mismatch.append(
'{} {} != {}'.format(key, value, self.
kwargs[key]))
464 """Creates an image group with the images of a (possibly multi-HDU) single FITS file. 466 If image is multi-hdu, psf and weight can either be multi hdu or lists of individual files. 468 In any case, they are matched in order and HDUs not containing images (two dimensional arrays) are ignored. 470 :param image: The FITS file containing the image(s) 471 :param psf: psf file or list of psf files 472 :param weight: FITS file for the weight image or a list of such files 474 :return: A ImageGroup representing the images 477 image_hdu_list = fits.open(image)
478 image_hdu_idx = [i
for i, hdu
in enumerate(image_hdu_list)
if hdu.is_image
and hdu.header[
'NAXIS'] == 2]
481 if isinstance(psf, list):
482 if len(psf) != len(image_hdu_idx):
483 raise ValueError(
"The number of psf files must match the number of images!")
485 psf_hdu_idx = [0] * len(psf_list)
487 psf_list = [psf] * len(image_hdu_idx)
488 psf_hdu_idx = range(len(image_hdu_idx))
491 if isinstance(weight, list):
492 if len(weight) != len(image_hdu_idx):
493 raise ValueError(
"The number of weight files must match the number of images!")
495 weight_hdu_idx = [0] * len(weight_list)
497 weight_list = [
None] * len(image_hdu_idx)
498 weight_hdu_idx = [0] * len(weight_list)
500 weight_hdu_list = fits.open(weight)
501 weight_hdu_idx = [i
for i, hdu
in enumerate(weight_hdu_list)
if hdu.is_image
and hdu.header[
'NAXIS'] == 2]
502 weight_list = [weight_hdu_list] * len(image_hdu_idx)
505 for hdu, psf_file, psf_hdu, weight_file, weight_hdu
in zip(
506 image_hdu_idx, psf_list, psf_hdu_idx, weight_list, weight_hdu_idx):
508 image_hdu=hdu+1, psf_hdu=psf_hdu+1, weight_hdu=weight_hdu+1, **kwargs))
513 """Creates an image group for the given images. 518 A list of relative paths to the images FITS files. Can also be single string in which case, 519 this function acts like load_fits_image 521 A list of relative paths to the PSF FITS files (optional). It must match the length of image_list or be None. 522 weights : list of str 523 A list of relative paths to the weight files (optional). It must match the length of image_list or be None. 528 A ImageGroup representing the images 533 In case of mismatched list of files 536 if isinstance(images, list):
538 raise ValueError(
"An empty list passed to load_fits_images")
540 psfs = psfs
or [
None] * len(images)
541 weights = weights
or [
None] * len(images)
543 if not isinstance(psfs, list)
or len(psfs) != len(images):
544 raise ValueError(
"The number of image files and psf files must match!")
546 if not isinstance(weights, list)
or len(weights) != len(images):
547 raise ValueError(
"The number of image files and weight files must match!")
550 for f, p, w
in zip(images, psfs, weights):
563 Callable that can be used to split an ImageGroup by a keyword value (i.e. FILTER). 568 FITS header keyword (i.e. FILTER) 585 images : list of MeasurementImage 586 List of images to group 590 list of tuples of str and list of MeasurementImage 592 (R, [frame_r_01.fits, frame_r_02.fits]), 593 (G, [frame_g_01.fits, frame_g_02.fits]) 598 if self.
__key not in im.meta:
599 raise KeyError(
'The image {}[{}] does not contain the key {}'.format(
600 im.meta[
'IMAGE_FILENAME'], im.image_hdu, self.
__key 602 if im.meta[self.
__key]
not in result:
603 result[im.meta[self.
__key]] = []
604 result[im.meta[self.
__key]].append(im)
605 return [(k, result[k])
for k
in result]
610 Callable that can be used to split an ImageGroup by a keyword value (i.e. FILTER), applying a regular 611 expression and using the first matching group as key. 618 Regular expression. The first matching group will be used as grouping key. 636 images : list of MeasurementImage 637 List of images to group 641 list of tuples of str and list of MeasurementImage 645 if self.
__key not in im.meta:
646 raise KeyError(
'The image {}[{}] does not contain the key {}'.format(
647 im.meta[
'IMAGE_FILENAME'], im.image_hdu, self.
__key 650 if group
not in result:
652 result[group].append(im)
653 return [(k, result[k])
for k
in result]
658 Once an instance of this class is created from an ImageGroup, its configuration is "frozen". i.e. 659 no new images can be added, or no new grouping applied. 663 image_group : ImageGroup 668 def __init__(self, image_group, is_subgroup=False):
674 if image_group.is_leaf():
675 self.
__images = [im
for im
in image_group]
679 MeasurementGroup._all_groups.append(self)
694 The subgroup with the given name or image with the given index depending on whether this is a leaf group. 699 Subgroup name or image index 703 MeasurementGroup or MeasurementImage 708 If we can't find what we want 713 return next(x
for x
in self.
__subgroups if x[0] == index)[1]
714 except StopIteration:
715 raise KeyError(
'Group {} not found'.format(index))
720 raise KeyError(
'Image #{} not found'.format(index))
727 Number of subgroups, or images contained within the group 739 True if the group is a leaf group 743 def print(self, prefix='', show_images=False, file=sys.stderr):
745 Print a human-readable representation of the group. 750 Print each line with this prefix. Used internally for indentation. 752 Show the images belonging to a leaf group. 754 Where to print the representation. Defaults to sys.stderr 757 print(
'{}Image List ({})'.format(prefix, len(self.
__images)), file=file)
760 print(
'{} {}'.format(prefix, im), file=file)
762 print(
'{}Measurement sub-groups: {}'.format(prefix,
','.
join(
765 print(
'{} {}:'.format(prefix, name), file=file)
766 group.print(prefix +
' ', show_images, file=file)
773 A human-readable representation of the group 776 self.
print(show_images=
True, file=string)
777 return string.getvalue()
ELEMENTS_API auto join(Args &&... args) -> decltype(joinPath(std::forward< Args >(args)...))