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':
264 self.__subgroup_names.add(name)
275 How may subgroups or images are there in this group
284 Allows to iterate on the contained subgroups or images
295 return self.__subgroups.__iter__()
297 return self.__images.__iter__()
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:
332 self.__subgroup_names.add(k)
333 self.__subgroups.append((k,
ImageGroup(images=im_list)))
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):
352 self.__images.append(images)
354 self.__images.extend(images)
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))
371 self.__subgroup_names.add(name)
372 self.__subgroups.append((name, group))
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)
688 return self.__subgroups.__iter__()
690 return self.__images.__iter__()
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)...))