Source code for directdemod.misc

"""
Tools for data extraction and json manipulations.
These classes provide API for the input/output operations
with json files.
"""
import os.path
import json
import argparse

from typing import Any
from datetime import datetime, timedelta

import numpy as np
import tifffile

from PIL import Image
from scipy.ndimage import rotate
from pyorbital.orbital import Orbital
from directdemod import constants


[docs]class Encoder(json.JSONEncoder): """ JSON encoder, which handles `np.ndarray` and `datetime` objects """
[docs] def default(self, obj) -> Any: """Encode the object Args: obj (:obj:`object`): oject to encode Returns: :obj:`object`: encoded object """ if isinstance(obj, np.ndarray): return obj.tolist() if isinstance(obj, datetime): return obj.isoformat() return super(Encoder, self).default(obj)
def to_datetime(image_time: str, image_date: str) -> datetime: """builds datetime object Args: image_time (:obj:`string`): time when the image was captured image_date (:obj:`string`): date when the image was captured Returns: :obj:`datetime`: contructed datetime object Throws: :obj:`ValueError`: if length of date string is not 8 or length of time string is not 6 """ if len(image_date) != 8 or len(image_time) != 6: raise ValueError('ERROR: Invalid length of input dates.') try: year = int(image_date[0:4]) month = int(image_date[4:6]) day = int(image_date[6:8]) hour = int(image_time[0:2]) minute = int(image_time[2:4]) second = int(image_time[4:6]) return datetime(year, month, day, hour, minute, second) except ValueError: # add error logging raise def compute_alt(orbiter: Orbital, dtime: datetime, image: np.ndarray, step: float) -> tuple: """compute coordinates of the satellite, shifts the position for step pixels Args: orbiter (:obj:`Orbital`): object representing orbit of satellite dtime (:obj:`datetime`): time when the image was captured image (:obj:`np.array`): captured image step (:obj:`float`): distance shift, couting from start of recording Returns: :obj:`tuple`: coordinates of satellite at certain point of time """ return orbiter.get_lonlatalt(dtime + timedelta( seconds=int(image.shape[0] / 4) + step))[:2][::-1] def extract_date(filename: str) -> datetime: """extracts date from filename Args: filename (:obj:`string`): name of the file Returns: :obj:`datetime`: extracted datetime object Throws: :obj:`ValueError`: if provided filename doesn't correspond to default SDR format """ parts = filename.split('_') image_date, image_time = None, None for index, part in reversed(list(enumerate(parts))): if part[-1] == "Z": image_time = part[:-1] image_date = parts[index - 1] if image_date is None or image_time is None: raise ValueError("ERROR: Invalid file name format \'" + str(filename) + "\'.") return to_datetime(image_time, image_date) def extract_coords(image: np.ndarray, satellite: str, dtime: datetime, tle_file: str = None) -> tuple: """extracts coordinates of the image bounds Args: image (:obj:`np.array`): captured image satellite (:obj:`string`): name of the satellite dtime (:obj:`datetime`): time when the satellite was in the center of the image tle_file (:obj:`string`): path to tle file Returns: :obj:`tuple`: extracted coordinates """ orbiter = Orbital(satellite) if tle_file is None else Orbital( satellite, tle_file=tle_file) delta = int(image.shape[0] / 16) delta = max(delta, 10) top_coord = compute_alt(orbiter, dtime, image, -delta) bot_coord = compute_alt(orbiter, dtime, image, delta) center_coord = compute_alt(orbiter, dtime, image, 0) return top_coord, bot_coord, center_coord def compute_angle(lat1: float, long1: float, lat2: float, long2: float) -> float: """compute angle between 2 points, defined by latitude and longitude Args: lat1 (:obj:`float`): latitude of start point long1 (:obj:`float`): longitude of start point lat2 (:obj:`float`): latitude of end point long2 (:obj:`float`): longitude of end point Returns: :obj:`float`: angle between points """ # source: https://stackoverflow.com/questions/3932502/ # calculate-angle-between-two-latitude-longitude-points lat1 = np.radians(lat1) long1 = np.radians(long1) lat2 = np.radians(lat2) long2 = np.radians(long2) d_lon = (long2 - long1) sincos = np.sin(d_lon) * np.cos(lat2) cosdiff = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos( lat2) * np.cos(d_lon) brng = np.arctan2(sincos, cosdiff) brng = np.degrees(brng) brng = (brng + 360) % 360 brng = 360 - brng return brng def create_desc(file_name: str, image_name: str, sat_type: str = "NOAA 19", tle_file: str = None) -> dict: """create descriptor file for audio record Args: file_name (:obj:`string`): path to audio record image_name (:obj:`string`): path to image file sat_type (:obj:`string`): name of the satellite tle_file (:obj:`string`): path to tle file Returns: :obj:`dict`: returns extracted descriptor file """ image = np.array(Image.open(image_name)) dtime = extract_date(file_name) top, bot, center = extract_coords(image, sat_type, dtime, tle_file=tle_file) degree = compute_angle(*bot, *top) descriptor = { "image_name": os.path.abspath(image_name), "sat_type": sat_type, "date_time": dtime, "center": list(center), "direction": degree } return descriptor def save_metadata(file_name: str, image_name: str, sat_type: str = "NOAA 19", tle_file: str = None) -> None: """creates descriptor from file_name and embeds it into the image in tif format. If the image provided is not tif, then creates new image Args: file_name (:obj:`string`): path to audio record image_name (:obj:`string`): path to image file (.tif) sat_type (:obj:`string`): name of the satellite tle_file (:obj:`string`): path to tle file """ name, _ = os.path.splitext(image_name) image = np.array(Image.open(image_name)) descriptor = create_desc(file_name, image_name, sat_type=sat_type, tle_file=tle_file) tifffile.imsave(name + '.tif', image, description=json.dumps(descriptor, cls=Encoder)) def preprocess(image_name: str, output_file: str) -> None: """preprocesses the image, crops it and rotates for 180 degrees result is saved in output_file file Args: image_name (:obj:`string`): path to image file output_file (:obj:`string`): path to output file """ image = Image.open(image_name) _, height = image.size image = image.crop((85, 0, 995, height)) image = rotate(image, 180) Image.fromarray(image).save(output_file) def main() -> None: """Descriptor CLI interface""" parser = argparse.ArgumentParser( description="Embed data from SDR into tif image") parser.add_argument('-f', '--file_sdr', required=True, help='Path to recorded SDR file.') parser.add_argument('-i', '--image_name', required=True, help='Path to decoded image.') parser.add_argument('-t', '--tle', required=False, help='Path to tle file.') parser.add_argument('-s', '--sat_type', required=False, help='Satellite type. \'NOAA 19\' by default.', default="NOAA 19") args = parser.parse_args() filename = args.file_sdr image_name = args.image_name sat_type = args.sat_type allowed_sats = {'NOAA 18', 'NOAA 19', 'NOAA 15'} if sat_type not in allowed_sats: raise ValueError('ERROR: Invalid satellite type: {0}'.format(sat_type)) tle_file = args.tle if tle_file is None: tle_file = constants.TLE_NOAA if not os.path.isfile(tle_file): raise ValueError("ERROR: Tle file doesn't exist {0}".format(tle_file)) save_metadata(filename, image_name, sat_type, tle_file) # Example arguments # -f = "../samples/SDRSharp_20190521_152538Z_137500000Hz_IQ.wav" # -i = "../samples/image_noaa_2.png" if __name__ == "__main__": main()