Source code for pywi.io.images

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright (c) 2016 Jérémie DECOCK (http://www.jdhp.org)

# This script is provided under the terms and conditions of the MIT license:
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

__all__ = ['fill_nan_pixels',
           'image_files_in_dir',
           'image_files_in_paths',
           'load_image',
           'save_image']

import numpy as np

import os

from pywi.io.pil import load_pil_image, save_pil_image
from pywi.io.fits import load_fits_image, save_fits_image
from pywi.io.plot import plot

DEBUG = False


# FILL NAN PIXELS #############################################################

[docs]def fill_nan_pixels(image, noise_distribution=None): """Replace *in-place* `NaN` values in `image` by zeros or by random noise. Images containing `NaN` values generate undesired harmonics with wavelet image cleaning. This function should be used to "fix" images before each wavelet image cleaning. Replace `NaN` ("Not a Number") values in `image` by zeros if `noise_distribution` is `None`. Otherwise, `NaN` values are replaced by random noise drawn by the `noise_distribution` random generator. Parameters ---------- image : array_like The image to process. `NaN` values are replaced **in-place** thus this function changes the provided object. noise_distribution : `pywi.denoising.inverse_transform_sampling.EmpiricalDistribution` The random generator to use to replace `NaN` pixels by random noise. Returns ------- array_like Returns a boolean mask array indicating whether pixels in `images` initially contained `NaN` values (`True`) of not (`False`). This array is defined by the instruction `np.isnan(image)`. Notes ----- `NaN` values are replaced **in-place** in the provided `image` parameter. Examples -------- >>> import numpy as np >>> img = np.array([[1, 2, np.nan],[4, np.nan, 6],[np.nan, 8, np.nan]]) >>> fill_nan_pixels(img) ... # doctest: +NORMALIZE_WHITESPACE array([[False, False, True], [False, True, False], [ True, False, True]], dtype=bool) >>> img ... # doctest: +NORMALIZE_WHITESPACE array([[ 1., 2., 0.], [ 4., 0., 6.], [ 0., 8., 0.]]) """ # See https://stackoverflow.com/questions/29365194/replacing-missing-values-with-random-in-a-numpy-array nan_mask = np.isnan(image) if DEBUG: print(image) plot(image, "In") plot(nan_mask, "Mask") if noise_distribution is not None: nan_noise_size = np.count_nonzero(nan_mask) image[nan_mask] = noise_distribution.rvs(size=nan_noise_size) else: image[nan_mask] = 0 if DEBUG: print(image) plot(image, "Noise injected") return nan_mask
# DIRECTORY PARSER ############################################################
[docs]def image_files_in_dir(directory_path, max_num_files=None, file_ext=(".fits", ".fit")): """Return the path of FITS and Simtel files in `directory_path`. Return the path of all (or `max_num_files`) files having the extension ".simtel", ".simtel.gz", ".fits" or ".fit" in `directory_path`. Parameters ---------- directory_path : str The directory's path where FITS and Simtel files are searched. max_num_files : int The maximum number of files to return. Yields ------ str The path of the next FITS or Simtel files in `directory_path`. """ directory_path = os.path.expanduser(directory_path) files_counter = 0 for file_name in os.listdir(directory_path): file_path = os.path.join(directory_path, file_name) if os.path.isfile(file_path) and file_name.lower().endswith(file_ext): files_counter += 1 if (max_num_files is not None) and (files_counter > max_num_files): break else: yield file_path
[docs]def image_files_in_paths(path_list, max_num_files=None): """Return the path of FITS and Simtel files in `path_list`. Return the path of all (or `max_num_files`) files having the extension ".simtel", ".simtel.gz", ".fits" or ".fit" in `path_list`. Parameters ---------- path_list : str The list of directory's path where FITS and Simtel files are searched. It can also directly contain individual file paths (or a mix of files and directories path). max_num_files : int The maximum number of files to return. Yields ------ str The path of the next FITS or Simtel files in `path_list`. """ files_counter = 0 for path in path_list: if os.path.isdir(path): # If path is a directory for file_path in image_files_in_dir(path): files_counter += 1 if (max_num_files is not None) and (files_counter > max_num_files): break else: yield file_path elif os.path.isfile(path): # If path is a regular file files_counter += 1 if (max_num_files is not None) and (files_counter > max_num_files): break else: yield path else: raise Exception("Wrong item:", path)
# LOAD AND SAVE FITS FILES ###################################################
[docs]def load_image(input_file_path, **kwargs): """Return the image array contained in the given image file. So far, this function convert all multi-channel input images as mono-channel grayscale. The list of supported formats is available in the following page: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html Fits format is also supported thanks to astropy. Parameters ---------- input_file_path : str The path of the image file to load Returns ------- ndarray The loaded image """ if input_file_path.lower().endswith((".fits", ".fit")): # FITS FILES image_array = load_fits_image(input_file_path, **kwargs) else: image_array = load_pil_image(input_file_path, **kwargs) return image_array
[docs]def save_image(image_array, output_file_path, **kwargs): """Save the image array `image` in the given file `output_file_path`. The list of supported formats is available in the following page: https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html Fits format is also supported thanks to astropy. Parameters ---------- image : array_like The image to save output_file_path : str The destination path of the image """ if output_file_path.lower().endswith((".fits", ".fit")): # FITS FILES save_fits_image(image_array, output_file_path) else: save_pil_image(image_array, output_file_path)
# DEBUG ####################################################################### def export_image_as_plain_text(image, output_file_path): fd = open(output_file_path, 'w') for x in image: for y in x: print("{:5.2f}".format(y), end=" ", file=fd) print("", file=fd) fd.close()