# -*- coding: utf-8 -*-
"""
Class definition of YOLO_v3 style detection model on image and video
"""

import colorsys
import os
from timeit import default_timer as timer

import numpy as np
from keras import backend as K
from keras.models import load_model
from keras.layers import Input
from PIL import Image, ImageFont, ImageDraw
import xml.dom.minidom
from yolo3.model import yolo_eval, yolo_body, tiny_yolo_body
from yolo3.utils import letterbox_image
import os
from keras.utils import multi_gpu_model

class YOLO(object):
    _defaults = {
        "model_path": 'logs/003/trained_weights_final.h5',
        "anchors_path": 'model_data/yolo_anchors.txt',
        "classes_path": 'model_data/coco_classes.txt',
        "score" : 0.2,           #0.3
        "iou" : 0.25,             #0.45
        "model_image_size" : (416, 416),
        "gpu_num" : 1,
    }

    @classmethod
    def get_defaults(cls, n):
        if n in cls._defaults:
            return cls._defaults[n]
        else:
            return "Unrecognized attribute name '" + n + "'"

    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults) # set up default values
        self.__dict__.update(kwargs) # and update with user overrides
        self.class_names = self._get_class()
        self.anchors = self._get_anchors()
        self.sess = K.get_session()
        self.boxes, self.scores, self.classes = self.generate()

    def _get_class(self):
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    def _get_anchors(self):
        anchors_path = os.path.expanduser(self.anchors_path)
        with open(anchors_path) as f:
            anchors = f.readline()
        anchors = [float(x) for x in anchors.split(',')]
        return np.array(anchors).reshape(-1, 2)

    def generate(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'

        # Load model, or construct model and load weights.
        num_anchors = len(self.anchors)
        num_classes = len(self.class_names)
        is_tiny_version = num_anchors==6 # default setting
        try:
            self.yolo_model = load_model(model_path, compile=False)
        except:
            self.yolo_model = tiny_yolo_body(Input(shape=(None,None,3)), num_anchors//2, num_classes) \
                if is_tiny_version else yolo_body(Input(shape=(None,None,3)), num_anchors//3, num_classes)
            self.yolo_model.load_weights(self.model_path) # make sure model, anchors and classes match
        else:
            assert self.yolo_model.layers[-1].output_shape[-1] == \
                num_anchors/len(self.yolo_model.output) * (num_classes + 5), \
                'Mismatch between model and given anchor and class sizes'

        print('{} model, anchors, and classes loaded.'.format(model_path))

        # Generate colors for drawing bounding boxes.
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
            map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                self.colors))
        np.random.seed(10101)  # Fixed seed for consistent colors across runs.
        np.random.shuffle(self.colors)  # Shuffle colors to decorrelate adjacent classes.
        np.random.seed(None)  # Reset seed to default.

        # Generate output tensor targets for filtered bounding boxes.
        self.input_image_shape = K.placeholder(shape=(2, ))
        if self.gpu_num>=2:
            self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num)
        boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors,
                len(self.class_names), self.input_image_shape,
                score_threshold=self.score, iou_threshold=self.iou)
        return boxes, scores, classes

    def detect_xml(self, image, lxml):
        start = timer()
        print(104)
        if self.model_image_size != (None, None):

            assert self.model_image_size[0] % 32 == 0, 'Multiples of 32 required'

            assert self.model_image_size[1] % 32 == 0, 'Multiples of 32 required'

            boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))

        else:

            new_image_size = (image.width - (image.width % 32),

                              image.height - (image.height % 32))

            boxed_image = letterbox_image(image, new_image_size)

        image_data = np.array(boxed_image, dtype='float32')

        print(image_data.shape)

        image_data /= 255.

        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        out_boxes, out_scores, out_classes = self.sess.run(

            [self.boxes, self.scores, self.classes],

            feed_dict={

                self.yolo_model.input: image_data,

                self.input_image_shape: [image.size[1], image.size[0]],

                K.learning_phase(): 0

            })
        print('142',out_boxes, out_scores, out_classes)  #print("s447",label, (left, top), (right, bottom))
        doc = xml.dom.minidom.Document()

        annotation = doc.createElement('annotation')

        doc.appendChild(annotation)

        folder = doc.createElement('folder')

        folder_text = doc.createTextNode('blank')

        folder.appendChild(folder_text)

        annotation.appendChild(folder)

        filename = doc.createElement('filename')

        filename_text = doc.createTextNode('blank')

        filename.appendChild(filename_text)

        annotation.appendChild(filename)

        path = doc.createElement('path')

        path_text = doc.createTextNode('blank')

        path.appendChild(path_text)

        annotation.appendChild(path)

        source = doc.createElement('source')

        databass = doc.createElement('databass')

        databass_text = doc.createTextNode('Unknown')

        source.appendChild(databass)

        databass.appendChild(databass_text)

        annotation.appendChild(source)

        size = doc.createElement('size')

        width = doc.createElement('width')

        width_text = doc.createTextNode(str(image.width))

        height = doc.createElement('height')

        height_text = doc.createTextNode(str(image.height))

        depth = doc.createElement('depth')

        depth_text = doc.createTextNode('3')

        size.appendChild(width)

        width.appendChild(width_text)

        size.appendChild(height)

        height.appendChild(height_text)

        size.appendChild(depth)

        depth.appendChild(depth_text)

        annotation.appendChild(size)

        segmented = doc.createElement('segmented')

        segmented_text = doc.createTextNode('0')

        segmented.appendChild(segmented_text)

        annotation.appendChild(segmented)

        for i, c in reversed(list(enumerate(out_classes))):

            predicted_class = self.class_names[c]

            if str(predicted_class) == 'screen':
                box = out_boxes[i]

                lobject = doc.createElement('object')

                name = doc.createElement('name')

                name_text = doc.createTextNode(str(predicted_class))

                pose = doc.createElement('pose')

                pose_text = doc.createTextNode('Unspecified')

                truncated = doc.createElement('truncated')

                truncated_text = doc.createTextNode('0')

                difficult = doc.createElement('difficult')

                difficult_text = doc.createTextNode('0')

                name.appendChild(name_text)

                lobject.appendChild(name)

                pose.appendChild(pose_text)

                lobject.appendChild(pose)

                truncated.appendChild(truncated_text)

                lobject.appendChild(truncated)

                difficult.appendChild(difficult_text)

                lobject.appendChild(difficult)

                top, left, bottom, right = box

                top = max(0, np.floor(top + 0.5).astype('int32'))

                left = max(0, np.floor(left + 0.5).astype('int32'))

                bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))

                right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

                bndbox = doc.createElement('bndbox')

                xmin = doc.createElement('xmin')

                xmin_text = doc.createTextNode(str(left))

                ymin = doc.createElement('ymin')

                ymin_text = doc.createTextNode(str(top))

                xmax = doc.createElement('xmax')

                xmax_text = doc.createTextNode(str(right))

                ymax = doc.createElement('ymax')

                ymax_text = doc.createTextNode(str(bottom))

                xmin.appendChild(xmin_text)

                bndbox.appendChild(xmin)

                ymin.appendChild(ymin_text)

                bndbox.appendChild(ymin)

                xmax.appendChild(xmax_text)

                bndbox.appendChild(xmax)

                ymax.appendChild(ymax_text)

                bndbox.appendChild(ymax)

                lobject.appendChild(bndbox)

                annotation.appendChild(lobject)
            if str(predicted_class) == 'icon':
                box = out_boxes[i]

                lobject = doc.createElement('object')

                name = doc.createElement('name')

                name_text = doc.createTextNode(str(predicted_class))

                pose = doc.createElement('pose')

                pose_text = doc.createTextNode('Unspecified')

                truncated = doc.createElement('truncated')

                truncated_text = doc.createTextNode('0')

                difficult = doc.createElement('difficult')

                difficult_text = doc.createTextNode('0')

                name.appendChild(name_text)

                lobject.appendChild(name)

                pose.appendChild(pose_text)

                lobject.appendChild(pose)

                truncated.appendChild(truncated_text)

                lobject.appendChild(truncated)

                difficult.appendChild(difficult_text)

                lobject.appendChild(difficult)

                top, left, bottom, right = box

                top = max(0, np.floor(top + 0.5).astype('int32'))

                left = max(0, np.floor(left + 0.5).astype('int32'))

                bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))

                right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

                bndbox = doc.createElement('bndbox')

                xmin = doc.createElement('xmin')

                xmin_text = doc.createTextNode(str(left))

                ymin = doc.createElement('ymin')

                ymin_text = doc.createTextNode(str(top))

                xmax = doc.createElement('xmax')

                xmax_text = doc.createTextNode(str(right))

                ymax = doc.createElement('ymax')

                ymax_text = doc.createTextNode(str(bottom))

                xmin.appendChild(xmin_text)

                bndbox.appendChild(xmin)

                ymin.appendChild(ymin_text)

                bndbox.appendChild(ymin)

                xmax.appendChild(xmax_text)

                bndbox.appendChild(xmax)

                ymax.appendChild(ymax_text)

                bndbox.appendChild(ymax)

                lobject.appendChild(bndbox)

                annotation.appendChild(lobject)
        fp = open('%s.xml' % lxml, 'w+')

        doc.writexml(fp, indent='\t', addindent='\t', newl='\n', encoding='utf-8')

        fp.close()

        end = timer()

        print(end - start)
    def detect_image(self, image):
        start = timer()

        if self.model_image_size != (None, None):
            assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required'
            assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required'
            boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size)))
        else:
            new_image_size = (image.width - (image.width % 32),
                              image.height - (image.height % 32))
            boxed_image = letterbox_image(image, new_image_size)
        image_data = np.array(boxed_image, dtype='float32')

        print(image_data.shape)
        image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        out_boxes, out_scores, out_classes = self.sess.run(
            [self.boxes, self.scores, self.classes],
            feed_dict={
                self.yolo_model.input: image_data,
                self.input_image_shape: [image.size[1], image.size[0]],
                K.learning_phase(): 0
            })

        print('Found {} boxes for {}'.format(len(out_boxes), 'img'))

        print('142', out_boxes, out_scores, out_classes)  # print("s447",label, (left, top), (right, bottom))
        doc = xml.dom.minidom.Document()

        annotation = doc.createElement('annotation')

        doc.appendChild(annotation)

        folder = doc.createElement('folder')

        folder_text = doc.createTextNode('blank')

        folder.appendChild(folder_text)

        annotation.appendChild(folder)

        filename = doc.createElement('filename')

        filename_text = doc.createTextNode('blank')

        filename.appendChild(filename_text)

        annotation.appendChild(filename)

        path = doc.createElement('path')

        path_text = doc.createTextNode('blank')

        path.appendChild(path_text)

        annotation.appendChild(path)

        source = doc.createElement('source')

        databass = doc.createElement('databass')

        databass_text = doc.createTextNode('Unknown')

        source.appendChild(databass)

        databass.appendChild(databass_text)

        annotation.appendChild(source)

        size = doc.createElement('size')

        width = doc.createElement('width')

        width_text = doc.createTextNode(str(image.width))

        height = doc.createElement('height')

        height_text = doc.createTextNode(str(image.height))

        depth = doc.createElement('depth')

        depth_text = doc.createTextNode('3')

        size.appendChild(width)

        width.appendChild(width_text)

        size.appendChild(height)

        height.appendChild(height_text)

        size.appendChild(depth)

        depth.appendChild(depth_text)

        annotation.appendChild(size)

        segmented = doc.createElement('segmented')

        segmented_text = doc.createTextNode('0')

        segmented.appendChild(segmented_text)

        annotation.appendChild(segmented)

        for i, c in reversed(list(enumerate(out_classes))):

            predicted_class = self.class_names[c]

            if str(predicted_class) == 'screen':
                box = out_boxes[i]

                lobject = doc.createElement('object')

                name = doc.createElement('name')

                name_text = doc.createTextNode(str(predicted_class))

                pose = doc.createElement('pose')

                pose_text = doc.createTextNode('Unspecified')

                truncated = doc.createElement('truncated')

                truncated_text = doc.createTextNode('0')

                difficult = doc.createElement('difficult')

                difficult_text = doc.createTextNode('0')

                name.appendChild(name_text)

                lobject.appendChild(name)

                pose.appendChild(pose_text)

                lobject.appendChild(pose)

                truncated.appendChild(truncated_text)

                lobject.appendChild(truncated)

                difficult.appendChild(difficult_text)

                lobject.appendChild(difficult)

                top, left, bottom, right = box

                top = max(0, np.floor(top + 0.5).astype('int32'))

                left = max(0, np.floor(left + 0.5).astype('int32'))

                bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))

                right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

                bndbox = doc.createElement('bndbox')

                xmin = doc.createElement('xmin')

                xmin_text = doc.createTextNode(str(left))

                ymin = doc.createElement('ymin')

                ymin_text = doc.createTextNode(str(top))

                xmax = doc.createElement('xmax')

                xmax_text = doc.createTextNode(str(right))

                ymax = doc.createElement('ymax')

                ymax_text = doc.createTextNode(str(bottom))

                xmin.appendChild(xmin_text)

                bndbox.appendChild(xmin)

                ymin.appendChild(ymin_text)

                bndbox.appendChild(ymin)

                xmax.appendChild(xmax_text)

                bndbox.appendChild(xmax)

                ymax.appendChild(ymax_text)

                bndbox.appendChild(ymax)

                lobject.appendChild(bndbox)

                annotation.appendChild(lobject)
            if str(predicted_class) == 'icon':
                box = out_boxes[i]

                lobject = doc.createElement('object')

                name = doc.createElement('name')

                name_text = doc.createTextNode(str(predicted_class))

                pose = doc.createElement('pose')

                pose_text = doc.createTextNode('Unspecified')

                truncated = doc.createElement('truncated')

                truncated_text = doc.createTextNode('0')

                difficult = doc.createElement('difficult')

                difficult_text = doc.createTextNode('0')

                name.appendChild(name_text)

                lobject.appendChild(name)

                pose.appendChild(pose_text)

                lobject.appendChild(pose)

                truncated.appendChild(truncated_text)

                lobject.appendChild(truncated)

                difficult.appendChild(difficult_text)

                lobject.appendChild(difficult)

                top, left, bottom, right = box

                top = max(0, np.floor(top + 0.5).astype('int32'))

                left = max(0, np.floor(left + 0.5).astype('int32'))

                bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))

                right = min(image.size[0], np.floor(right + 0.5).astype('int32'))

                bndbox = doc.createElement('bndbox')

                xmin = doc.createElement('xmin')

                xmin_text = doc.createTextNode(str(left))

                ymin = doc.createElement('ymin')

                ymin_text = doc.createTextNode(str(top))

                xmax = doc.createElement('xmax')

                xmax_text = doc.createTextNode(str(right))

                ymax = doc.createElement('ymax')

                ymax_text = doc.createTextNode(str(bottom))

                xmin.appendChild(xmin_text)

                bndbox.appendChild(xmin)

                ymin.appendChild(ymin_text)

                bndbox.appendChild(ymin)

                xmax.appendChild(xmax_text)

                bndbox.appendChild(xmax)

                ymax.appendChild(ymax_text)

                bndbox.appendChild(ymax)

                lobject.appendChild(bndbox)

                annotation.appendChild(lobject)
        fp = open('%s.xml' % lxml, 'w+')

        doc.writexml(fp, indent='\t', addindent='\t', newl='\n', encoding='utf-8')

        fp.close()

        end = timer()

        print(end - start)
        return image

    def detect_video(yolo, video_path, output_path=""):
        import cv2
        vid = cv2.VideoCapture('F:/新建文件夹/lunwen/123.mp4')
        if not vid.isOpened():
            raise IOError("Couldn't open webcam or video")
        video_FourCC = int(vid.get(cv2.CAP_PROP_FOURCC))
        video_fps = vid.get(cv2.CAP_PROP_FPS)
        video_size = (int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)),
                      int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)))
        isOutput = True if output_path != "" else False
        if isOutput:
            print("!!! TYPE:", type(output_path), type(video_FourCC), type(video_fps), type(video_size))
            out = cv2.VideoWriter('F:/新建文件夹/lunwen/11223344.mp4', video_FourCC, video_fps, video_size)
        accum_time = 0
        curr_fps = 0
        fps = "FPS: ??"
        prev_time = timer()
        while True:
            return_value, frame = vid.read()
            if return_value:
                print('当前数据读入正确，此处放入正确代码！')
                image = Image.fromarray(frame)
                image = yolo.detect_image(image)
                result = np.asarray(image)
                curr_time = timer()
                exec_time = curr_time - prev_time
                prev_time = curr_time
                accum_time = accum_time + exec_time
                curr_fps = curr_fps + 1
            else:
                print('当前数据读入错误，跳出当前循环……')
                break
            if accum_time > 1:
                accum_time = accum_time - 1
                fps = "FPS: " + str(curr_fps)
                curr_fps = 0
            cv2.putText(result, text=fps, org=(3, 15), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                        fontScale=0.50, color=(255, 0, 0), thickness=2)
            cv2.namedWindow("result", cv2.WINDOW_NORMAL)
            cv2.imshow("result", result)
            if isOutput:
                out.write(result)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        yolo.close_session()

    def close_session(self):
        self.sess.close()

def detect_video1(yolo, video_path, output_path=""):
    import cv2
    vid = cv2.VideoCapture('F:/新建文件夹/lunwen/123.mp4')
    if not vid.isOpened():
        raise IOError("Couldn't open webcam or video")
    video_FourCC    = int(vid.get(cv2.CAP_PROP_FOURCC))
    video_fps       = vid.get(cv2.CAP_PROP_FPS)
    video_size      = (int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)),
                        int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    isOutput = True if output_path != "" else False
    if isOutput:
        print("!!! TYPE:", type(output_path), type(video_FourCC), type(video_fps), type(video_size))
        out = cv2.VideoWriter('F:/新建文件夹/lunwen/11223344.mp4', video_FourCC, video_fps, video_size)
    accum_time = 0
    curr_fps = 0
    fps = "FPS: ??"
    prev_time = timer()
    while True:
        return_value, frame = vid.read()
        folder = ''
        if return_value:
            print('当前数据读入正确，此处放入正确代码！')
            print("540",frame.shape)  #矩阵,shape==3
            image = Image.fromarray(frame)
            print("542",image)  #3通道
            #lxml = os.path.join(folder, os.path.splitext(frame.split('/')[-1])[0])
            #image = yolo.detect_xml(image,lxml)    #yolo.detect_image
            image = yolo.detect_image
            result = np.asarray(image)
            curr_time = timer()
            exec_time = curr_time - prev_time
            prev_time = curr_time
            accum_time = accum_time + exec_time
            curr_fps = curr_fps + 1
        else:
            print('当前数据读入错误，跳出当前循环……')
            break
        if accum_time > 1:
            accum_time = accum_time - 1
            fps = "FPS: " + str(curr_fps)
            curr_fps = 0
        cv2.putText(result, text=fps, org=(3, 15), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=0.50, color=(255, 0, 0), thickness=2)
        cv2.namedWindow("result", cv2.WINDOW_NORMAL)
        cv2.imshow("result", result)
        if isOutput:
            out.write(result)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    yolo.close_session()       #未调用

if __name__ == '__main__':
    image_path = 'F:/tensor1.5.0/keras-yolo3-master/keras-yolo3-master/VOCdevkit/VOC2007/JPEGImages1/frame_000007.jpg'
    image = Image.open(image_path)
    yolo = YOLO()
    r_image = yolo.detect_image(image=image)
    r_image.show()


import matplotlib
matplotlib.use("Agg")
# import necessary packages
from keras.preprocessing.image import ImageDataGenerator
from keras.applications import VGG16
from keras.layers import Flatten,Dense,BatchNormalization,Dropout,Input
from keras.models import Model
from keras.optimizers import Adam
from sklearn.metrics import classification_report
from keras.callbacks import ModelCheckpoint
from deepmanupy import config
from imutils import paths
from matplotlib import pyplot as plt
import numpy as np
import pickle
import os

def plot_training(H, N, plotPath):
	# construct a plot that plots and saves the training history
	plt.style.use("ggplot")
	plt.figure()
	plt.plot(np.arange(0, N), H.history["loss"], label="train_loss")
	plt.plot(np.arange(0, N), H.history["val_loss"], label="val_loss")
	plt.plot(np.arange(0, N), H.history["acc"], label="train_acc")
	plt.plot(np.arange(0, N), H.history["val_acc"], label="val_acc")
	plt.title("Training Loss and Accuracy")
	plt.xlabel("Epoch #")
	plt.ylabel("Loss/Accuracy")
	plt.legend(loc="lower left")
	plt.savefig(plotPath)
# derive the paths to the training, validation directories
trainPath = os.path.sep.join([config.BASE_PATH, config.TRAIN])
valPath = os.path.sep.join([config.BASE_PATH, config.VAL])
# determine the total number of image paths in training, validation directories
totalTrain = len(list(paths.list_images(trainPath)))
totalVal = len(list(paths.list_images(valPath)))
# initilaize the training data augmentation object
trainAug = ImageDataGenerator(
rotation_range = 15,
zoom_range = 0.15,
width_shift_range = 0.2,
height_shift_range = 0.2,
horizontal_flip = True,
fill_mode = "nearest"
)
valAug = ImageDataGenerator()

mean = np.array([123.68, 116.779, 103.939],dtype="float32")
trainAug.mean = mean
valAug.mean = mean
# initialize the training generator
trainGen = trainAug.flow_from_directory(
	trainPath,
	class_mode="categorical",
	target_size=(224, 224),
	color_mode="rgb",
	shuffle=True,
	batch_size=config.BATCH_SIZE)

# initialize the validation generator
valGen = valAug.flow_from_directory(
	valPath,
	class_mode="categorical",
	target_size=(224, 224),
	color_mode="rgb",
	shuffle=False,
	batch_size=config.BATCH_SIZE)
# load the VGG16 network, ensuring the head FC layer sets are left
# off
baseModel = VGG16(weights="imagenet", include_top=False,input_tensor=Input(shape=(224, 224, 3)))

# construct the head of the model that will be placed on top of the
# the base model
headModel = baseModel.output
headModel = Flatten(name="flatten")(headModel)
headModel = Dense(512, activation="relu",kernel_initializer="he_uniform")(headModel)
headModel = Dropout(0.3)(headModel)
headModel = BatchNormalization()(headModel)
headModel = Dense(len(config.CLASSES), activation="softmax")(headModel)

# place the head FC model on top of the base model (this will become
# the actual model we will train)
model = Model(inputs=baseModel.input, outputs=headModel)
# loop over all layers in the base model and freeze them so they will
# *not* be updated during the first training process

for layer in baseModel.layers:
	layer.trainable = False
print("[INFO] compiling model...")
opt = Adam(lr=6e-4)
model.compile(loss="categorical_crossentropy", optimizer=opt,
	metrics=["accuracy"])
# train the head of the network for a few epochs (all other layers
# are frozen) -- this will allow the new FC layers to start to become
# initialized with actual "learned" values versus pure random
print("[INFO] training head...")
checkPoint = ModelCheckpoint("model_{val_loss:.3f}.model",
                            save_weights_only=False,save_best_only=True,
                            verbose=0,mode="min")
H = model.fit_generator(
	trainGen,
	steps_per_epoch=totalTrain // config.BATCH_SIZE,
	validation_data=valGen,
	validation_steps=totalVal // config.BATCH_SIZE,
	epochs=8,
    callbacks=[checkPoint])

plot_training(H,8,config.TRAIN_PLOT_PATH)

trainGen.reset()
valGen.reset()
for layer in baseModel.layers[15:]:
	layer.trainable = True
print("[INFO] re-compiling model..")
opt = Adam(lr=6e-4)
checkPoint = ModelCheckpoint("model_{val_loss:.3f}.model",
                            save_weights_only=False,save_best_only=True,
                            verbose=0,mode="min")
model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])
model.load_weights("weights_0.061.hdf5")
H = model.fit_generator(
	trainGen,
	steps_per_epoch=totalTrain // config.BATCH_SIZE,
	validation_data=valGen,
	validation_steps=totalVal // config.BATCH_SIZE,
	epochs=10,
	callbacks=[checkPoint]
)
plot_training(H,10,config.TRAIN_PLOT_PATH)


"""Train YOLOv3 with random shapes."""
import argparse
import os
import logging
import time
import warnings
import numpy as np
import cv2
from PIL import Image

from utils import box_utils

from utils.anchors import yolo_anchors

def get_y_true(max_side, batch_boxes, groundtruth_valids, args):

    if args.model_type == 'tiny':
        detect_layer_num = 2
        strides = [16 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p5':
        detect_layer_num = 3
        strides = [8 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p6':
        detect_layer_num = 4
        strides = [8 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p7':
        detect_layer_num = 5
        strides = [8 * 2 ** i for i in range(detect_layer_num)]

    offset = 0.5

    class_num = int(args.num_classes)
    batch_size = batch_boxes.shape[0]
    grid_size = max_side//np.array(strides).astype(np.int32)

    if args.anchor_match_type == 'iou':
        iou_scores = box_utils.boxes_iou(batch_boxes, np.reshape(np.array(yolo_anchors[args.model_type]), [-1,2])/(max_side, max_side))
        matched_anchor_index = np.argsort(-iou_scores,axis=-1)
        matched_anchor_num = np.sum(iou_scores>args.anchor_match_iou_thr, axis=-1)
        if args.anchor_match_iou_thr == -1:
            matched_anchor_num = np.ones_like(matched_anchor_num)
    else:
        anchors_wh = np.reshape(np.array(yolo_anchors[args.model_type]), [-1, 2]) / (max_side, max_side)
        batch_boxes_wh = batch_boxes[..., 2:4] - batch_boxes[..., 0:2]
        batch_boxes_wh = batch_boxes_wh[:,:, None]
        wh_ratio = anchors_wh/(batch_boxes_wh+1e-7)
        wh_ratio = np.max(np.maximum(wh_ratio, 1./wh_ratio),axis=-1)
        matched_anchor_index = np.argsort(wh_ratio, axis=-1)
        matched_anchor_num = np.sum(wh_ratio < args.anchor_match_wh_ratio_thr, axis=-1)

    matched_anchor_num = np.expand_dims(matched_anchor_num, axis=-1)
    batch_boxes = np.concatenate([batch_boxes, matched_anchor_index,matched_anchor_num], axis=-1)

    if args.model_type=='tiny':
        grids_0 = np.zeros([batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros([batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1]
    elif args.model_type=='p5':
        grids_0 = np.zeros([batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros([batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros([batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2]
    elif args.model_type=='p6':
        grids_0 = np.zeros([batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros([batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros([batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_3 = np.zeros([batch_size, grid_size[3], grid_size[3], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2, grids_3]
    else :
        grids_0 = np.zeros([batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros([batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros([batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_3 = np.zeros([batch_size, grid_size[3], grid_size[3], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_4 = np.zeros([batch_size, grid_size[4], grid_size[4], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2, grids_3, grids_4]

    for batch_index in range(batch_size):
        for box_index in range(groundtruth_valids[batch_index]):
            for anchor_index in batch_boxes[batch_index][box_index][5:5 + int(batch_boxes[batch_index][box_index][-1])]:
                grid_index = (anchor_index // len(yolo_anchors[args.model_type][0])).astype(np.int32)
                grid_anchor_index = (anchor_index % len(yolo_anchors[args.model_type][0])).astype(np.int32)
                cxcy = (batch_boxes[batch_index][box_index][0:2]+batch_boxes[batch_index][box_index][2:4])/2
                cxcy *= grid_size[grid_index]
                grid_xy = np.floor(cxcy).astype(np.int32)

                dxdy = cxcy - grid_xy
                # dwdh = batch_boxes[batch_index][box_index][2:4]/np.array(strides[grid_index])
                dwdh = batch_boxes[batch_index][box_index][2:4]-batch_boxes[batch_index][box_index][0:2]
                dwdh = dwdh*np.array(grid_size[grid_index])
                grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][4] = 1
                grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][5+batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1
                # continue
                # print(grid_xy[1], grid_xy[0],anchor_index)
                grid_xy_fract = cxcy%1.
                if (grid_xy > 0).all():
                    if grid_xy_fract[0] < offset:
                        dxdy = cxcy - np.floor(cxcy - [0.5, 0.])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][4] = 1
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][
                            5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

                    if grid_xy_fract[1] < offset:
                        dxdy = cxcy - np.floor(cxcy - [0., 0.5])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][4] = 1
                        grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][
                            5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

                if (grid_xy<grid_size[grid_index]-1).all():
                    if grid_xy_fract[0] > offset:
                        dxdy = cxcy - np.floor(cxcy + [0.5, 0.])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][4] = 1
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][
                            5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1
                    if grid_xy_fract[1] > offset:
                        dxdy = cxcy - np.floor(cxcy + [0., 0.5])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][4] = 1
                        grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][
                            5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

    return tuple(grids)

def get_y_true_with_one_class(max_side, batch_boxes, groundtruth_valids, args):
    if args.model_type == 'tiny':
        detect_layer_num = 2
        strides = [16 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p5':
        detect_layer_num = 3
        strides = [8 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p6':
        detect_layer_num = 4
        strides = [8 * 2 ** i for i in range(detect_layer_num)]
    if args.model_type == 'p7':
        detect_layer_num = 5
        strides = [8 * 2 ** i for i in range(detect_layer_num)]

    offset = 0.5
    # class_num = args.num_classes
    class_num = 0
    batch_size = batch_boxes.shape[0]
    grid_size = max_side//np.array(strides).astype(np.int32)

    if args.anchor_match_type == 'iou':
        iou_scores = box_utils.boxes_iou(batch_boxes, np.reshape(np.array(yolo_anchors[args.model_type]), [-1,2])/(max_side, max_side))
        matched_anchor_index = np.argsort(-iou_scores,axis=-1)
        matched_anchor_num = np.sum(iou_scores>args.anchor_match_iou_thr, axis=-1)
        if args.anchor_match_iou_thr == -1:
            matched_anchor_num = np.ones_like(matched_anchor_num)
    else:
        anchors_wh = np.reshape(np.array(yolo_anchors[args.model_type]), [-1, 2]) / (max_side, max_side)
        batch_boxes_wh = batch_boxes[..., 2:4] - batch_boxes[..., 0:2]
        batch_boxes_wh = batch_boxes_wh[:, :,None]
        # print(batch_boxes_wh)
        wh_ratio = anchors_wh/(batch_boxes_wh+1e-7)
        wh_ratio = np.max(np.maximum(wh_ratio, 1./wh_ratio),axis=-1)
        matched_anchor_index = np.argsort(wh_ratio, axis=-1)
        matched_anchor_num = np.sum(wh_ratio < args.anchor_match_wh_ratio_thr, axis=-1)

    matched_anchor_num = np.expand_dims(matched_anchor_num, axis=-1)
    batch_boxes = np.concatenate([batch_boxes, matched_anchor_index,matched_anchor_num], axis=-1)

    if args.model_type == 'tiny':
        grids_0 = np.zeros(
            [batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros(
            [batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1]
    elif args.model_type == 'p5':
        grids_0 = np.zeros(
            [batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros(
            [batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros(
            [batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2]
    elif args.model_type == 'p6':
        grids_0 = np.zeros(
            [batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros(
            [batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros(
            [batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_3 = np.zeros(
            [batch_size, grid_size[3], grid_size[3], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2, grids_3]
    else:
        grids_0 = np.zeros(
            [batch_size, grid_size[0], grid_size[0], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_1 = np.zeros(
            [batch_size, grid_size[1], grid_size[1], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_2 = np.zeros(
            [batch_size, grid_size[2], grid_size[2], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_3 = np.zeros(
            [batch_size, grid_size[3], grid_size[3], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids_4 = np.zeros(
            [batch_size, grid_size[4], grid_size[4], len(yolo_anchors[args.model_type][0]), 5 + class_num], np.float32)
        grids = [grids_0, grids_1, grids_2, grids_3, grids_4]

    for batch_index in range(batch_size):
        for box_index in range(groundtruth_valids[batch_index]):
            for anchor_index in batch_boxes[batch_index][box_index][5:5 + int(batch_boxes[batch_index][box_index][-1])]:
                grid_index = (anchor_index // len(yolo_anchors[args.model_type][0])).astype(np.int32)
                grid_anchor_index = (anchor_index % len(yolo_anchors[args.model_type][0])).astype(np.int32)
                cxcy = (batch_boxes[batch_index][box_index][0:2]+batch_boxes[batch_index][box_index][2:4])/2
                cxcy *= grid_size[grid_index]
                grid_xy = np.floor(cxcy).astype(np.int32)

                dxdy = cxcy - grid_xy
                # dwdh = batch_boxes[batch_index][box_index][2:4]/np.array(strides[grid_index])
                dwdh = batch_boxes[batch_index][box_index][2:4]-batch_boxes[batch_index][box_index][0:2]
                dwdh = dwdh*np.array(grid_size[grid_index])
                grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][4] = 1
                # grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]][grid_anchor_index][5+batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1
                # continue
                # print(grid_xy[1], grid_xy[0],anchor_index)
                grid_xy_fract = cxcy%1.
                if (grid_xy > 0).all():
                    if grid_xy_fract[0] < offset:
                        dxdy = cxcy - np.floor(cxcy - [0.5, 0.])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][4] = 1
                        # grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]-1][grid_anchor_index][
                        #     5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

                    if grid_xy_fract[1] < offset:
                        dxdy = cxcy - np.floor(cxcy - [0., 0.5])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][4] = 1
                        # grids[grid_index][batch_index][grid_xy[1]-1][grid_xy[0]][grid_anchor_index][
                        #     5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

                if (grid_xy<grid_size[grid_index]-1).all():
                    if grid_xy_fract[0] > offset:
                        dxdy = cxcy - np.floor(cxcy + [0.5, 0.])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][4] = 1
                        # grids[grid_index][batch_index][grid_xy[1]][grid_xy[0]+1][grid_anchor_index][
                        #     5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1
                    if grid_xy_fract[1] > offset:
                        dxdy = cxcy - np.floor(cxcy + [0., 0.5])
                        # dwdh = batch_boxes[batch_index][box_index][2:4] / np.array(strides[grid_index])
                        grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][0:4] = np.concatenate([dxdy,dwdh])
                        grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][4] = 1
                        # grids[grid_index][batch_index][grid_xy[1]+1][grid_xy[0]][grid_anchor_index][
                        #     5 + batch_boxes[batch_index][box_index][4].astype(np.int32)] = 1

    return tuple(grids)


import factory
import uuid
import numpy as np
from pathlib import Path
from zuds import DBSession, ScienceImage, Source

from tempfile import mkdtemp

datadir = Path(__file__).parent / 'data'

TMP_DIR = mkdtemp()

class BaseMeta:
    sqlalchemy_session = DBSession
    sqlalchemy_session_persistence = 'commit'

def random_ra():
    return np.random.uniform() * 360.

def random_dec():
    return np.random.uniform() * 180 - 90.

class SourceFactory(factory.alchemy.SQLAlchemyModelFactory):
    class Meta(BaseMeta):
        model = Source

    id = factory.LazyFunction(lambda: uuid.uuid4().hex)
    ra = factory.LazyFunction(random_ra)
    dec = factory.LazyFunction(random_dec)

class TriangulumScienceImageFactory(factory.alchemy.SQLAlchemyModelFactory):
    class Meta(BaseMeta):
        model = ScienceImage

    basename = f"ztf_20171229173808_000651_zg_c03_o_q1_sciimg.fits"
    ra1 = 23.852027389390017
    dec1 = 31.35253978382308
    ra2 = 23.85248973060998
    dec2 = 30.486655894780796
    ra3 = 22.85022147849855
    dec3 = 30.482502625369364
    ra4 = 22.8407986703633
    dec4 = 31.348524349204567
    ra = 23.34914054252041
    dec = 30.918779847599264
    field = 651
    ccdid = 3
    qid = 1
    fid = 1
    filtercode = "zg"
    imgtypecode = "o"
    filefracday = 20171229173808
    obsjd = 2458116.6738079
    infobits = 0
    pid = 362173800815
    nid = 362
    expid = 36217380
    seeing = 1.943
    airmass = 1.0
    moonillf = 0.7893975
    moonesb = 0.0
    maglimit = 20.12
    crpix1 = 1536.5
    crpix2 = 1540.5
    crval1 = 23.34894444544
    crval2 = 30.91859533121
    cd11 = -0.0002812466181043
    cd12 = 1.366648840419e-06
    cd21 = -1.336245321655e-06
    cd22 = -0.0002812619006425
    ipac_gid = 0
    exptime = 30.0

    header = {
        "SIMPLE": True,
        "BITPIX": -32,
        "NAXIS": 2,
        "NAXIS1": 3072,
        "NAXIS2": 3080,
        "BUNIT": "Data Value",
        "ORIGIN": "Zwicky Transient Facility",
        "OBSERVER": "ZTF Robotic Software",
        "ROBOSOFT": "0.70-devel October 7, 2017",
        "FITSSOFT": "0.70-devel October 7, 2017",
        "INSTRUME": "ZTF/MOSAIC",
        "DETECTOR": "",
        "DETSOFT": "",
        "DETFIRM": "",
        "IMGTYPE": "object",
        "EXPOSURE": 30.0,
        "EXPTIME": 30.0,
        "MODE_NUM": 0,
        "CCDSUM": "1 1",
        "PIXSCALE": 1.012,
        "FILENAME": "ztf_20171229173808_000651_zg_c03_o.fits",
        "ORIGNAME": "ztf_20171229173808_000651_zg_c03_o.fits",
        "FRAMENUM": 204,
        "HEADTEMP": 159.972,
        "DEWPRESS": 3.23e-06,
        "DETHEAT": 0.9,
        "CCDTMP01": "169.981",
        "CCDTMP02": "167.823",
        "CCDTMP03": "166.794",
        "CCDTMP04": "168.68",
        "CCDTMP05": "167.921",
        "CCDTMP06": "165.227",
        "CCDTMP07": "167.163",
        "CCDTMP08": "167.122",
        "CCDTMP09": "167.394",
        "CCDTMP10": "165.691",
        "CCDTMP11": "165.986",
        "CCDTMP12": "167.42",
        "CCDTMP13": "168.579",
        "CCDTMP14": "166.898",
        "CCDTMP15": "167.269",
        "CCDTMP16": "169.064",
        "INST_X": -2.20309e-06,
        "INST_Y": -1.37748e-06,
        "FOCUSPOS": 1.29,
        "THETA_X": 0.345805,
        "THETA_Y": 0.2915,
        "INST_ROT": 3.40255e-07,
        "FIELDID": 651,
        "OBJRA": "+01h35m42.16s",
        "OBJDEC": "+33d21m00s",
        "OBJRAD": 23.9257,
        "OBJDECD": 33.35,
        "EQUINOX": 2000.0,
        "RA_RATE": 0.0,
        "DEC_RATE": 0.0,
        "PROGRMPI": "Kulkarni",
        "REQUSTID": 306,
        "QCOMMENT": "scheduler_test",
        "TEMPTURE": 14.3,
        "WINDSPD": 0.6,
        "WINDDIR": 168.0,
        "DEWPOINT": 36.2,
        "WETNESS": 0.0,
        "HUMIDITY": 6.0,
        "PRESSURE": -9999.0,
        "CLOUDS": -9999.0,
        "SUNAZ": 267.107135,
        "SUNALT": -41.2579774,
        "MOONRA": 42.0540926,
        "MOONDEC": 10.4029396,
        "MOONALT": 67.0148964,
        "MOONILLF": 0.7893975,
        "MOONPHAS": 54.6341705,
        "MOONESB": 0.0,
        "PROGRMID": 0,
        "FILTERID": 1,
        "FILTER": "ZTF g",
        "FILTPOS": 1,
        "OBSJD": 2458116.6738079,
        "OBSMJD": 58116.1738079,
        "HJD": 2458116.6738079,
        "OBLST": "+02:54:37.1",
        "HOURANG": "+01:17:53.16",
        "HOURANGD": 1.3,
        "RA": "+01:35:42.16",
        "DEC": "+33:21:00",
        "RAD": 23.9,
        "DECD": 33.3,
        "TELRA": "+01:35:42.14",
        "TELDEC": "+33:21:0.1",
        "TELRAD": 23.9,
        "TELDECD": 33.4,
        "AZIMUTH": 275.7,
        "ELVATION": 73.8,
        "TRACKRA": 0.0,
        "TRACKDEC": 0.0,
        "AIRMASS": 1.0,
        "DOME_AZ": 275.4,
        "WINDSCAL": 0.0,
        "OBSERVAT": "Palomar Observatory",
        "TELESCOP": "Palomar 48-inch",
        "OBSLON": -116.8597,
        "OBSLAT": 33.3483,
        "OBSALT": 1706.0,
        "DOMESTAT": "Open",
        "SHUTOPEN": "2017-12-29T04:10:18.621",
        "SHUTCLSD": "2017-12-29T04:10:48.586",
        "PIXSCALX": 1.012,
        "PIXSCALY": 1.012,
        "NFRAMES": 8,
        "EXTNAME": "1",
        "HDUVERS": 1,
        "CCD_ID": 3,
        "CCDNAME": "S03",
        "AMP_ID": 0,
        "AMP_NAME": "F",
        "GAIN": 6.2,
        "READNOI": 10.0,
        "DARKCUR": 1.0,
        "WCSVERS": "2.1",
        "WCSAXES": 2,
        "WCSNAME": "ZTF",
        "RADESYS": "ICRS",
        "CTYPE1": "RA---TPV",
        "CTYPE2": "DEC--TPV",
        "CRPIX1": 1536.5,
        "CRPIX2": 1540.5,
        "CRVAL1": 23.34894444544,
        "CRVAL2": 30.91859533121,
        "CUNIT1": "deg",
        "CUNIT2": "deg",
        "CD1_1": -0.0002812466181043,
        "CD1_2": 1.366648840419e-06,
        "CD2_1": -1.336245321655e-06,
        "CD2_2": -0.0002812619006425,
        "CCD_ROT": 0.0,
        "PV1_0": 2.82931510214e-05,
        "PV1_1": 1.000000886805,
        "PV1_2": -1.583849820628e-05,
        "PV1_4": -0.0004551962017747,
        "PV1_5": -8.448987011491e-05,
        "PV1_6": -0.0002590727599212,
        "PV1_7": 0.0001271446860683,
        "PV1_8": -2.218410001277e-05,
        "PV1_9": -0.0002238379281277,
        "PV1_10": -8.6789023318149e-05,
        "PV1_12": 0.0007544816031555,
        "PV1_13": -0.0006509589247359,
        "PV1_14": 0.0001397116876056,
        "PV1_15": 0.0001571286145113,
        "PV1_16": 0.0006416466661674,
        "PV2_0": 4.320809994094e-05,
        "PV2_1": 1.000088198292,
        "PV2_2": -2.199903872155e-05,
        "PV2_4": -0.0005366569130607,
        "PV2_5": -0.0002406676575668,
        "PV2_6": -0.000207199127297,
        "PV2_7": -0.0002189047536949,
        "PV2_8": -0.0001038595866183,
        "PV2_9": -0.0002698874623035,
        "PV2_10": 9.987399186654e-05,
        "PV2_12": 0.0007652771860817,
        "PV2_13": 0.0003851918002,
        "PV2_14": -0.0001589248794534,
        "PV2_15": -0.0003523303703531,
        "PV2_16": 0.0002730145790782,
        "FGROUPNO": 1,
        "ASTIRMS1": 0.0,
        "ASTIRMS2": 0.0,
        "ASTRRMS1": 1.38158428994e-05,
        "ASTRRMS2": 1.198420375338e-05,
        "ASTINST": 1,
        "FLXSCALE": 0.2746611284301518,
        "MAGZEROP": 0.0,
        "PHOTIRMS": 0.0,
        "PHOTINST": 1,
        "PHOTLINK": False,
        "ASTROMN": 1260,
        "ASTCHI2": 5.028639793395996,
        "SCAMPFIL": "gaia_000651_c03_q1.fits",
        "SCAMPPTH": "/ztf/ops/static/rc08/astrom",
        "CALNOTE1": "*** photometric-calibration keywords below are from PSF-fitting:",
        "CALNOTE2": "*** will also be applicable to aperture measurements on point",
        "CALNOTE3": "*** sources using psf_mag-aper_mag corrections below (if available)",
        "NMATCHES": 628,
        "MAGZP": 26.403007,
        "MAGZPUNC": 1.15261e-05,
        "MAGZPRMS": 0.034173,
        "CLRCOEFF": -0.058441,
        "CLRCOUNC": 2.01596e-05,
        "ZPCLRCOV": -1.396e-05,
        "PCOLOR": "g - R",
        "SATURATE": 48059.879,
        "FIXAPERS": "2,3,4,6,10,14",
        "APCOR1": -1.05363548,
        "APCORUN1": 0.0030910038,
        "APCOR2": -0.51719302,
        "APCORUN2": 0.0015610205,
        "APCOR3": -0.274389,
        "APCORUN3": 0.0010768763,
        "APCOR4": -0.096003,
        "APCORUN4": 0.0008797319,
        "APCOR5": -0.013771,
        "APCORUN5": 0.001571736,
        "APCOR6": 0.005341,
        "APCORUN6": 0.0024231774,
        "CCDID": 3,
        "QID": 1,
        "RCID": 8,
        "EXPID": 36217380,
        "INFOBITS": 0,
        "SEEING": 1.943,
        "CIDPMASK": 8011,
        "CIDBIAS": 36208002,
        "CIDHIFLT": 36208014,
        "CIDLOFLT": 8015,
        "MAGLIM": 20.12,
        "BIT00": 0,
        "BIT01": 1,
        "BIT02": 2,
        "BIT03": 3,
        "BIT04": 4,
        "BIT05": 5,
        "BIT06": 6,
        "BIT07": 7,
        "BIT08": 8,
        "BIT09": 9,
        "BIT10": 10,
        "BIT11": 11,
        "BIT12": 12,
        "BIT13": 13,
        "BIT14": 14,
        "BIT15": 15,
        "BIT16": 16,
        "BIT17": 17,
        "BIT18": 18,
        "BIT19": 19,
        "BIT20": 20,
        "BIT21": 21,
        "STATUS": 1,
        "CREATED": "2017-12-28T20:26:00",
        "DBPID": 362173800815,
        "PROCVSN": 1,
        "DBFIELD": 651,
        "DBNID": 362,
        "DBEXPID": 36217380,
        "DBRID": 3621738008,
        "DBRCID": 8,
        "DBFID": 1,
        "DBPIPEID": 15,
        "FLXSCLZP": 25.0
    }

    header_comments = {
        "SIMPLE": "conforms to FITS standard",
        "BITPIX": "array data type",
        "NAXIS": "number of array dimensions",
        "NAXIS1": "",
        "NAXIS2": "",
        "BUNIT": "",
        "ORIGIN": "Data origin",
        "OBSERVER": "Observer",
        "ROBOSOFT": "Robotic software version",
        "FITSSOFT": "FITS software version",
        "INSTRUME": "Instrument name",
        "DETECTOR": "Detector controller",
        "DETSOFT": "Detector software version",
        "DETFIRM": "Detector firmware version",
        "IMGTYPE": "Image type",
        "EXPOSURE": "Total Exposure Time (s)",
        "EXPTIME": "Requested Exposure Time (s)",
        "MODE_NUM": "Mode identifying key",
        "CCDSUM": "CCD binning",
        "PIXSCALE": "Pixel scale, in arcsec per pixel",
        "FILENAME": "File name",
        "ORIGNAME": "Original file name",
        "FRAMENUM": "Detector frame number",
        "HEADTEMP": "Cryo cooler cold head temp (K)",
        "DEWPRESS": "Dewar pressure (milli-torr)",
        "DETHEAT": "Detector focal plane heater power (%)",
        "CCDTMP01": "CCD temperature01 (K)",
        "CCDTMP02": "CCD temperature02 (K)",
        "CCDTMP03": "CCD temperature03 (K)",
        "CCDTMP04": "CCD temperature04 (K)",
        "CCDTMP05": "CCD temperature05 (K)",
        "CCDTMP06": "CCD temperature06 (K)",
        "CCDTMP07": "CCD temperature07 (K)",
        "CCDTMP08": "CCD temperature08 (K)",
        "CCDTMP09": "CCD temperature09 (K)",
        "CCDTMP10": "CCD temperature10 (K)",
        "CCDTMP11": "CCD temperature11 (K)",
        "CCDTMP12": "CCD temperature12 (K)",
        "CCDTMP13": "CCD temperature13 (K)",
        "CCDTMP14": "CCD temperature14 (K)",
        "CCDTMP15": "CCD temperature15 (K)",
        "CCDTMP16": "CCD temperature16 (K)",
        "INST_X": "Hexapod X (RA) axis position (mm)",
        "INST_Y": "Hexapod Y (Dec) axis position (mm)",
        "FOCUSPOS": "Hexapod Z (Focus) axis position (mm)",
        "THETA_X": "Hexapod rotation about INST_X (deg)",
        "THETA_Y": "Hexapod rotation about INST_Y (deg)",
        "INST_ROT": "Hexapod rotation about Z axis (deg)",
        "FIELDID": "ZTF field ID number",
        "OBJRA": "Queue object right ascension",
        "OBJDEC": "Queue object declination",
        "OBJRAD": "Object right ascension (deg)",
        "OBJDECD": "Object declination (deg)",
        "EQUINOX": "Equinox",
        "RA_RATE": "RA tracking rate (\"/hr)",
        "DEC_RATE": "Dec tracking rate (\"/hr)",
        "PROGRMPI": "Queue program PI",
        "REQUSTID": "Queue request ID",
        "QCOMMENT": "Queue comment",
        "TEMPTURE": "Outside air temperature (C)",
        "WINDSPD": "Outside wind speed",
        "WINDDIR": "Outside wind direction (deg)",
        "DEWPOINT": "Dewpoint (C)",
        "WETNESS": "Wetness sensor reading",
        "HUMIDITY": "Humitidy",
        "PRESSURE": "Atmospheric pressure, millibars",
        "CLOUDS": "Cloud sensor reading",
        "SUNAZ": "Sun azimuth (deg)",
        "SUNALT": "Sun altitude (deg)",
        "MOONRA": "Moon J2000.0 R.A. (deg)",
        "MOONDEC": "Moon J2000.0 Dec. (deg)",
        "MOONALT": "Moon altitude (deg)",
        "MOONILLF": "Moon illuminated fraction (frac)",
        "MOONPHAS": "Moon phase angle (deg)",
        "MOONESB": "Moon excess in sky brightness V-band",
        "PROGRMID": "Queue program ID",
        "FILTERID": "Filter ID code",
        "FILTER": "Filter name",
        "FILTPOS": "Filter ID code",
        "OBSJD": "Julian day corresponds to UTC-OBS (day)",
        "OBSMJD": "MJD corresponds to UTC-OBS (day)",
        "HJD": "Heliocentric Julian Day (days)",
        "OBLST": "Local Sidereal Time",
        "HOURANG": "Hour angle",
        "HOURANGD": "Hour angle",
        "RA": "Requested right ascension",
        "DEC": "Requested declination",
        "RAD": "Requested right ascension (deg)",
        "DECD": "Requested declination (deg)",
        "TELRA": "Telescope right ascension",
        "TELDEC": "Telescope declination",
        "TELRAD": "Telescope right ascension (deg)",
        "TELDECD": "Telescope declination (deg)",
        "AZIMUTH": "Telescope azimuth (deg)",
        "ELVATION": "Telescope elevation (deg)",
        "TRACKRA": "TCS RA non-sidereal tracking rate (\"/hr)",
        "TRACKDEC": "TCS Dec non-sidereal tracking rate (\"/hr)",
        "AIRMASS": "Airmass (Hardie)",
        "DOME_AZ": "Dome azimuth (deg)",
        "WINDSCAL": "Windscreen elevation (deg)",
        "OBSERVAT": "Observatory",
        "TELESCOP": "Observatory telescope",
        "OBSLON": "Observatory longitude (deg)",
        "OBSLAT": "Observatory latitude (deg E)",
        "OBSALT": "Observatory altitude (m)",
        "DOMESTAT": "Dome status at beginning of exposure",
        "SHUTOPEN": "Shutter open time",
        "SHUTCLSD": "Shutter close time",
        "PIXSCALX": "Pixel scale in X",
        "PIXSCALY": "Pixel scale in Y",
        "NFRAMES": "number of frames in FITS file",
        "EXTNAME": "",
        "HDUVERS": "",
        "CCD_ID": "ID value of CCD detector",
        "CCDNAME": "Detector name or serial number",
        "AMP_ID": "ID value of CCD amplifier",
        "AMP_NAME": "Name of CCD amplifier",
        "GAIN": "Gain e-/adu",
        "READNOI": "Read noise e-",
        "DARKCUR": "Dark current e-/s @ 150 K",
        "WCSVERS": "WCS version number",
        "WCSAXES": "Number of WCS axes",
        "WCSNAME": "Name of the WCS coordinate system",
        "RADESYS": "Coordinate reference system",
        "CTYPE1": "Name of coord X axis",
        "CTYPE2": "Name of coord Y axis",
        "CRPIX1": "Coord system X ref pixel",
        "CRPIX2": "Coord system Y ref pixel",
        "CRVAL1": "Coord system value at CRPIX1",
        "CRVAL2": "Coord system value at CRPIX2",
        "CUNIT1": "Coordinate units for X axis",
        "CUNIT2": "Coordinate units for Y axis",
        "CD1_1": "WCS matrix",
        "CD1_2": "WCS matrix",
        "CD2_1": "WCS matrix",
        "CD2_2": "WCS matrix",
        "CCD_ROT": "CCD rotation angle",
        "PV1_0": "Projection distortion parameter",
        "PV1_1": "Projection distortion parameter",
        "PV1_2": "Projection distortion parameter",
        "PV1_4": "Projection distortion parameter",
        "PV1_5": "Projection distortion parameter",
        "PV1_6": "Projection distortion parameter",
        "PV1_7": "Projection distortion parameter",
        "PV1_8": "Projection distortion parameter",
        "PV1_9": "Projection distortion parameter",
        "PV1_10": "Projection distortion parameter",
        "PV1_12": "Projection distortion parameter",
        "PV1_13": "Projection distortion parameter",
        "PV1_14": "Projection distortion parameter",
        "PV1_15": "Projection distortion parameter",
        "PV1_16": "Projection distortion parameter",
        "PV2_0": "Projection distortion parameter",
        "PV2_1": "Projection distortion parameter",
        "PV2_2": "Projection distortion parameter",
        "PV2_4": "Projection distortion parameter",
        "PV2_5": "Projection distortion parameter",
        "PV2_6": "Projection distortion parameter",
        "PV2_7": "Projection distortion parameter",
        "PV2_8": "Projection distortion parameter",
        "PV2_9": "Projection distortion parameter",
        "PV2_10": "Projection distortion parameter",
        "PV2_12": "Projection distortion parameter",
        "PV2_13": "Projection distortion parameter",
        "PV2_14": "Projection distortion parameter",
        "PV2_15": "Projection distortion parameter",
        "PV2_16": "Projection distortion parameter",
        "FGROUPNO": "SCAMP field group label",
        "ASTIRMS1": "Astrom. dispersion RMS (intern., high S/N)",
        "ASTIRMS2": "Astrom. dispersion RMS (intern., high S/N)",
        "ASTRRMS1": "Astrom. dispersion RMS (ref., high S/N)",
        "ASTRRMS2": "Astrom. dispersion RMS (ref., high S/N)",
        "ASTINST": "SCAMP astrometric instrument label",
        "FLXSCALE": "Flux scale factor for coadd / DG",
        "MAGZEROP": "SCAMP zero-point",
        "PHOTIRMS": "mag dispersion RMS (internal, high S/N)",
        "PHOTINST": "SCAMP photometric instrument label",
        "PHOTLINK": "True if linked to a photometric field",
        "ASTROMN": "",
        "ASTCHI2": "",
        "SCAMPFIL": "",
        "SCAMPPTH": "",
        "CALNOTE1": "",
        "CALNOTE2": "",
        "CALNOTE3": "",
        "NMATCHES": "Number of photometric calibrators used",
        "MAGZP": "Magnitude zero point for PSF-photometry [mag]",
        "MAGZPUNC": "Magnitude zero point uncertainty [mag]",
        "MAGZPRMS": "Magnitude zero point RMS deviation [mag]",
        "CLRCOEFF": "Color coefficient from linear fit",
        "CLRCOUNC": "Color coefficient uncertainty from linear fit",
        "ZPCLRCOV": "Covariance in MAGZP and CLRCOEFF [mag^2]",
        "PCOLOR": "Calibrator color used for PSF-fit calibration",
        "SATURATE": "Saturation value in final science image [DN]",
        "FIXAPERS": "fixed aperture diameters used [pixels]",
        "APCOR1": "PSF_mag - AP_mag for AP.diam= 2.0 pix.",
        "APCORUN1": "Uncertainty in APCOR1 [mag]",
        "APCOR2": "PSF_mag - AP_mag for AP.diam= 3.0 pix.",
        "APCORUN2": "Uncertainty in APCOR2 [mag]",
        "APCOR3": "PSF_mag - AP_mag for AP.diam= 4.0 pix.",
        "APCORUN3": "Uncertainty in APCOR3 [mag]",
        "APCOR4": "PSF_mag - AP_mag for AP.diam= 6.0 pix.",
        "APCORUN4": "Uncertainty in APCOR4 [mag]",
        "APCOR5": "PSF_mag - AP_mag for AP.diam=10.0 pix.",
        "APCORUN5": "Uncertainty in APCOR5 [mag]",
        "APCOR6": "PSF_mag - AP_mag for AP.diam=14.0 pix.",
        "APCORUN6": "Uncertainty in APCOR6 [mag]",
        "CCDID": "",
        "QID": "",
        "RCID": "",
        "EXPID": "",
        "INFOBITS": "infoBits string; bit(s) = null",
        "SEEING": "seeing FWHM [pixels]",
        "CIDPMASK": "calibration DB ID for pmask image input",
        "CIDBIAS": "calibration DB ID for bias image input",
        "CIDHIFLT": "calibration DB ID for hi-nu flat image input",
        "CIDLOFLT": "calibration DB ID for low-nu flat image input",
        "MAGLIM": "5-sigma mag-limit in PSF-fit catalog [mag]",
        "BIT00": "MEDIAN BACKGROUND LEVEL is >  4000.0 DN",
        "BIT01": "ROBUST PIXEL NOISE (RMS) is > 100.0 DN",
        "BIT02": "PERCENT OF SATURATED PIXELS is > 50.0%",
        "BIT03": "IMAGE SEEING (FWHM) is > 5.0\"",
        "BIT04": "MAGLIM FROM PSF CATALOG UNCERTS is < 17.5 mag",
        "BIT05": "MAGLIM FROM EMPIRICAL FORMULA is < 17.5 mag",
        "BIT06": "NUMBER OF PSF CATALOG SOURCES is < 10",
        "BIT07": "NUMBER OF SEXTRACTOR CAT SOURCES is < 20",
        "BIT08": "PHOTOMETRIC CALIBRATION NOT POSSIBLE",
        "BIT09": "NUMBER OF PHOTOMETRIC CALIBRATORS is < 30",
        "BIT10": "NUMBER OF MATCHES FOR PHOTOMETRIC CAL < 5",
        "BIT11": "RMS in PHOTCAL RESIDUALS (ZPs) is > 0.2 mag",
        "BIT12": "ASTROMETRIC CALIBRATION NOT POSSIBLE",
        "BIT13": "PCT OF REF-CAT MATCHES FOR ASTROM is < 0%",
        "BIT14": "NUMBER OF MATCHES FOR ASTROM CAL is < 100",
        "BIT15": "MIN NUM OF IN-GRID MATCHES FOR ASTROM is < 5",
        "BIT16": "REL-PIXEL SCALE OUT OF RANGE: -0.20% - 2.00%",
        "BIT17": "PCT CHANGE IN SCAMP CHI-SQUARE is > 0.0%",
        "BIT18": "CHI-SQUARE FROM SECOND PASS SCAMP is > 30",
        "BIT19": "MAX IN-GRID MEDIAN ASTROM RESID is > 0.7\"",
        "BIT20": "PSF CREATION (HENCE PSF-FIT PHOT) NOT POSSIBLE",
        "BIT21": "PCT OF DETECTED MATCHES FOR ASTROM is < 50%",
        "STATUS": "overall processing flag: 0 = bad; 1 = usable",
        "CREATED": "File creation date/time (Pacific Time)",
        "DBPID": "Database processed-image ID",
        "PROCVSN": "Processing version",
        "DBFIELD": "Database field",
        "DBNID": "Database night ID",
        "DBEXPID": "Database exposure ID",
        "DBRID": "Database raw-image ID",
        "DBRCID": "Database readout-channel ID",
        "DBFID": "Database filter ID",
        "DBPIPEID": "Database pipeline ID",
        "FLXSCLZP": "FLXSCALE equivalent ZP / DG"
    }

class ScienceImageFactory(factory.alchemy.SQLAlchemyModelFactory):

    class Meta(BaseMeta):
        model = ScienceImage

    basename = f"ztf_20200428501227_000763_zi_c14_o_q4_sciimg.fits"
    type = "sci"
    field = 763
    qid = 4
    fid = 3
    ccdid = 14
    ra = 265.54654494
    dec = 50.1401914
    ra1 = 266.24913116
    dec1 = 50.55649774
    ra2 = 264.88952742
    dec2 = 50.58580637
    ra3 = 264.85615664
    dec3 = 49.71990481
    ra4 = 266.19103678
    dec4 = 49.69129586
    header = {
        "RA": "+17:33:29.45",
        "DEC": "+47:45:00",
        "HJD": 2458968.001713,
        "QID": 4,
        "RAD": 263.373,
        "CDS0": "#eval RGsettleT",
        "CDS1": "#eval PixelT",
        "CDS2": "#eval PixelT",
        "CDS3": "#eval PixelT - 2",
        "DECD": 47.75,
        "GAIN": 6.4,
        "RCID": 55,
        "BIT00": 0,
        "BIT01": 1,
        "BIT02": 2,
        "BIT03": 3,
        "BIT04": 4,
        "BIT05": 5,
        "BIT06": 6,
        "BIT07": 7,
        "BIT08": 8,
        "BIT09": 9,
        "BIT10": 10,
        "BIT11": 11,
        "BIT12": 12,
        "BIT13": 13,
        "BIT14": 14,
        "BIT15": 15,
        "BIT16": 16,
        "BIT17": 17,
        "BIT18": 18,
        "BIT19": 19,
        "BIT20": 20,
        "BIT21": 21,
        "BUNIT": "Data Value",
        "CCDID": 14,
        "CD1_1": -0.0002810897503477,
        "CD1_2": -9.522695258402e-06,
        "CD2_1": 9.446260482836e-06,
        "CD2_2": -0.0002810750535657,
        "DBFID": 3,
        "DBNID": 1213,
        "DBPID": 1213501715515,
        "DBRID": 12135017155,
        "EXPID": 121350171,
        "MAGZP": 25.535251,
        "NAXIS": 2,
        "OBJRA": "+17h33m29.45s",
        "OBLST": "+18:42:54",
        "OBSJD": 2458968.001713,
        "PV1_0": -6.061916029554e-06,
        "PV1_1": 0.9999829324481,
        "PV1_2": -2.776192486309e-05,
        "PV1_4": -0.0002976100291911,
        "PV1_5": 0.0002890545839448,
        "PV1_6": 3.651244422373e-06,
        "PV1_7": -0.0002442592605214,
        "PV1_8": 1.654194883434e-05,
        "PV1_9": -0.0001522862973596,
        "PV2_0": -2.55388995927e-05,
        "PV2_1": 0.9999751319839,
        "PV2_2": 5.800118934482e-05,
        "PV2_4": 0.0004650749029021,
        "PV2_5": -0.0002198602289224,
        "PV2_6": 0.0002622211765119,
        "PV2_7": -0.0002112408420576,
        "PV2_8": 2.600626758607e-05,
        "PV2_9": -0.0001736175199381,
        "SUNAZ": 63.3872,
        "TELRA": "+17:33:29.52",
        "ZPAVG": 25.594698,
        "ZPMED": 25.583,
        "AMP_ID": 3,
        "APCOR1": -0.92645901,
        "APCOR2": -0.46005851,
        "APCOR3": -0.25503701,
        "APCOR4": -0.0953845,
        "APCOR5": -0.0101355,
        "APCOR6": 0.0130485,
        "BITPIX": -32,
        "CCDSUM": "1 1",
        "CCD_ID": 14,
        "CLOUDS": -9999.0,
        "CLRAVG": 0.319,
        "CLRMED": 0.231,
        "CLRRMS": 0.23706,
        "CRPIX1": 1536.5,
        "CRPIX2": 1540.5,
        "CRVAL1": 265.5465544028,
        "CRVAL2": 50.14021694322,
        "CTYPE1": "RA---TPV",
        "CTYPE2": "DEC--TPV",
        "CUNIT1": "deg",
        "CUNIT2": "deg",
        "DBRCID": 55,
        "FILTER": "ZTF_i",
        "GCOUNT": 1,
        "INST_X": -2.11851e-06,
        "INST_Y": 6.20769e-07,
        "MAGLIM": 20.52,
        "MOONRA": 100.697,
        "NAXIS1": 3072,
        "NAXIS2": 3080,
        "OBJDEC": "+47d45m00s",
        "OBJRAD": 263.373,
        "OBSALT": 1668.0,
        "OBSLAT": 33.3573,
        "OBSLON": -116.8598,
        "OBSMJD": 58967.501713,
        "ORIGIN": "Zwicky Transient Facility",
        "PCOLOR": "R - i",
        "PCOUNT": 0,
        "PV1_10": 8.533353944661e-05,
        "PV1_12": 6.008700983438e-05,
        "PV1_13": 8.09885566748e-05,
        "PV1_14": -0.0001360614238381,
        "PV1_15": 0.0002098756373112,
        "PV1_16": 9.487029460218e-05,
        "PV2_10": 1.225216097988e-05,
        "PV2_12": 0.0002421917225606,
        "PV2_13": 0.0003059127434546,
        "PV2_14": -0.0001818901255026,
        "PV2_15": -0.0002407740005299,
        "PV2_16": -5.73887908413e-05,
        "SEEING": 1.654,
        "SIMPLE": True,
        "STATUS": 1,
        "SUNALT": -12.297,
        "TELDEC": "+47:45:0.1",
        "TELRAD": 263.373,
        "AIRMASS": 1.06,
        "ASTCHI2": 15.40110015869141,
        "ASTINST": 1,
        "ASTROMN": 1725,
        "AZIMUTH": 323.15,
        "CCDNAME": "S14",
        "CCD_ROT": 0.0,
        "CIDBIAS": 121255002,
        "CREATED": "2020-04-28T05:09:01",
        "DARKCUR": 1.0,
        "DBEXPID": 121350171,
        "DBFIELD": 763,
        "DETFIRM": "",
        "DETHEAT": 0.0,
        "DETSOFT": "",
        "DOME_AZ": 323.5,
        "EQUINOX": 2000.0,
        "EXPTIME": 30.0,
        "EXTNAME": "4",
        "FIELDID": 763,
        "FILTPOS": 1,
        "HDUVERS": 1,
        "HOURANG": "+01:08:50.85",
        "IMGTYPE": "object",
        "MOONALT": -33.4386,
        "MOONDEC": 23.2041,
        "MOONESB": 0.0,
        "NFRAMES": 8,
        "OBJDECD": 47.75,
        "PROCVSN": 1,
        "RADESYS": "ICRS",
        "RA_RATE": 0.0,
        "READNOI": 9.1,
        "TELDECD": 47.75,
        "THETA_X": 0.322827,
        "THETA_Y": 0.29866,
        "TRACKRA": 0.0,
        "WCSAXES": 2,
        "WCSNAME": "ZTF",
        "WCSVERS": "2.1",
        "WETNESS": 0.0,
        "WINDDIR": 71.0,
        "WINDSPD": 1.7,
        "AMP_NAME": "E",
        "APCORUN1": 0.0020122454,
        "APCORUN2": 0.0005486888,
        "APCORUN3": 0.0005811852,
        "APCORUN4": 0.0006903782,
        "APCORUN5": 0.0010050984,
        "APCORUN6": 0.0013816915,
        "ASTIRMS1": 0.0,
        "ASTIRMS2": 0.0,
        "ASTRRMS1": 1.523584540641e-05,
        "ASTRRMS2": 1.759957311169e-05,
        "CALNOTE1": "*** photometric-calibration keywords below are from PSF-fitting:",
        "CALNOTE2": "*** will also be applicable to aperture measurements on point",
        "CALNOTE3": "*** sources using psf_mag-aper_mag corrections below (if available)",
        "CCDTMP01": 170.239,
        "CCDTMP02": 175.4,
        "CCDTMP03": 167.795,
        "CCDTMP04": 169.768,
        "CCDTMP05": 167.72,
        "CCDTMP06": 166.116,
        "CCDTMP07": 168.613,
        "CCDTMP08": 168.352,
        "CCDTMP09": 168.395,
        "CCDTMP10": 166.422,
        "CCDTMP11": 166.818,
        "CCDTMP12": 168.74,
        "CCDTMP13": 169.669,
        "CCDTMP14": 167.954,
        "CCDTMP15": 168.667,
        "CCDTMP16": 169.522,
        "CIDHIFLT": 121255034,
        "CIDLOFLT": 55035,
        "CIDPMASK": 39655031,
        "CLRCOEFF": 0.186208,
        "CLRCOUNC": 8.4516e-06,
        "DBPIPEID": 15,
        "DEC_RATE": 0.0,
        "DETECTOR": "",
        "DEWPOINT": 22.0,
        "DEWPRESS": -9999.0,
        "DOMESTAT": "Open",
        "ELVATION": 70.62,
        "EXPOSURE": 30.0,
        "FGROUPNO": 1,
        "FILENAME": "ztf_20200428501227_000763_zi_c14_o.fits",
        "FILTERID": 3,
        "FITSSOFT": "0.99.20  April 27, 2020",
        "FIXAPERS": "2,3,4,6,10,14",
        "FLXSCALE": 0.0,
        "FOCUSPOS": 1.60949,
        "FRAMENUM": 813,
        "HEADTEMP": 162.673,
        "HOURANGD": 1.14746,
        "HUMIDITY": 22.0,
        "INFOBITS": 0,
        "INSTRUME": "ZTF/MOSAIC",
        "INST_ROT": 1.02139e-06,
        "MAGZEROP": 0.0,
        "MAGZPRMS": 0.024032,
        "MAGZPUNC": 1.3364e-06,
        "MODE_NUM": 0,
        "MOONILLF": 0.259471,
        "MOONPHAS": 118.755,
        "NMATCHES": 1216,
        "OBSERVAT": "Palomar Observatory",
        "OBSERVER": "ZTF Robotic Observing Software",
        "ORIGNAME": "ztf_20200428501227_000763_zi_c14_o.fits",
        "PHOTINST": 1,
        "PHOTIRMS": 0.0,
        "PHOTLINK": False,
        "PIXSCALE": 1.012,
        "PIXSCALX": 1.012,
        "PIXSCALY": 1.012,
        "PRESSURE": -9999.0,
        "PROGRMID": 2,
        "PROGRMPI": "Kulkarni",
        "QCOMMENT": "ZUDS2",
        "REQUSTID": 277,
        "ROBOSOFT": "0.99.20  April 27, 2020",
        "SATURATE": 58552.598,
        "SCAMPFIL": "gaia_000763_c14_q4.fits",
        "SCAMPPTH": "/ztf/ops/static/rc55/astrom",
        "SHUTCLSD": "2020-04-28T12:02:58.740",
        "SHUTOPEN": "2020-04-28T12:02:28.771",
        "TELESCOP": "Palomar 48-inch",
        "TEMPTURE": 18.8,
        "TRACKDEC": 0.0,
        "WINDSCAL": 0.0,
        "ZPCLRCOV": -2.7e-06,
        "ZPRMSALL": 0.05026
    }
    header_comments = {
        "RA": "Requested right ascension",
        "DEC": "Requested declination",
        "HJD": "Heliocentric Julian Day (days)",
        "QID": "",
        "RAD": "Requested right ascension (deg)",
        "CDS0": "",
        "CDS1": "2 - 1",
        "CDS2": "2 - 1 + SWsettleT",
        "CDS3": "",
        "DECD": "Requested declination (deg)",
        "GAIN": "Gain e-/adu",
        "RCID": "",
        "BIT00": "MEDIAN BACKGROUND LEVEL is >  4000.0 DN",
        "BIT01": "ROBUST PIXEL NOISE (RMS) is > 100.0 DN",
        "BIT02": "PERCENT OF SATURATED PIXELS is > 50.0%",
        "BIT03": "IMAGE SEEING (FWHM) is > 5.0\"",
        "BIT04": "MAGLIM FROM PSF CATALOG UNCERTS is < 17.5 mag",
        "BIT05": "MAGLIM FROM EMPIRICAL FORMULA is < 17.5 mag",
        "BIT06": "NUMBER OF PSF CATALOG SOURCES is < 10",
        "BIT07": "NUMBER OF SEXTRACTOR CAT SOURCES is < 20",
        "BIT08": "PHOTOMETRIC CALIBRATION NOT POSSIBLE",
        "BIT09": "NUMBER OF PHOTOMETRIC CALIBRATORS is < 30",
        "BIT10": "NUMBER OF MATCHES FOR PHOTOMETRIC CAL < 5",
        "BIT11": "RMS in PHOTCAL RESIDUALS (ZPs) is > 0.2 mag",
        "BIT12": "ASTROMETRIC CALIBRATION NOT POSSIBLE",
        "BIT13": "PCT OF REF-CAT MATCHES FOR ASTROM is < 0%",
        "BIT14": "NUMBER OF MATCHES FOR ASTROM CAL is < 100",
        "BIT15": "MIN NUM OF IN-GRID MATCHES FOR ASTROM is < 5",
        "BIT16": "REL-PIXEL SCALE OUT OF RANGE: -0.20% - 2.00%",
        "BIT17": "PCT CHANGE IN SCAMP CHI-SQUARE is > 0.0%",
        "BIT18": "CHI-SQUARE FROM SECOND PASS SCAMP is > 55",
        "BIT19": "MAX IN-GRID MEDIAN ASTROM RESID is > 0.7\"",
        "BIT20": "PSF CREATION (HENCE PSF-FIT PHOT) NOT POSSIBLE",
        "BIT21": "PCT OF DETECTED MATCHES FOR ASTROM is < 50%",
        "BUNIT": "",
        "CCDID": "",
        "CD1_1": "WCS matrix",
        "CD1_2": "WCS matrix",
        "CD2_1": "WCS matrix",
        "CD2_2": "WCS matrix",
        "DBFID": "Database filter ID",
        "DBNID": "Database night ID",
        "DBPID": "Database processed-image ID",
        "DBRID": "Database raw-image ID",
        "EXPID": "",
        "MAGZP": "Magnitude zero point for PSF-photometry [mag]",
        "NAXIS": "",
        "OBJRA": "Queue object right ascension",
        "OBLST": "Local Sidereal Time",
        "OBSJD": "Julian day corresponds to UTC-OBS (day)",
        "PV1_0": "Projection distortion parameter",
        "PV1_1": "Projection distortion parameter",
        "PV1_2": "Projection distortion parameter",
        "PV1_4": "Projection distortion parameter",
        "PV1_5": "Projection distortion parameter",
        "PV1_6": "Projection distortion parameter",
        "PV1_7": "Projection distortion parameter",
        "PV1_8": "Projection distortion parameter",
        "PV1_9": "Projection distortion parameter",
        "PV2_0": "Projection distortion parameter",
        "PV2_1": "Projection distortion parameter",
        "PV2_2": "Projection distortion parameter",
        "PV2_4": "Projection distortion parameter",
        "PV2_5": "Projection distortion parameter",
        "PV2_6": "Projection distortion parameter",
        "PV2_7": "Projection distortion parameter",
        "PV2_8": "Projection distortion parameter",
        "PV2_9": "Projection distortion parameter",
        "SUNAZ": "Sun azimuth (deg)",
        "TELRA": "Telescope right ascension",
        "ZPAVG": "Mag zero point from average of mag diffs [mag]",
        "ZPMED": "Mag zero point from median of mag diffs [mag]",
        "AMP_ID": "ID value of CCD amplifier",
        "APCOR1": "PSF_mag - AP_mag for AP.diam= 2.0 pix.",
        "APCOR2": "PSF_mag - AP_mag for AP.diam= 3.0 pix.",
        "APCOR3": "PSF_mag - AP_mag for AP.diam= 4.0 pix.",
        "APCOR4": "PSF_mag - AP_mag for AP.diam= 6.0 pix.",
        "APCOR5": "PSF_mag - AP_mag for AP.diam=10.0 pix.",
        "APCOR6": "PSF_mag - AP_mag for AP.diam=14.0 pix.",
        "BITPIX": "",
        "CCDSUM": "CCD binning",
        "CCD_ID": "ID value of CCD detector",
        "CLOUDS": "Cloud sensor reading",
        "CLRAVG": "Average calibrator color [mag]",
        "CLRMED": "Median calibrator color [mag]",
        "CLRRMS": "RMS in calibrator color [mag]",
        "CRPIX1": "Coord system X ref pixel",
        "CRPIX2": "Coord system Y ref pixel",
        "CRVAL1": "Coord system value at CRPIX1",
        "CRVAL2": "Coord system value at CRPIX2",
        "CTYPE1": "Name of coord X axis",
        "CTYPE2": "Name of coord Y axis",
        "CUNIT1": "Coordinate units for X axis",
        "CUNIT2": "Coordinate units for Y axis",
        "DBRCID": "Database readout-channel ID",
        "FILTER": "Filter name",
        "GCOUNT": "required keyword; must = 1",
        "INST_X": "Hexapod X (RA) axis position (mm)",
        "INST_Y": "Hexapod Y (Dec) axis position (mm)",
        "MAGLIM": "5-sigma mag-limit in PSF-fit catalog [mag]",
        "MOONRA": "Moon J2000.0 R.A. (deg)",
        "NAXIS1": "",
        "NAXIS2": "",
        "OBJDEC": "Queue object declination",
        "OBJRAD": "Object right ascension (deg)",
        "OBSALT": "Observatory altitude (m)",
        "OBSLAT": "Observatory latitude (deg E)",
        "OBSLON": "Observatory longitude (deg)",
        "OBSMJD": "MJD corresponds to UTC-OBS (day)",
        "ORIGIN": "Data origin",
        "PCOLOR": "Calibrator color used for PSF-fit calibration",
        "PCOUNT": "required keyword; must = 0",
        "PV1_10": "Projection distortion parameter",
        "PV1_12": "Projection distortion parameter",
        "PV1_13": "Projection distortion parameter",
        "PV1_14": "Projection distortion parameter",
        "PV1_15": "Projection distortion parameter",
        "PV1_16": "Projection distortion parameter",
        "PV2_10": "Projection distortion parameter",
        "PV2_12": "Projection distortion parameter",
        "PV2_13": "Projection distortion parameter",
        "PV2_14": "Projection distortion parameter",
        "PV2_15": "Projection distortion parameter",
        "PV2_16": "Projection distortion parameter",
        "SEEING": "seeing FWHM [pixels]",
        "SIMPLE": "file does conform to FITS standard",
        "STATUS": "overall processing flag: 0 = bad; 1 = usable",
        "SUNALT": "Sun altitude (deg)",
        "TELDEC": "Telescope declination",
        "TELRAD": "Telescope right ascension (deg)",
        "AIRMASS": "Airmass (Hardie)",
        "ASTCHI2": "",
        "ASTINST": "SCAMP astrometric instrument label",
        "ASTROMN": "",
        "AZIMUTH": "Telescope azimuth (deg)",
        "CCDNAME": "Detector name or serial number",
        "CCD_ROT": "CCD rotation angle",
        "CIDBIAS": "calibration DB ID for bias image input",
        "CREATED": "File creation date/time (Pacific Time)",
        "DARKCUR": "Dark current e-/s @ 150 K",
        "DBEXPID": "Database exposure ID",
        "DBFIELD": "Database field",
        "DETFIRM": "Detector firmware version",
        "DETHEAT": "Detector focal plane heater power (%)",
        "DETSOFT": "Detector software version",
        "DOME_AZ": "Dome azimuth (deg)",
        "EQUINOX": "Equinox",
        "EXPTIME": "Requested Exposure Time (s)",
        "EXTNAME": "",
        "FIELDID": "ZTF field ID number",
        "FILTPOS": "Filter position",
        "HDUVERS": "",
        "HOURANG": "Hour angle",
        "IMGTYPE": "Image type",
        "MOONALT": "Moon altitude (deg)",
        "MOONDEC": "Moon J2000.0 Dec. (deg)",
        "MOONESB": "Moon excess in sky brightness V-band",
        "NFRAMES": "number of frames in FITS file",
        "OBJDECD": "Object declination (deg)",
        "PROCVSN": "Processing version",
        "RADESYS": "Coordinate reference system",
        "RA_RATE": "RA tracking rate (\"/hr)",
        "READNOI": "Read noise e-",
        "TELDECD": "Telescope declination (deg)",
        "THETA_X": "Hexapod rotation about INST_X (deg)",
        "THETA_Y": "Hexapod rotation about INST_Y (deg)",
        "TRACKRA": "TCS RA non-sidereal tracking rate (\"/hr)",
        "WCSAXES": "Number of WCS axes",
        "WCSNAME": "Name of the WCS coordinate system",
        "WCSVERS": "WCS version number",
        "WETNESS": "Wetness sensor reading",
        "WINDDIR": "Outside wind direction (deg)",
        "WINDSPD": "Outside wind speed",
        "AMP_NAME": "Name of CCD amplifier",
        "APCORUN1": "Uncertainty in APCOR1 [mag]",
        "APCORUN2": "Uncertainty in APCOR2 [mag]",
        "APCORUN3": "Uncertainty in APCOR3 [mag]",
        "APCORUN4": "Uncertainty in APCOR4 [mag]",
        "APCORUN5": "Uncertainty in APCOR5 [mag]",
        "APCORUN6": "Uncertainty in APCOR6 [mag]",
        "ASTIRMS1": "Astrom. dispersion RMS (intern., high S/N)",
        "ASTIRMS2": "Astrom. dispersion RMS (intern., high S/N)",
        "ASTRRMS1": "Astrom. dispersion RMS (ref., high S/N)",
        "ASTRRMS2": "Astrom. dispersion RMS (ref., high S/N)",
        "CALNOTE1": "",
        "CALNOTE2": "",
        "CALNOTE3": "",
        "CCDTMP01": "CCD temperature 01 (K)",
        "CCDTMP02": "CCD temperature 02 (K)",
        "CCDTMP03": "CCD temperature 03 (K)",
        "CCDTMP04": "CCD temperature 04 (K)",
        "CCDTMP05": "CCD temperature 05 (K)",
        "CCDTMP06": "CCD temperature 06 (K)",
        "CCDTMP07": "CCD temperature 07 (K)",
        "CCDTMP08": "CCD temperature 08 (K)",
        "CCDTMP09": "CCD temperature 09 (K)",
        "CCDTMP10": "CCD temperature 10 (K)",
        "CCDTMP11": "CCD temperature 11 (K)",
        "CCDTMP12": "CCD temperature 12 (K)",
        "CCDTMP13": "CCD temperature 13 (K)",
        "CCDTMP14": "CCD temperature 14 (K)",
        "CCDTMP15": "CCD temperature 15 (K)",
        "CCDTMP16": "CCD temperature 16 (K)",
        "CIDHIFLT": "calibration DB ID for hi-nu flat image input",
        "CIDLOFLT": "calibration DB ID for low-nu flat image input",
        "CIDPMASK": "calibration DB ID for pmask image input",
        "CLRCOEFF": "Color coefficient from linear fit",
        "CLRCOUNC": "Color coefficient uncertainty from linear fit",
        "DBPIPEID": "Database pipeline ID",
        "DEC_RATE": "Dec tracking rate (\"/hr)",
        "DETECTOR": "Detector controller",
        "DEWPOINT": "Dewpoint (C)",
        "DEWPRESS": "Dewar pressure (milli-torr)",
        "DOMESTAT": "Dome status at beginning of exposure",
        "ELVATION": "Telescope elevation (deg)",
        "EXPOSURE": "Total Exposure Time (s)",
        "FGROUPNO": "SCAMP field group label",
        "FILENAME": "File name",
        "FILTERID": "Filter ID code",
        "FITSSOFT": "FITS software version",
        "FIXAPERS": "fixed aperture diameters used [pixels]",
        "FLXSCALE": "SCAMP relative flux scale",
        "FOCUSPOS": "Hexapod Z (Focus) axis position (mm)",
        "FRAMENUM": "Detector frame number",
        "HEADTEMP": "Cryo cooler cold head temp (K)",
        "HOURANGD": "Hour angle (deg)",
        "HUMIDITY": "Humidity",
        "INFOBITS": "infoBits string; bit(s) = null",
        "INSTRUME": "Instrument name",
        "INST_ROT": "Hexapod rotation about Z axis (deg)",
        "MAGZEROP": "SCAMP zero-point",
        "MAGZPRMS": "Magnitude zero point RMS deviation [mag]",
        "MAGZPUNC": "Magnitude zero point uncertainty [mag]",
        "MODE_NUM": "Mode identifying key",
        "MOONILLF": "Moon illuminated fraction (frac)",
        "MOONPHAS": "Moon phase angle (deg)",
        "NMATCHES": "Number of photometric calibrators used",
        "OBSERVAT": "Observatory",
        "OBSERVER": "Observer",
        "ORIGNAME": "Original file name",
        "PHOTINST": "SCAMP photometric instrument label",
        "PHOTIRMS": "mag dispersion RMS (internal, high S/N)",
        "PHOTLINK": "True if linked to a photometric field",
        "PIXSCALE": "Pixel scale, in arcsec per pixel",
        "PIXSCALX": "Pixel scale in X",
        "PIXSCALY": "Pixel scale in Y",
        "PRESSURE": "Atmospheric pressure, millibars",
        "PROGRMID": "Queue program ID",
        "PROGRMPI": "Queue program PI",
        "QCOMMENT": "Queue comment",
        "REQUSTID": "Queue request ID",
        "ROBOSOFT": "Robotic software version",
        "SATURATE": "Saturation value in final science image [DN]",
        "SCAMPFIL": "",
        "SCAMPPTH": "",
        "SHUTCLSD": "Shutter close time",
        "SHUTOPEN": "Shutter open time",
        "TELESCOP": "Observatory telescope",
        "TEMPTURE": "Outside air temperature (C)",
        "TRACKDEC": "TCS Dec non-sidereal tracking rate (\"/hr)",
        "WINDSCAL": "Windscreen elevation (deg)",
        "ZPCLRCOV": "Covariance in MAGZP and CLRCOEFF [mag^2]",
        "ZPRMSALL": "Mag zero point RMS from all mag diffs [mag]"
    }
    filtercode = "zi"
    obsjd = 2458968.001713
    infobits = 0
    pid = 1213501715515
    nid = 1213
    expid = 121350171
    itid = 1
    obsdate = "2020-04-28T12:02:28.281000"
    seeing = 1.69956994
    airmass = 1.05999994
    moonillf = 0.259470999
    moonesb = 0.0
    maglimit = 20.1413994
    crpix1 = 1536.5
    crpix2 = 1540.5
    crval1 = 265.5465544028
    crval2 = 50.14021694322
    cd11 = -0.0002810897503477
    cd12 = -9.522695258402e-06
    cd21 = 9.446260482836e-06
    cd22 = -0.0002810750535657
    ipac_gid = 2
    imgtypecode = "o"
    exptime = 30.0
    filefracday = 20200428501227