Source code for mask

"""A module providing facilities for creating masking arrays for all-sky images.

This module contains methods to create three different kinds of masking arrays.
The masks are as follows:

- One that masks hot pixels
- One that masks hot pixels and any horizon objects
- One that masks hot pixels, horizon objects and everything outside the
  circular image.

Further methods are provided to save a given mask as an image,
and to apply a masking array to an image.
"""

import os
import math
import numpy as np
from PIL import Image

import image
from image import AllSkyImage


[docs]def generate_clean_mask(): """Generate a clean mask for KPNO images. Generates a masking array for KPNO images that only masks out hot pixels. Returns ------- numpy.ndarray The mask array where 1 represents pixels that are to be masked and 0 represents pixels that should remain visible. Notes ----- generate_clean_mask requires there to be median images in Images/mask/. These images can be downloaded from the kpno-allsky github or may be generated by median.median_all_date and moved. """ fileloc = os.path.join(os.path.dirname(__file__), *["Images", "mask"]) files = os.listdir(fileloc) tolerance = 160 # Sets up the mask to be the right size file1 = os.path.join(fileloc, files[0]) img = np.asarray(Image.open(file1).convert("L")) mask = np.zeros((img.shape[0], img.shape[1])) for f in files: f = os.path.join(fileloc, f) img = np.asarray(Image.open(f).convert("L")) for y in range(0, img.shape[1]): for x in range(0, img.shape[0]): # 255 is pure white so accept pixels between # white-tolerance and white # Y is first value as it"s the row value if img[y, x] >= (255 - tolerance): mask[y, x] += 1 # Get only the pixels that appear as "hot" in ALL of the images. # Set those to 0 to mask them. final = np.where(mask >= np.amax(mask), 1, 0) return final
[docs]def generate_mask(forcenew=False): """Generate a mask for KPNO images. Generates a masking array for KPNO images that masks out not only hot pixels, but also the horizon objects. Parameters ---------- forcenew : bool, optional Whether or not this method should load a previously saved mask or if it should generate it completely from scratch. Returns ------- numpy.ndarray The mask array where 1 represents pixels that are to be masked and 0 represents pixels that should remain visible. See Also -------- generate_clean_mask : Used by generate_mask to generate the hot pixel mask. Notes ----- generate_mask requires there to be median images in Images/mask/ but also additionally requires an image named Ignore.png in Images/ that deliniates the horizon objects to be ignored. These images can be downloaded from the kpno-allsky github or may be generated by median.median_all_date and moved. """ center = (256, 252) # Read in the ignore image. # I read this in first to make sure the Mask.png is the correct dimensions. ignore_loc = os.path.join(os.path.dirname(__file__), *["Images", "Ignore.png"]) ignore = np.asarray(Image.open(ignore_loc).convert("RGB")) # If we"ve already generated and saved a mask, load that one. # This speeds up code execution by a lot, otherwise we loop through 512x512 # pixels 6 times! With this we don"t have to even do it once, we just load # and go. maskloc = os.path.join(os.path.dirname(__file__), *["Images", "Mask.png"]) if os.path.isfile(maskloc) and not forcenew: mask = np.asarray(Image.open(maskloc).convert("L")) # Converts the 255 bit loaded image to binary 1-0 image. mask = np.where(mask == 255, 1, 0) # Have to compare these two separately, since ignore has a third color # dimensions and mask.shape == ignore.shape would therefore always be # False. if mask.shape[0] == ignore.shape[0] and mask.shape[1] == ignore.shape[1]: return mask # Get the "clean" mask, i.e. the pixels only ignore mask. mask = generate_clean_mask() hyp = math.hypot for y in range(0, ignore.shape[1]): for x in range(0, ignore.shape[0]): x1 = x - center[0] y1 = center[1] - y r = hyp(x1, y1) # Ignore horizon objects (which have been painted pink) # Only want the horizon objects actually in the circle. # Avoids unnecessary pixels. if r < 242 and np.array_equal(ignore[y, x], [244, 66, 235]): mask[y, x] = 1 # If we've made a new mask, save it so we can skip the above steps later. save_mask(mask) return mask
[docs]def generate_full_mask(forcenew=False): """Generates a complete mask for KPNO images. Generates a masking array for KPNO images that masks out not only hot pixels, but also the horizon objects and then additionally masks pixels outside of the circular all-sky image. Parameters ---------- forcenew : bool, optional Whether or not this method should load a previously saved mask or if it should generate it completely from scratch. Returns ------- numpy.ndarray The mask array where 0 represents pixels that are to be masked and 1 represents pixels that should remain visible. See Also -------- generate_mask : Used by generate_full_mask to generate the hot pixel and horizon mask. Notes ----- generate_full_mask calls generate_mask, which requires there to be median images in Images/mask/ but also additionally requires an image named Ignore.png in Images/ that deliniates the horizon objects to be ignored. These images can be downloaded from the kpno-allsky github. """ mask = generate_mask(forcenew) center = (256, 252) hyp = math.hypot # Ignore everything outside the circular image. for y in range(0, mask.shape[1]): for x in range(0, mask.shape[0]): x1 = x - center[0] y1 = center[1] - y r = hyp(x1, y1) if r > 241: mask[y, x] = 1 return mask
[docs]def save_mask(mask): """Save a masking image. Parameters ---------- mask : numpy.ndarray The mask to save. See Also -------- image.save_image : Save an image. """ img = AllSkyImage("Mask.png", None, None, mask * 255) image.save_image(img, "Images/")
[docs]def apply_mask(mask, img): """Apply a mask to a given image. Parameters ---------- mask : numpy.ndarray The mask to apply. img : image.AllSkyImage The image to apply the mask to. Returns ------- image.AllSkyImage The masked image, where masked pixels have been set to 0. Notes ----- In order to apply a mask, it is inverted and then multiplied by the image. This zeroes out the masked pixels while leaving the non masked pixels untouched. In a rigorous analysis of the image, the masked pixels will count as zeroes. In these cases it is better to use the generated mask in conjunction with NumPy"s masked arrays to complete ignore the pixels you want to mask, instead of setting them to 0. """ data = np.copy(img.data) # Multiply mask with image to keep only non masked pixels. # Invert the mask first since masked pixels are 1 and non masked are 0, # And we want the non mask to be 1 so they get kept. mask = 1 - mask data = np.multiply(mask, data) new = AllSkyImage(img.name, img.date, img.camera, data) return new
if __name__ == "__main__": generate_mask(True)