예제 #1
0
def getPlatepar(config):
    """ Downloads a new platepar from the server of uses an existing one. """

    # Download a new platepar from the server, if present
    downloadNewPlatepar(config)

    # Load the default platepar if it is available
    platepar = None
    platepar_fmt = None
    platepar_path = os.path.join(os.getcwd(), config.platepar_name)
    if os.path.exists(platepar_path):
        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path)

        log.info('Loaded platepar: ' + platepar_path)

    else:

        log.info('No platepar file found!')

    return platepar, platepar_path, platepar_fmt
예제 #2
0
def main(dir_path):
    rms_path = os.path.abspath(os.path.dirname(os.path.dirname(RMS.__file__)))

    try:
        config = RMS.ConfigReader.parse(join(dir_path, ".config"))
    except FileNotFoundError:
        logger.warning(f"Could not find .config in {dir_path}, using default")
        config = RMS.ConfigReader.parse(join(rms_path, ".config"))

    try:
        with open(join(dir_path, "platepars_all_recalibrated.json"), "r") as f:
            platepars_recalibrated = json.load(f)
    except FileNotFoundError:
        logger.warning(f"Could not find platepars_recalibrated in {dir_path}")
        platepars_recalibrated = {}

    global_platepar = Platepar()

    if os.path.isfile(join(dir_path, "platepar_cmn2010.cal")):
        global_platepar.read(join(dir_path, "platepar_cmn2010.cal"))
    else:
        logger.warning(
            f"Couldn't find platepar_cmn2010.cal in {dir_path}, using default")
        global_platepar.read(join(rms_path, "platepar_cmn2010.cal"))

    for ff_filename in glob(join(dir_path, "FF*fits")):
        logger.info(f"Updating {ff_filename}")
        add_fffits_metadata(ff_filename, config, platepars_recalibrated,
                            global_platepar)
예제 #3
0
def generateCalibrationReport(config,
                              night_dir_path,
                              match_radius=2.0,
                              platepar=None,
                              show_graphs=False):
    """ Given the folder of the night, find the Calstars file, check the star fit and generate a report
        with the quality of the calibration. The report contains information about both the astrometry and
        the photometry calibration. Graphs will be saved in the given directory of the night.
    
    Arguments:
        config: [Config instance]
        night_dir_path: [str] Full path to the directory of the night.

    Keyword arguments:
        match_radius: [float] Match radius for star matching between image and catalog stars (px).
        platepar: [Platepar instance] Use this platepar instead of finding one in the folder.
        show_graphs: [bool] Show the graphs on the screen. False by default.

    Return:
        None
    """

    # Find the CALSTARS file in the given folder
    calstars_file = None
    for calstars_file in os.listdir(night_dir_path):
        if ('CALSTARS' in calstars_file) and ('.txt' in calstars_file):
            break

    if calstars_file is None:
        print('CALSTARS file could not be found in the given directory!')
        return None

    # Load the calstars file
    star_list = readCALSTARS(night_dir_path, calstars_file)

    ### Load recalibrated platepars, if they exist ###

    # Find recalibrated platepars file per FF file
    platepars_recalibrated_file = None
    for file_name in os.listdir(night_dir_path):
        if file_name == config.platepars_recalibrated_name:
            platepars_recalibrated_file = file_name
            break

    # Load all recalibrated platepars if the file is available
    recalibrated_platepars = None
    if platepars_recalibrated_file:
        with open(os.path.join(night_dir_path,
                               platepars_recalibrated_file)) as f:
            recalibrated_platepars = json.load(f)
            print(
                'Loaded recalibrated platepars JSON file for the calibration report...'
            )

    ### ###

    ### Load the platepar file ###

    # Find the platepar file in the given directory if it was not given
    if platepar is None:

        # Find the platepar file
        platepar_file = None
        for file_name in os.listdir(night_dir_path):
            if file_name == config.platepar_name:
                platepar_file = file_name
                break

        if platepar_file is None:
            print('The platepar cannot be found in the night directory!')
            return None

        # Load the platepar file
        platepar = Platepar()
        platepar.read(os.path.join(night_dir_path, platepar_file),
                      use_flat=config.use_flat)

    ### ###

    night_name = os.path.split(night_dir_path.strip(os.sep))[1]

    # Go one mag deeper than in the config
    lim_mag = config.catalog_mag_limit + 1

    # Load catalog stars (load one magnitude deeper)
    catalog_stars, mag_band_str, config.star_catalog_band_ratios = StarCatalog.readStarCatalog(\
        config.star_catalog_path, config.star_catalog_file, lim_mag=lim_mag, \
        mag_band_ratios=config.star_catalog_band_ratios)

    ### Take only those CALSTARS entires for which FF files exist in the folder ###

    # Get a list of FF files in the folder
    ff_list = []
    for file_name in os.listdir(night_dir_path):
        if validFFName(file_name):
            ff_list.append(file_name)

    # Filter out calstars entries, generate a star dictionary where the keys are JDs of FFs
    star_dict = {}
    ff_dict = {}
    for entry in star_list:

        ff_name, star_data = entry

        # Check if the FF from CALSTARS exists in the folder
        if ff_name not in ff_list:
            continue

        dt = getMiddleTimeFF(ff_name, config.fps, ret_milliseconds=True)
        jd = date2JD(*dt)

        # Add the time and the stars to the dict
        star_dict[jd] = star_data
        ff_dict[jd] = ff_name

    ### ###

    # If there are no FF files in the directory, don't generate a report
    if len(star_dict) == 0:
        print('No FF files from the CALSTARS file in the directory!')
        return None

    # If the recalibrated platepars file exists, take the one with the most stars
    max_jd = 0
    using_recalib_platepars = False
    if recalibrated_platepars is not None:
        max_stars = 0
        for ff_name_temp in recalibrated_platepars:

            # Compute the Julian date of the FF middle
            dt = getMiddleTimeFF(ff_name_temp,
                                 config.fps,
                                 ret_milliseconds=True)
            jd = date2JD(*dt)

            # Check that this file exists in CALSTARS and the list of FF files
            if (jd not in star_dict) or (jd not in ff_dict):
                continue

            # Check if the number of stars on this FF file is larger than the before
            if len(star_dict[jd]) > max_stars:
                max_jd = jd
                max_stars = len(star_dict[jd])

        # Set a flag to indicate if using recalibrated platepars has failed
        if max_jd == 0:
            using_recalib_platepars = False
        else:

            print('Using recalibrated platepars, file:', ff_dict[max_jd])
            using_recalib_platepars = True

            # Select the platepar where the FF file has the most stars
            platepar_dict = recalibrated_platepars[ff_dict[max_jd]]
            platepar = Platepar()
            platepar.loadFromDict(platepar_dict, use_flat=config.use_flat)

            filtered_star_dict = {max_jd: star_dict[max_jd]}

            # Match stars on the image with the stars in the catalog
            n_matched, avg_dist, cost, matched_stars = matchStarsResiduals(config, platepar, catalog_stars, \
                filtered_star_dict, match_radius, ret_nmatch=True, lim_mag=lim_mag)

            max_matched_stars = n_matched

    # Otherwise take the optimal FF file for evaluation
    if (recalibrated_platepars is None) or (not using_recalib_platepars):

        # If there are more than a set number of FF files to evaluate, choose only the ones with most stars on
        #   the image
        if len(star_dict) > config.calstars_files_N:

            # Find JDs of FF files with most stars on them
            top_nstars_indices = np.argsort([len(x) for x in star_dict.values()])[::-1][:config.calstars_files_N \
                - 1]

            filtered_star_dict = {}
            for i in top_nstars_indices:
                filtered_star_dict[list(star_dict.keys())[i]] = list(
                    star_dict.values())[i]

            star_dict = filtered_star_dict

        # Match stars on the image with the stars in the catalog
        n_matched, avg_dist, cost, matched_stars = matchStarsResiduals(config, platepar, catalog_stars, \
            star_dict, match_radius, ret_nmatch=True, lim_mag=lim_mag)

    # If no recalibrated platepars where found, find the image with the largest number of matched stars
    if (not using_recalib_platepars) or (max_jd == 0):

        max_jd = 0
        max_matched_stars = 0
        for jd in matched_stars:
            _, _, distances = matched_stars[jd]
            if len(distances) > max_matched_stars:
                max_jd = jd
                max_matched_stars = len(distances)

        # If there are no matched stars, use the image with the largest number of detected stars
        if max_matched_stars <= 2:
            max_jd = max(star_dict, key=lambda x: len(star_dict[x]))
            distances = [np.inf]

    # Take the FF file with the largest number of matched stars
    ff_name = ff_dict[max_jd]

    # Load the FF file
    ff = readFF(night_dir_path, ff_name)
    img_h, img_w = ff.avepixel.shape

    dpi = 200
    plt.figure(figsize=(ff.avepixel.shape[1] / dpi,
                        ff.avepixel.shape[0] / dpi),
               dpi=dpi)

    # Take the average pixel
    img = ff.avepixel

    # Slightly adjust the levels
    img = Image.adjustLevels(img, np.percentile(img, 1.0), 1.3,
                             np.percentile(img, 99.99))

    plt.imshow(img, cmap='gray', interpolation='nearest')

    legend_handles = []

    # Plot detected stars
    for img_star in star_dict[max_jd]:

        y, x, _, _ = img_star

        rect_side = 5 * match_radius
        square_patch = plt.Rectangle((x - rect_side/2, y - rect_side/2), rect_side, rect_side, color='g', \
            fill=False, label='Image stars')

        plt.gca().add_artist(square_patch)

    legend_handles.append(square_patch)

    # If there are matched stars, plot them
    if max_matched_stars > 2:

        # Take the solution with the largest number of matched stars
        image_stars, matched_catalog_stars, distances = matched_stars[max_jd]

        # Plot matched stars
        for img_star in image_stars:
            x, y, _, _ = img_star

            circle_patch = plt.Circle((y, x), radius=3*match_radius, color='y', fill=False, \
                label='Matched stars')

            plt.gca().add_artist(circle_patch)

        legend_handles.append(circle_patch)

        ### Plot match residuals ###

        # Compute preducted positions of matched image stars from the catalog
        x_predicted, y_predicted = raDecToXYPP(matched_catalog_stars[:, 0], \
            matched_catalog_stars[:, 1], max_jd, platepar)

        img_y, img_x, _, _ = image_stars.T

        delta_x = x_predicted - img_x
        delta_y = y_predicted - img_y

        # Compute image residual and angle of the error
        res_angle = np.arctan2(delta_y, delta_x)
        res_distance = np.sqrt(delta_x**2 + delta_y**2)

        # Calculate coordinates of the beginning of the residual line
        res_x_beg = img_x + 3 * match_radius * np.cos(res_angle)
        res_y_beg = img_y + 3 * match_radius * np.sin(res_angle)

        # Calculate coordinates of the end of the residual line
        res_x_end = img_x + 100 * np.cos(res_angle) * res_distance
        res_y_end = img_y + 100 * np.sin(res_angle) * res_distance

        # Plot the 100x residuals
        for i in range(len(x_predicted)):
            res_plot = plt.plot([res_x_beg[i], res_x_end[i]], [res_y_beg[i], res_y_end[i]], color='orange', \
                lw=0.5, label='100x residuals')

        legend_handles.append(res_plot[0])

        ### ###

    else:

        distances = [np.inf]

        # If there are no matched stars, plot large text in the middle of the screen
        plt.text(img_w / 2,
                 img_h / 2,
                 "NO MATCHED STARS!",
                 color='r',
                 alpha=0.5,
                 fontsize=20,
                 ha='center',
                 va='center')

    ### Plot positions of catalog stars to the limiting magnitude of the faintest matched star + 1 mag ###

    # Find the faintest magnitude among matched stars
    if max_matched_stars > 2:
        faintest_mag = np.max(matched_catalog_stars[:, 2]) + 1

    else:
        # If there are no matched stars, use the limiting magnitude from config
        faintest_mag = config.catalog_mag_limit + 1

    # Estimate RA,dec of the centre of the FOV
    _, RA_c, dec_c, _ = xyToRaDecPP([jd2Date(max_jd)], [platepar.X_res / 2],
                                    [platepar.Y_res / 2], [1], platepar)

    RA_c = RA_c[0]
    dec_c = dec_c[0]

    fov_radius = np.hypot(*computeFOVSize(platepar))

    # Get stars from the catalog around the defined center in a given radius
    _, extracted_catalog = subsetCatalog(catalog_stars, RA_c, dec_c,
                                         fov_radius, faintest_mag)
    ra_catalog, dec_catalog, mag_catalog = extracted_catalog.T

    # Compute image positions of all catalog stars that should be on the image
    x_catalog, y_catalog = raDecToXYPP(ra_catalog, dec_catalog, max_jd,
                                       platepar)

    # Filter all catalog stars outside the image
    temp_arr = np.c_[x_catalog, y_catalog, mag_catalog]
    temp_arr = temp_arr[temp_arr[:, 0] >= 0]
    temp_arr = temp_arr[temp_arr[:, 0] <= ff.avepixel.shape[1]]
    temp_arr = temp_arr[temp_arr[:, 1] >= 0]
    temp_arr = temp_arr[temp_arr[:, 1] <= ff.avepixel.shape[0]]
    x_catalog, y_catalog, mag_catalog = temp_arr.T

    # Plot catalog stars on the image
    cat_stars_handle = plt.scatter(x_catalog, y_catalog, c='none', marker='D', lw=1.0, alpha=0.4, \
        s=((4.0 + (faintest_mag - mag_catalog))/3.0)**(2*2.512), edgecolor='r', label='Catalog stars')

    legend_handles.append(cat_stars_handle)

    ### ###

    # Add info text in the corner
    info_text = ff_dict[max_jd] + '\n' \
        + "Matched stars within {:.1f} px radius: {:d}/{:d} \n".format(match_radius, max_matched_stars, \
            len(star_dict[max_jd])) \
        + "Median distance = {:.2f} px\n".format(np.median(distances)) \
        + "Catalog lim mag = {:.1f}".format(lim_mag)

    plt.text(10, 10, info_text, bbox=dict(facecolor='black', alpha=0.5), va='top', ha='left', fontsize=4, \
        color='w', family='monospace')

    legend = plt.legend(handles=legend_handles,
                        prop={'size': 4},
                        loc='upper right')
    legend.get_frame().set_facecolor('k')
    legend.get_frame().set_edgecolor('k')
    for txt in legend.get_texts():
        txt.set_color('w')

    ### Add FOV info (centre, size) ###

    # Mark FOV centre
    plt.scatter(platepar.X_res / 2,
                platepar.Y_res / 2,
                marker='+',
                s=20,
                c='r',
                zorder=4)

    # Compute FOV centre alt/az
    azim_centre, alt_centre = raDec2AltAz(max_jd, platepar.lon, platepar.lat,
                                          RA_c, dec_c)

    # Compute FOV size
    fov_h, fov_v = computeFOVSize(platepar)

    # Compute the rotation wrt. horizon
    rot_horizon = rotationWrtHorizon(platepar)

    fov_centre_text = "Azim  = {:6.2f}$\\degree$\n".format(azim_centre) \
                    + "Alt   = {:6.2f}$\\degree$\n".format(alt_centre) \
                    + "Rot h = {:6.2f}$\\degree$\n".format(rot_horizon) \
                    + "FOV h = {:6.2f}$\\degree$\n".format(fov_h) \
                    + "FOV v = {:6.2f}$\\degree$".format(fov_v) \

    plt.text(10, platepar.Y_res - 10, fov_centre_text, bbox=dict(facecolor='black', alpha=0.5), \
        va='bottom', ha='left', fontsize=4, color='w', family='monospace')

    ### ###

    # Plot RA/Dec gridlines #
    addEquatorialGrid(plt, platepar, max_jd)

    plt.axis('off')
    plt.gca().get_xaxis().set_visible(False)
    plt.gca().get_yaxis().set_visible(False)

    plt.xlim([0, ff.avepixel.shape[1]])
    plt.ylim([ff.avepixel.shape[0], 0])

    # Remove the margins
    plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)

    plt.savefig(os.path.join(night_dir_path, night_name + '_calib_report_astrometry.jpg'), \
        bbox_inches='tight', pad_inches=0, dpi=dpi)

    if show_graphs:
        plt.show()

    else:
        plt.clf()
        plt.close()

    if max_matched_stars > 2:

        ### PHOTOMETRY FIT ###

        # If a flat is used, set the vignetting coeff to 0
        if config.use_flat:
            platepar.vignetting_coeff = 0.0

        # Extact intensities and mangitudes
        star_intensities = image_stars[:, 2]
        catalog_mags = matched_catalog_stars[:, 2]

        # Compute radius of every star from image centre
        radius_arr = np.hypot(image_stars[:, 0] - img_h / 2,
                              image_stars[:, 1] - img_w / 2)

        # Fit the photometry on automated star intensities (use the fixed vignetting coeff, use robust fit)
        photom_params, fit_stddev, fit_resid, star_intensities, radius_arr, catalog_mags = \
            photometryFitRobust(star_intensities, radius_arr, catalog_mags, \
            fixed_vignetting=platepar.vignetting_coeff)

        photom_offset, _ = photom_params

        ### ###

        ### PLOT PHOTOMETRY ###
        # Note: An almost identical code exists in RMS.Astrometry.SkyFit in the PlateTool.photometry function

        dpi = 130
        fig_p, (ax_p, ax_r) = plt.subplots(nrows=2, facecolor=None, figsize=(6.0, 7.0), dpi=dpi, \
            gridspec_kw={'height_ratios':[2, 1]})

        # Plot raw star intensities
        ax_p.scatter(-2.5 * np.log10(star_intensities),
                     catalog_mags,
                     s=5,
                     c='r',
                     alpha=0.5,
                     label="Raw")

        # If a flat is used, disregard the vignetting
        if not config.use_flat:

            # Plot intensities of image stars corrected for vignetting
            lsp_corr_arr = np.log10(correctVignetting(star_intensities, radius_arr, \
                platepar.vignetting_coeff))
            ax_p.scatter(-2.5*lsp_corr_arr, catalog_mags, s=5, c='b', alpha=0.5, \
                label="Corrected for vignetting")

        # Plot photometric offset from the platepar
        x_min, x_max = ax_p.get_xlim()
        y_min, y_max = ax_p.get_ylim()

        x_min_w = x_min - 3
        x_max_w = x_max + 3
        y_min_w = y_min - 3
        y_max_w = y_max + 3

        photometry_info = "Platepar: {:+.1f}*LSP + {:.2f} +/- {:.2f}".format(platepar.mag_0, \
            platepar.mag_lev, platepar.mag_lev_stddev) \
            + "\nVignetting coeff = {:.5f}".format(platepar.vignetting_coeff) \
            + "\nGamma = {:.2f}".format(platepar.gamma)

        # Plot the photometry calibration from the platepar
        logsum_arr = np.linspace(x_min_w, x_max_w, 10)
        ax_p.plot(logsum_arr, logsum_arr + platepar.mag_lev, label=photometry_info, linestyle='--', \
            color='k', alpha=0.5)

        # Plot the fitted photometry calibration
        fit_info = "Fit: {:+.1f}*LSP + {:.2f} +/- {:.2f}".format(
            -2.5, photom_offset, fit_stddev)
        ax_p.plot(logsum_arr,
                  logsum_arr + photom_offset,
                  label=fit_info,
                  linestyle='--',
                  color='b',
                  alpha=0.75)

        ax_p.legend()

        ax_p.set_ylabel("Catalog magnitude ({:s})".format(mag_band_str))
        ax_p.set_xlabel("Uncalibrated magnitude")

        # Set wider axis limits
        ax_p.set_xlim(x_min_w, x_max_w)
        ax_p.set_ylim(y_min_w, y_max_w)

        ax_p.invert_yaxis()
        ax_p.invert_xaxis()

        ax_p.grid()

        ### Plot photometry vs radius ###

        img_diagonal = np.hypot(img_h / 2, img_w / 2)

        # Plot photometry residuals (including vignetting)
        ax_r.scatter(radius_arr, fit_resid, c='b', alpha=0.75, s=5, zorder=3)

        # Plot a zero line
        ax_r.plot(np.linspace(0, img_diagonal, 10), np.zeros(10), linestyle='dashed', alpha=0.5, \
            color='k')

        # Plot only when no flat is used
        if not config.use_flat:

            #  Plot radius from centre vs. fit residual
            fit_resids_novignetting = catalog_mags - photomLine((np.array(star_intensities), \
                np.array(radius_arr)), photom_offset, 0.0)
            ax_r.scatter(radius_arr,
                         fit_resids_novignetting,
                         s=5,
                         c='r',
                         alpha=0.5,
                         zorder=3)

            px_sum_tmp = 1000
            radius_arr_tmp = np.linspace(0, img_diagonal, 50)

            # Plot vignetting loss curve
            vignetting_loss = 2.5*np.log10(px_sum_tmp) \
                - 2.5*np.log10(correctVignetting(px_sum_tmp, radius_arr_tmp, \
                    platepar.vignetting_coeff))

            ax_r.plot(radius_arr_tmp,
                      vignetting_loss,
                      linestyle='dotted',
                      alpha=0.5,
                      color='k')

        ax_r.grid()

        ax_r.set_ylabel("Fit residuals (mag)")
        ax_r.set_xlabel("Radius from centre (px)")

        ax_r.set_xlim(0, img_diagonal)

        ### ###

        plt.tight_layout()

        plt.savefig(os.path.join(night_dir_path,
                                 night_name + '_calib_report_photometry.png'),
                    dpi=150)

        if show_graphs:
            plt.show()

        else:
            plt.clf()
            plt.close()
예제 #4
0
def getPlatepar(config, night_data_dir):
    """ Downloads a new platepar from the server of uses an existing one. 
    
    Arguments:
        Config: [Config instance]
        night_data_dir: [str] Full path to the data directory.

    Return:
        platepar, platepar_path, platepar_fmt
    """


    # Download a new platepar from the server, if present
    downloadNewPlatepar(config)


    # Construct path to the platepar in the night directory
    platepar_night_dir_path = os.path.join(night_data_dir, config.platepar_name)

    # Load the default platepar from the RMS if it is available
    platepar = None
    platepar_fmt = None
    platepar_path = os.path.join(os.getcwd(), config.platepar_name)
    if os.path.exists(platepar_path):
        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path, use_flat=config.use_flat)

        log.info('Loaded platepar from RMS directory: ' + platepar_path)


    # Otherwise, try to find the platepar in the data directory
    elif os.path.exists(platepar_night_dir_path):

        platepar_path = platepar_night_dir_path

        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path, use_flat=config.use_flat)

        log.info('Loaded platepar from night directory: ' + platepar_path)

    else:

        log.info('No platepar file found!')


    if platepar is not None:
        
        # Make sure that the station code from the config and the platepar match
        if platepar.station_code is not None:
            if config.stationID != platepar.station_code:

                # If they don't match, don't use this platepar
                log.info("The station code in the platepar doesn't match the station code in config file! Not using the platepar...")

                platepar = None
                platepar_fmt = None


    # Make sure the image resolution matches
    if platepar is not None:
        if (int(config.width) != int(platepar.X_res)) or (int(config.height) != int(platepar.Y_res)):

            # If they don't match, don't use this platepar
            log.info("The image resolution in config and platepar don't match! Not using the platepar...")

            platepar = None
            platepar_fmt = None

        

    return platepar, platepar_path, platepar_fmt
예제 #5
0
    arg_parser = argparse.ArgumentParser(
        description="""Compute the FOV area given the platepar and mask files. \
        """,
        formatter_class=argparse.RawTextHelpFormatter)

    arg_parser.add_argument('platepar', metavar='PLATEPAR', type=str, \
                    help="Path to the platepar file.")

    arg_parser.add_argument('mask', metavar='MASK', type=str, nargs='?', \
                    help="Path to the mask file.")

    # Parse the command line arguments
    cml_args = arg_parser.parse_args()

    #########################

    # Load the platepar file
    pp = Platepar()
    pp.read(cml_args.platepar)

    # Load the mask file
    if cml_args.mask is not None:
        mask = loadMask(cml_args.mask)
    else:
        mask = None

    # Compute the FOV geo points
    area_list = fovArea(pp, mask)

    for side_points in area_list:
        print(side_points)
예제 #6
0
def writeCAL(night_dir, config, platepar):
    """ Write the CAL file. 

    Arguments:
        night_dir: [str] Path of the night directory where the file will be saved. This folder will be used
            to construct the name of CAL file.
        config: [Config]
        platepar: [Platepar]

    Return:
        file_name: [str] Name of the CAL file.

    """

    # Remove the last slash, if it exists
    if night_dir[-1] == os.sep:
        night_dir = night_dir[:-1]

    # Extract time from night name
    _, night_name = os.path.split(night_dir)
    night_time = "_".join(night_name.split('_')[1:4])[:-3]

    # Construct the CAL file name
    file_name = "CAL_{:06d}_{:s}.txt".format(config.cams_code, night_time)


    # If there was no platepar, init an empty one
    if platepar is None:
        platepar = Platepar()

    # Make a copy of the platepar that can be modified
    platepar = copy.deepcopy(platepar)


    # Compute rotations (must be done before distorsion correction)
    rot_horiz = rotationWrtHorizon(platepar)
    rot_std = rotationWrtStandard(platepar)


    # Switch ry in Y coeffs
    platepar.y_poly_fwd[11], platepar.y_poly_fwd[10] = platepar.y_poly_fwd[10], platepar.y_poly_fwd[11]


    # Correct distorsion parameters so they are CAMS compatible
    platepar.x_poly_fwd[ 1] = +platepar.x_poly_fwd[ 1] + 1.0
    platepar.x_poly_fwd[ 2] = -platepar.x_poly_fwd[ 2]
    platepar.x_poly_fwd[ 4] = -platepar.x_poly_fwd[ 4]
    platepar.x_poly_fwd[ 7] = -platepar.x_poly_fwd[ 7]
    platepar.x_poly_fwd[ 9] = -platepar.x_poly_fwd[ 9]
    platepar.x_poly_fwd[11] = -platepar.x_poly_fwd[11]
    platepar.y_poly_fwd[ 2] = -platepar.y_poly_fwd[ 2] - 1.0
    platepar.y_poly_fwd[ 4] = -platepar.y_poly_fwd[ 4]
    platepar.y_poly_fwd[ 7] = -platepar.y_poly_fwd[ 7]
    platepar.y_poly_fwd[ 9] = -platepar.y_poly_fwd[ 9]
    platepar.y_poly_fwd[11] = -platepar.y_poly_fwd[11]


    # Compute scale in arcmin/px
    arcminperpixel = 60/platepar.F_scale

    # Correct scaling and rotation
    for k in range(12):
        
        x_prime = platepar.x_poly_fwd[k]*math.radians(arcminperpixel/60.0)
        y_prime = platepar.y_poly_fwd[k]*math.radians(arcminperpixel/60.0)

        platepar.x_poly_fwd[k] = math.cos(math.radians(platepar.pos_angle_ref))*x_prime \
            + math.sin(math.radians(platepar.pos_angle_ref))*y_prime

        platepar.y_poly_fwd[k] = math.sin(math.radians(platepar.pos_angle_ref))*x_prime \
            - math.cos(math.radians(platepar.pos_angle_ref))*y_prime


    # Open the file
    with open(os.path.join(night_dir, file_name), 'w') as f:

        # Construct calibration date and time
        calib_dt = jd2Date(platepar.JD, dt_obj=True)
        calib_date = calib_dt.strftime("%m/%d/%Y")
        calib_time = calib_dt.strftime("%H:%M:%S.%f")[:-3]

        s  =" Camera number            = {:d}\n".format(config.cams_code)
        s +=" Calibration date         = {:s}\n".format(calib_date)
        s +=" Calibration time (UT)    = {:s}\n".format(calib_time)
        s +=" Longitude +west (deg)    = {:9.5f}\n".format(-platepar.lon)
        s +=" Latitude +north (deg)    = {:9.5f}\n".format(platepar.lat)
        s +=" Height above WGS84 (km)  = {:8.5f}\n".format(platepar.elev/1000)
        s +=" FOV dimension hxw (deg)  =   {:.2f} x   {:.2f}\n".format(platepar.fov_v, platepar.fov_h)
        s +=" Plate scale (arcmin/pix) = {:8.3f}\n".format(arcminperpixel)
        s +=" Plate roll wrt Std (deg) = {:8.3f}\n".format(rot_std)
        s +=" Cam tilt wrt Horiz (deg) = {:8.3f}\n".format(rot_horiz)
        s +=" Frame rate (Hz)          = {:8.3f}\n".format(config.fps)
        s +=" Cal center RA (deg)      = {:8.3f}\n".format(platepar.RA_d)
        s +=" Cal center Dec (deg)     = {:8.3f}\n".format(platepar.dec_d)
        s +=" Cal center Azim (deg)    = {:8.3f}\n".format(platepar.az_centre)
        s +=" Cal center Elev (deg)    = {:8.3f}\n".format(platepar.alt_centre)
        s +=" Cal center col (colcen)  = {:8.3f}\n".format(platepar.X_res/2)
        s +=" Cal center row (rowcen)  = {:8.3f}\n".format(platepar.Y_res/2)
        s +=" Cal fit order            = 201\n" # 201 = RMS 3rd order poly with radial terms
        s +="\n"
        s +=" Camera description       = None\n"
        s +=" Lens description         = None\n"
        s +=" Focal length (mm)        =    0.000\n"
        s +=" Focal ratio              =    0.000\n"
        s +=" Pixel pitch H (um)       =    0.000\n"
        s +=" Pixel pitch V (um)       =    0.000\n"
        s +=" Spectral response B      = {:8.3f}\n".format(config.star_catalog_band_ratios[0])
        s +=" Spectral response V      = {:8.3f}\n".format(config.star_catalog_band_ratios[1])
        s +=" Spectral response R      = {:8.3f}\n".format(config.star_catalog_band_ratios[2])
        s +=" Spectral response I      = {:8.3f}\n".format(config.star_catalog_band_ratios[3])
        s +=" Vignetting coef(deg/pix) =    0.000\n"
        s +=" Gamma                    = {:8.3f}\n".format(config.gamma)
        s +="\n"
        s +=" Xstd, Ystd = Radialxy2Standard( col, row, colcen, rowcen, Xcoef, Ycoef )\n"
        s +=" x = col - colcen\n"
        s +=" y = rowcen - row\n"
        s +="\n"
        s +=" Term       Xcoef            Ycoef     \n"
        s +=" ----  ---------------  ---------------\n"
        s +=" 1     {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[0], platepar.y_poly_fwd[0])
        s +=" x     {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[1], platepar.y_poly_fwd[1])
        s +=" y     {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[2], platepar.y_poly_fwd[2])
        s +=" xx    {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[3], platepar.y_poly_fwd[3])
        s +=" xy    {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[4], platepar.y_poly_fwd[4])
        s +=" yy    {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[5], platepar.y_poly_fwd[5])
        s +=" xxx   {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[6], platepar.y_poly_fwd[6])
        s +=" xxy   {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[7], platepar.y_poly_fwd[7])
        s +=" xyy   {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[8], platepar.y_poly_fwd[8])
        s +=" yyy   {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[9], platepar.y_poly_fwd[9])
        s +=" rx    {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[10], platepar.y_poly_fwd[10])
        s +=" ry    {:+.7e}    {:+.7e} \n".format(platepar.x_poly_fwd[11], platepar.y_poly_fwd[11])
        s +=" ----  ---------------  ---------------\n"
        s +="\n"
        s +=" Mean O-C =   0.000 +-   0.000 arcmin\n"
        s +="\n"
        s +=" Magnitude = A + B (logI-logVig)   fit mV vs. -2.5 (logI-logVig),   B-V <  1.20, mV <  6.60\n"
        s +="         A = {:8.3f} \n".format(platepar.mag_lev)
        s +="         B =   -2.50 \n"
        s +="\n"
        s +=" Magnitude = -2.5 ( C + D (logI-logVig) )   fit logFlux vs. Gamma (logI-logVig), mV <  6.60\n"
        s +="         C = {:8.3f} \n".format(platepar.mag_lev/(-2.5))
        s +="         D =    1.00 \n"
        s +="\n"
        s +=" logVig = log( cos( Vignetting_coef * Rpixels * pi/180 )^4 )\n"
        s +="\n"
        s +="\n"
        s +=" Star    RA (deg)  DEC (deg)    row      col       V      B-V      R      IR    logInt  logVig  logFlux  O-C arcmin \n"
        s +=" ----   ---------  ---------  -------  -------  ------  ------  ------  ------  ------  ------  -------  ---------- \n"


        # Write CAL content
        f.write(s)


        return file_name
예제 #7
0
        s +="\n"
        s +="\n"
        s +=" Star    RA (deg)  DEC (deg)    row      col       V      B-V      R      IR    logInt  logVig  logFlux  O-C arcmin \n"
        s +=" ----   ---------  ---------  -------  -------  ------  ------  ------  ------  ------  ------  -------  ---------- \n"


        # Write CAL content
        f.write(s)


        return file_name



if __name__ == "__main__":

    import RMS.ConfigReader as cr

    # Load the default configuration file
    config = cr.parse(".config")

    # Load a platepar file
    pp = Platepar()
    pp.read("/home/dvida/Desktop/HR0010_20190216_170146_265550_detected/platepar_cmn2010.cal")


    night_dir = "/home/dvida/Desktop/HR0010_20190216_170146_265550_detected"
    #night_dir = "D:/Dropbox/RPi_Meteor_Station/data/CA0004_20180516_040459_588816_detected"

    # Write the CAL file
    writeCAL(night_dir, config, pp)
예제 #8
0
def applyAstrometryFTPdetectinfo(dir_path,
                                 ftp_detectinfo_file,
                                 platepar_file,
                                 UT_corr=0):
    """ Use the given platepar to calculate the celestial coordinates of detected meteors from a FTPdetectinfo
        file and save the updates values.

    Arguments:
        dir_path: [str] Path to the night.
        ftp_detectinfo_file: [str] Name of the FTPdetectinfo file.
        platepar_file: [str] Name of the platepar file.

    Keyword arguments:
        UT_corr: [float] Difference of time from UTC in hours.

    Return:
        None
    """

    # Save a copy of the uncalibrated FTPdetectinfo
    ftp_detectinfo_copy = "".join(
        ftp_detectinfo_file.split('.')[:-1]) + "_uncalibrated.txt"

    # Back up the original FTPdetectinfo, only if a backup does not exist already
    if not os.path.isfile(os.path.join(dir_path, ftp_detectinfo_copy)):
        shutil.copy2(os.path.join(dir_path, ftp_detectinfo_file),
                     os.path.join(dir_path, ftp_detectinfo_copy))

    # Load the platepar
    platepar = Platepar()
    platepar.read(os.path.join(dir_path, platepar_file))

    # Load the FTPdetectinfo file
    meteor_data = readFTPdetectinfo(dir_path, ftp_detectinfo_file)

    # List for final meteor data
    meteor_list = []

    # Go through every meteor
    for meteor in meteor_data:

        ff_name, cam_code, meteor_No, n_segments, fps, hnr, mle, binn, px_fm, rho, phi, meteor_meas = meteor

        meteor_meas = np.array(meteor_meas)

        # Extract frame number, x, y, intensity
        frames = meteor_meas[:, 1]
        X_data = meteor_meas[:, 2]
        Y_data = meteor_meas[:, 3]
        level_data = meteor_meas[:, 8]

        # Get the beginning time of the FF file
        time_beg = filenameToDatetime(ff_name)

        # Calculate time data of every point
        time_data = []
        for frame_n in frames:
            t = time_beg + datetime.timedelta(seconds=frame_n / fps)
            time_data.append([
                t.year, t.month, t.day, t.hour, t.minute, t.second,
                int(t.microsecond / 1000)
            ])

        # Convert image cooredinates to RA and Dec, and do the photometry
        JD_data, RA_data, dec_data, magnitudes = XY2CorrectedRADecPP(np.array(time_data), X_data, Y_data, \
            level_data, platepar)

        # Compute azimuth and altitude of centroids
        az_data = np.zeros_like(RA_data)
        alt_data = np.zeros_like(RA_data)

        for i in range(len(az_data)):

            jd = JD_data[i]
            ra_tmp = RA_data[i]
            dec_tmp = dec_data[i]

            az_tmp, alt_tmp = raDec2AltAz(jd, platepar.lon, platepar.lat,
                                          ra_tmp, dec_tmp)

            az_data[i] = az_tmp
            alt_data[i] = alt_tmp

        # print(ff_name, cam_code, meteor_No, fps)
        # print(X_data, Y_data)
        # print(RA_data, dec_data)
        # print('------------------------------------------')

        # Construct the meteor measurements array
        meteor_picks = np.c_[frames, X_data, Y_data, RA_data, dec_data, az_data, alt_data, level_data, \
            magnitudes]

        # Add the calculated values to the final list
        meteor_list.append([ff_name, meteor_No, rho, phi, meteor_picks])

    # Calibration string to be written to the FTPdetectinfo file
    calib_str = 'Calibrated with RMS on: ' + str(
        datetime.datetime.utcnow()) + ' UTC'

    # If no meteors were detected, set dummpy parameters
    if len(meteor_list) == 0:
        cam_code = ''
        fps = 0

    # Save the updated FTPdetectinfo
    writeFTPdetectinfo(meteor_list,
                       dir_path,
                       ftp_detectinfo_file,
                       dir_path,
                       cam_code,
                       fps,
                       calibration=calib_str,
                       celestial_coords_given=True)
예제 #9
0
파일: CAL.py 프로젝트: tammojan/RMS
    ### COMMAND LINE ARGUMENTS

    # Init the command line arguments parser
    arg_parser = argparse.ArgumentParser(description="Covert the RMS platepar to a CAMS-style CAL file.")

    arg_parser.add_argument('platepar_path', metavar='PLATEPAR_PATH', type=str, \
        help='Path to a platepar file.')

    arg_parser.add_argument('-c', '--config', nargs=1, metavar='CONFIG_PATH', type=str, \
        help="Path to a config file which will be used instead of the default one.")

    # Parse the command line arguments
    cml_args = arg_parser.parse_args()

    #########################


    # Extract parent directory
    dir_path = os.path.dirname(cml_args.platepar_path)

    # Load the config file
    config = cr.loadConfigFromDirectory(cml_args.config, dir_path)

    # Load a platepar file
    pp = Platepar()
    pp.read(cml_args.platepar_path, use_flat=config.use_flat)

    # Write the CAL file
    writeCAL(dir_path, config, pp)
예제 #10
0
def generateCalibrationReport(config, night_dir_path, match_radius=2.0, platepar=None, show_graphs=False):
    """ Given the folder of the night, find the Calstars file, check the star fit and generate a report
        with the quality of the calibration. The report contains information about both the astrometry and
        the photometry calibration. Graphs will be saved in the given directory of the night.
    
    Arguments:
        config: [Config instance]
        night_dir_path: [str] Full path to the directory of the night.

    Keyword arguments:
        match_radius: [float] Match radius for star matching between image and catalog stars (px).
        platepar: [Platepar instance] Use this platepar instead of finding one in the folder.
        show_graphs: [bool] Show the graphs on the screen. False by default.

    Return:
        None
    """

    # Find the CALSTARS file in the given folder
    calstars_file = None
    for calstars_file in os.listdir(night_dir_path):
        if ('CALSTARS' in calstars_file) and ('.txt' in calstars_file):
            break

    if calstars_file is None:
        print('CALSTARS file could not be found in the given directory!')
        return None


    # Load the calstars file
    star_list = readCALSTARS(night_dir_path, calstars_file)



    ### Load recalibrated platepars, if they exist ###

    # Find recalibrated platepars file per FF file
    platepars_recalibrated_file = None
    for file_name in os.listdir(night_dir_path):
        if file_name == config.platepars_recalibrated_name:
            platepars_recalibrated_file = file_name
            break


    # Load all recalibrated platepars if the file is available
    recalibrated_platepars = None
    if platepars_recalibrated_file:
        with open(os.path.join(night_dir_path, platepars_recalibrated_file)) as f:
            recalibrated_platepars = json.load(f)
            print('Loaded recalibrated platepars JSON file for the calibration report...')

    ### ###


    ### Load the platepar file ###

    # Find the platepar file in the given directory if it was not given
    if platepar is None:

        # Find the platepar file
        platepar_file = None
        for file_name in os.listdir(night_dir_path):
            if file_name == config.platepar_name:
                platepar_file = file_name
                break

        if platepar_file is None:
            print('The platepar cannot be found in the night directory!')
            return None

        # Load the platepar file
        platepar = Platepar()
        platepar.read(os.path.join(night_dir_path, platepar_file))


    ### ###


    night_name = os.path.split(night_dir_path.strip(os.sep))[1]


    # Go one mag deeper than in the config
    lim_mag = config.catalog_mag_limit + 1

    # Load catalog stars (load one magnitude deeper)
    catalog_stars, mag_band_str, config.star_catalog_band_ratios = StarCatalog.readStarCatalog(\
        config.star_catalog_path, config.star_catalog_file, lim_mag=lim_mag, \
        mag_band_ratios=config.star_catalog_band_ratios)

    
    ### Take only those CALSTARS entires for which FF files exist in the folder ###

    # Get a list of FF files in the folder\
    ff_list = []
    for file_name in os.listdir(night_dir_path):
        if validFFName(file_name):
            ff_list.append(file_name)


    # Filter out calstars entries, generate a star dictionary where the keys are JDs of FFs
    star_dict = {}
    ff_dict = {}
    for entry in star_list:

        ff_name, star_data = entry

        # Check if the FF from CALSTARS exists in the folder
        if ff_name not in ff_list:
            continue


        dt = getMiddleTimeFF(ff_name, config.fps, ret_milliseconds=True)
        jd = date2JD(*dt)

        # Add the time and the stars to the dict
        star_dict[jd] = star_data
        ff_dict[jd] = ff_name

    ### ###

    # If there are no FF files in the directory, don't generate a report
    if len(star_dict) == 0:
        print('No FF files from the CALSTARS file in the directory!')
        return None


    # If the recalibrated platepars file exists, take the one with the most stars
    max_jd = 0
    if recalibrated_platepars is not None:
        max_stars = 0
        for ff_name_temp in recalibrated_platepars:

            # Compute the Julian date of the FF middle
            dt = getMiddleTimeFF(ff_name_temp, config.fps, ret_milliseconds=True)
            jd = date2JD(*dt)

            # Check that this file exists in CALSTARS and the list of FF files
            if (jd not in star_dict) or (jd not in ff_dict):
                continue

            # Check if the number of stars on this FF file is larger than the before
            if len(star_dict[jd]) > max_stars:
                max_jd = jd
                max_stars = len(star_dict[jd])


        # Set a flag to indicate if using recalibrated platepars has failed
        if max_jd == 0:
            using_recalib_platepars = False
        else:

            print('Using recalibrated platepars, file:', ff_dict[max_jd])
            using_recalib_platepars = True

            # Select the platepar where the FF file has the most stars
            platepar_dict = recalibrated_platepars[ff_dict[max_jd]]
            platepar = Platepar()
            platepar.loadFromDict(platepar_dict)

            filtered_star_dict = {max_jd: star_dict[max_jd]}

            # Match stars on the image with the stars in the catalog
            n_matched, avg_dist, cost, matched_stars = matchStarsResiduals(config, platepar, catalog_stars, \
                filtered_star_dict, match_radius, ret_nmatch=True, lim_mag=lim_mag)

            max_matched_stars = n_matched


    # Otherwise take the optimal FF file for evaluation
    if (recalibrated_platepars is None) or (not using_recalib_platepars):

        # If there are more than a set number of FF files to evaluate, choose only the ones with most stars on
        #   the image
        if len(star_dict) > config.calstars_files_N:

            # Find JDs of FF files with most stars on them
            top_nstars_indices = np.argsort([len(x) for x in star_dict.values()])[::-1][:config.calstars_files_N \
                - 1]

            filtered_star_dict = {}
            for i in top_nstars_indices:
                filtered_star_dict[list(star_dict.keys())[i]] = list(star_dict.values())[i]

            star_dict = filtered_star_dict


        # Match stars on the image with the stars in the catalog
        n_matched, avg_dist, cost, matched_stars = matchStarsResiduals(config, platepar, catalog_stars, \
            star_dict, match_radius, ret_nmatch=True, lim_mag=lim_mag)



    # If no recalibrated platepars where found, find the image with the largest number of matched stars
    if (not using_recalib_platepars) or (max_jd == 0):

        max_jd = 0
        max_matched_stars = 0
        for jd in matched_stars:
            _, _, distances = matched_stars[jd]
            if len(distances) > max_matched_stars:
                max_jd = jd
                max_matched_stars = len(distances)

        
        # If there are no matched stars, use the image with the largest number of detected stars
        if max_matched_stars <= 2:
            max_jd = max(star_dict, key=lambda x: len(star_dict[x]))
            distances = [np.inf]



    # Take the FF file with the largest number of matched stars
    ff_name = ff_dict[max_jd]

    # Load the FF file
    ff = readFF(night_dir_path, ff_name)
    img_h, img_w = ff.avepixel.shape

    dpi = 200
    plt.figure(figsize=(ff.avepixel.shape[1]/dpi, ff.avepixel.shape[0]/dpi), dpi=dpi)

    # Take the average pixel
    img = ff.avepixel

    # Slightly adjust the levels
    img = Image.adjustLevels(img, np.percentile(img, 1.0), 1.2, np.percentile(img, 99.99))

    plt.imshow(img, cmap='gray', interpolation='nearest')

    legend_handles = []


    # Plot detected stars
    for img_star in star_dict[max_jd]:

        y, x, _, _ = img_star

        rect_side = 5*match_radius
        square_patch = plt.Rectangle((x - rect_side/2, y - rect_side/2), rect_side, rect_side, color='g', \
            fill=False, label='Image stars')

        plt.gca().add_artist(square_patch)

    legend_handles.append(square_patch)



    # If there are matched stars, plot them
    if max_matched_stars > 2:

        # Take the solution with the largest number of matched stars
        image_stars, matched_catalog_stars, distances = matched_stars[max_jd]

        # Plot matched stars
        for img_star in image_stars:
            x, y, _, _ = img_star

            circle_patch = plt.Circle((y, x), radius=3*match_radius, color='y', fill=False, \
                label='Matched stars')

            plt.gca().add_artist(circle_patch)

        legend_handles.append(circle_patch)


        ### Plot match residuals ###

        # Compute preducted positions of matched image stars from the catalog
        x_predicted, y_predicted = raDecToXYPP(matched_catalog_stars[:, 0], \
            matched_catalog_stars[:, 1], max_jd, platepar)

        img_y, img_x, _, _ = image_stars.T

        delta_x = x_predicted - img_x
        delta_y = y_predicted - img_y

        # Compute image residual and angle of the error
        res_angle = np.arctan2(delta_y, delta_x)
        res_distance = np.sqrt(delta_x**2 + delta_y**2)


        # Calculate coordinates of the beginning of the residual line
        res_x_beg = img_x + 3*match_radius*np.cos(res_angle)
        res_y_beg = img_y + 3*match_radius*np.sin(res_angle)

        # Calculate coordinates of the end of the residual line
        res_x_end = img_x + 100*np.cos(res_angle)*res_distance
        res_y_end = img_y + 100*np.sin(res_angle)*res_distance

        # Plot the 100x residuals
        for i in range(len(x_predicted)):
            res_plot = plt.plot([res_x_beg[i], res_x_end[i]], [res_y_beg[i], res_y_end[i]], color='orange', \
                lw=0.5, label='100x residuals')

        legend_handles.append(res_plot[0])

        ### ###

    else:

        distances = [np.inf]
        
        # If there are no matched stars, plot large text in the middle of the screen
        plt.text(img_w/2, img_h/2, "NO MATCHED STARS!", color='r', alpha=0.5, fontsize=20, ha='center',
            va='center')


    ### Plot positions of catalog stars to the limiting magnitude of the faintest matched star + 1 mag ###

    # Find the faintest magnitude among matched stars
    if max_matched_stars > 2:
        faintest_mag = np.max(matched_catalog_stars[:, 2]) + 1

    else:
        # If there are no matched stars, use the limiting magnitude from config
        faintest_mag = config.catalog_mag_limit + 1


    # Estimate RA,dec of the centre of the FOV
    _, RA_c, dec_c, _ = xyToRaDecPP([jd2Date(max_jd)], [platepar.X_res/2], [platepar.Y_res/2], [1], 
        platepar)

    RA_c = RA_c[0]
    dec_c = dec_c[0]

    fov_radius = np.hypot(*computeFOVSize(platepar))

    # Get stars from the catalog around the defined center in a given radius
    _, extracted_catalog = subsetCatalog(catalog_stars, RA_c, dec_c, fov_radius, faintest_mag)
    ra_catalog, dec_catalog, mag_catalog = extracted_catalog.T

    # Compute image positions of all catalog stars that should be on the image
    x_catalog, y_catalog = raDecToXYPP(ra_catalog, dec_catalog, max_jd, platepar)

    # Filter all catalog stars outside the image
    temp_arr = np.c_[x_catalog, y_catalog, mag_catalog]
    temp_arr = temp_arr[temp_arr[:, 0] >= 0]
    temp_arr = temp_arr[temp_arr[:, 0] <= ff.avepixel.shape[1]]
    temp_arr = temp_arr[temp_arr[:, 1] >= 0]
    temp_arr = temp_arr[temp_arr[:, 1] <= ff.avepixel.shape[0]]
    x_catalog, y_catalog, mag_catalog = temp_arr.T

    # Plot catalog stars on the image
    cat_stars_handle = plt.scatter(x_catalog, y_catalog, c='none', marker='D', lw=1.0, alpha=0.4, \
        s=((4.0 + (faintest_mag - mag_catalog))/3.0)**(2*2.512), edgecolor='r', label='Catalog stars')

    legend_handles.append(cat_stars_handle)

    ### ###


    # Add info text
    info_text = ff_dict[max_jd] + '\n' \
        + "Matched stars: {:d}/{:d}\n".format(max_matched_stars, len(star_dict[max_jd])) \
        + "Median distance: {:.2f} px\n".format(np.median(distances)) \
        + "Catalog limiting magnitude: {:.1f}".format(lim_mag)

    plt.text(10, 10, info_text, bbox=dict(facecolor='black', alpha=0.5), va='top', ha='left', fontsize=4, \
        color='w')

    legend = plt.legend(handles=legend_handles, prop={'size': 4}, loc='upper right')
    legend.get_frame().set_facecolor('k')
    legend.get_frame().set_edgecolor('k')
    for txt in legend.get_texts():
        txt.set_color('w')


    plt.axis('off')
    plt.gca().get_xaxis().set_visible(False)
    plt.gca().get_yaxis().set_visible(False)

    plt.xlim([0, ff.avepixel.shape[1]])
    plt.ylim([ff.avepixel.shape[0], 0])

    # Remove the margins
    plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)

    plt.savefig(os.path.join(night_dir_path, night_name + '_calib_report_astrometry.jpg'), \
        bbox_inches='tight', pad_inches=0, dpi=dpi)


    if show_graphs:
        plt.show()

    else:
        plt.clf()
        plt.close()



    if max_matched_stars > 2:

        ### Plot the photometry ###

        plt.figure(dpi=dpi)

        # Take only those stars which are inside the 3/4 of the shorter image axis from the center
        photom_selection_radius = np.min([img_h, img_w])/3
        filter_indices = ((image_stars[:, 0] - img_h/2)**2 + (image_stars[:, 1] \
            - img_w/2)**2) <= photom_selection_radius**2
        star_intensities = image_stars[filter_indices, 2]
        catalog_mags = matched_catalog_stars[filter_indices, 2]

        # Plot intensities of image stars
        #star_intensities = image_stars[:, 2]
        plt.scatter(-2.5*np.log10(star_intensities), catalog_mags, s=5, c='r')

        # Fit the photometry on automated star intensities
        photom_offset, fit_stddev, _ = photometryFit(np.log10(star_intensities), catalog_mags)


        # Plot photometric offset from the platepar
        x_min, x_max = plt.gca().get_xlim()
        y_min, y_max = plt.gca().get_ylim()

        x_min_w = x_min - 3
        x_max_w = x_max + 3
        y_min_w = y_min - 3
        y_max_w = y_max + 3

        photometry_info = 'Platepar: {:+.2f}LSP {:+.2f} +/- {:.2f} \nGamma = {:.2f}'.format(platepar.mag_0, \
            platepar.mag_lev, platepar.mag_lev_stddev, platepar.gamma)

        # Plot the photometry calibration from the platepar
        logsum_arr = np.linspace(x_min_w, x_max_w, 10)
        plt.plot(logsum_arr, logsum_arr + platepar.mag_lev, label=photometry_info, linestyle='--', \
            color='k', alpha=0.5)

        # Plot the fitted photometry calibration
        fit_info = "Fit: {:+.2f}LSP {:+.2f} +/- {:.2f}".format(-2.5, photom_offset, fit_stddev)
        plt.plot(logsum_arr, logsum_arr + photom_offset, label=fit_info, linestyle='--', color='red', 
            alpha=0.5)

        plt.legend()

        plt.ylabel("Catalog magnitude ({:s})".format(mag_band_str))
        plt.xlabel("Uncalibrated magnitude")

        # Set wider axis limits
        plt.xlim(x_min_w, x_max_w)
        plt.ylim(y_min_w, y_max_w)

        plt.gca().invert_yaxis()
        plt.gca().invert_xaxis()

        plt.grid()

        plt.savefig(os.path.join(night_dir_path, night_name + '_calib_report_photometry.png'), dpi=150)


        if show_graphs:
            plt.show()

        else:
            plt.clf()
            plt.close()
예제 #11
0
            if file_name == config.platepar_name:
                platepar_path = os.path.join(dir_path, file_name)

            # Locate mask
            if file_name == config.mask_file:
                mask_path = os.path.join(dir_path, file_name)

        if platepar_path is None:
            print("No platepar find was found in {:s}!".format(dir_path))
            sys.exit()

        else:
            print("Found platepar!")

    # Load the platepar file
    pp = Platepar()
    pp.read(platepar_path)

    # Assign mask
    mask_path = None
    if cml_args.mask is not None:
        mask_path = cml_args.mask

    # Load the mask file
    if mask_path is not None:
        mask = loadMask(mask_path)
        print("Loading mask:", mask_path)
    else:
        mask = None

    # Generate a KML file from the platepar
예제 #12
0
def runCapture(config,
               duration=None,
               video_file=None,
               nodetect=False,
               detect_end=False,
               upload_manager=None):
    """ Run capture and compression for the given time.given

    Arguments:
        config: [config object] Configuration read from the .config file

    Keyword arguments:
        duration: [float] Time in seconds to capture. None by default.
        video_file: [str] Path to the video file, if it was given as the video source. None by default.
        nodetect: [bool] If True, detection will not be performed. False by defualt.
        detect_end: [bool] If True, detection will be performed at the end of the night, when capture 
            finishes. False by default.
        upload_manager: [UploadManager object] A handle to the UploadManager, which handles uploading files to
            the central server. None by default.

    """

    global STOP_CAPTURE

    # Create a directory for captured files
    night_data_dir_name = str(
        config.stationID) + '_' + datetime.datetime.utcnow().strftime(
            '%Y%m%d_%H%M%S_%f')

    # Full path to the data directory
    night_data_dir = os.path.join(os.path.abspath(config.data_dir),
                                  config.captured_dir, night_data_dir_name)

    # Make a directory for the night
    mkdirP(night_data_dir)

    log.info('Data directory: ' + night_data_dir)

    # Load the default flat field image if it is available
    flat_struct = None

    if config.use_flat:

        # Check if the flat exists
        if os.path.exists(os.path.join(os.getcwd(), config.flat_file)):
            flat_struct = Image.loadFlat(os.getcwd(), config.flat_file)

            log.info('Loaded flat field image: ' +
                     os.path.join(os.getcwd(), config.flat_file))

    # Load the default platepar if it is available
    platepar = None
    platepar_path = os.path.join(os.getcwd(), config.platepar_name)
    if os.path.exists(platepar_path):
        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path)

        log.info('Loaded platepar: ' + platepar_path)

    else:

        log.info('No platepar file found!')

    log.info('Initializing frame buffers...')
    ### For some reason, the RPi 3 does not like memory chunks which size is the multipier of its L2
    ### cache size (512 kB). When such a memory chunk is provided, the compression becomes 10x slower
    ### then usual. We are applying a dirty fix here where we just add an extra image row and column
    ### if such a memory chunk will be created. The compression is performed, and the image is cropped
    ### back to its original dimensions.
    array_pad = 0

    # Check if the image dimensions are divisible by RPi3 L2 cache size and add padding
    if (256 * config.width * config.height) % (512 * 1024) == 0:
        array_pad = 1

    # Init arrays for parallel compression on 2 cores
    sharedArrayBase = multiprocessing.Array(
        ctypes.c_uint8,
        256 * (config.width + array_pad) * (config.height + array_pad))
    sharedArray = np.ctypeslib.as_array(sharedArrayBase.get_obj())
    sharedArray = sharedArray.reshape(256, (config.height + array_pad),
                                      (config.width + array_pad))
    startTime = multiprocessing.Value('d', 0.0)

    sharedArrayBase2 = multiprocessing.Array(
        ctypes.c_uint8,
        256 * (config.width + array_pad) * (config.height + array_pad))
    sharedArray2 = np.ctypeslib.as_array(sharedArrayBase2.get_obj())
    sharedArray2 = sharedArray2.reshape(256, (config.height + array_pad),
                                        (config.width + array_pad))
    startTime2 = multiprocessing.Value('d', 0.0)

    log.info('Initializing frame buffers done!')

    # Check if the detection should be performed or not
    if nodetect:
        detector = None

    else:

        if detect_end:

            # Delay detection until the end of the night
            delay_detection = duration

        else:
            # Delay the detection for 2 minutes after capture start
            delay_detection = 120

        # Initialize the detector
        detector = QueuedPool(detectStarsAndMeteors,
                              cores=1,
                              log=log,
                              delay_start=delay_detection)
        detector.startPool()

    # Initialize buffered capture
    bc = BufferedCapture(sharedArray,
                         startTime,
                         sharedArray2,
                         startTime2,
                         config,
                         video_file=video_file)

    # Initialize the live image viewer
    live_view = LiveViewer(window_name='Maxpixel')

    # Initialize compression
    compressor = Compressor(night_data_dir,
                            sharedArray,
                            startTime,
                            sharedArray2,
                            startTime2,
                            config,
                            detector=detector,
                            live_view=live_view,
                            flat_struct=flat_struct)

    # Start buffered capture
    bc.startCapture()

    # Start the compression
    compressor.start()

    # Capture until Ctrl+C is pressed
    wait(duration)

    # If capture was manually stopped, end capture
    if STOP_CAPTURE:
        log.info('Ending capture...')

    # Stop the capture
    log.debug('Stopping capture...')
    bc.stopCapture()
    log.debug('Capture stopped')

    dropped_frames = bc.dropped_frames
    log.info('Total number of dropped frames: ' + str(dropped_frames))

    # Stop the compressor
    log.debug('Stopping compression...')
    detector, live_view = compressor.stop()
    log.debug('Compression stopped')

    # Stop the live viewer
    log.debug('Stopping live viewer...')
    live_view.stop()
    del live_view
    log.debug('Live view stopped')

    # Init data lists
    star_list = []
    meteor_list = []
    ff_detected = []

    # If detection should be performed
    if not nodetect:

        log.info('Finishing up the detection, ' +
                 str(detector.input_queue.qsize()) + ' files to process...')

        # Reset the Ctrl+C to KeyboardInterrupt
        resetSIGINT()

        try:

            # If there are some more files to process, process them on more cores
            if detector.input_queue.qsize() > 0:

                # Let the detector use all cores, but leave 1 free
                available_cores = multiprocessing.cpu_count() - 1

                if available_cores > 1:

                    log.info('Running the detection on {:d} cores...'.format(
                        available_cores))

                    # Start the detector
                    detector.updateCoreNumber(cores=available_cores)

            log.info('Waiting for the detection to finish...')

            # Wait for the detector to finish and close it
            detector.closePool()

            log.info('Detection finished!')

        except KeyboardInterrupt:

            log.info('Ctrl + C pressed, exiting...')

            if upload_manager is not None:

                # Stop the upload manager
                if upload_manager.is_alive():
                    log.debug('Closing upload manager...')
                    upload_manager.stop()
                    del upload_manager

            # Terminate the detector
            if detector is not None:
                del detector

            sys.exit()

        # Set the Ctrl+C back to 'soft' program kill
        setSIGINT()

        ### SAVE DETECTIONS TO FILE

        log.info('Collecting results...')

        # Get the detection results from the queue
        detection_results = detector.getResults()

        # Remove all 'None' results, which were errors
        detection_results = [
            res for res in detection_results if res is not None
        ]

        # Count the number of detected meteors
        meteors_num = 0
        for _, _, meteor_data in detection_results:
            for meteor in meteor_data:
                meteors_num += 1

        log.info('TOTAL: ' + str(meteors_num) + ' detected meteors.')

        # Save the detections to a file
        for ff_name, star_data, meteor_data in detection_results:

            x2, y2, background, intensity = star_data

            # Skip if no stars were found
            if not x2:
                continue

            # Construct the table of the star parameters
            star_data = zip(x2, y2, background, intensity)

            # Add star info to the star list
            star_list.append([ff_name, star_data])

            # Handle the detected meteors
            meteor_No = 1
            for meteor in meteor_data:

                rho, theta, centroids = meteor

                # Append to the results list
                meteor_list.append([ff_name, meteor_No, rho, theta, centroids])
                meteor_No += 1

            # Add the FF file to the archive list if a meteor was detected on it
            if meteor_data:
                ff_detected.append(ff_name)

        # Generate the name for the CALSTARS file
        calstars_name = 'CALSTARS_' + "{:s}".format(str(config.stationID)) + '_' \
            + os.path.basename(night_data_dir) + '.txt'

        # Write detected stars to the CALSTARS file
        CALSTARS.writeCALSTARS(star_list, night_data_dir, calstars_name, config.stationID, config.height, \
            config.width)

        # Generate FTPdetectinfo file name
        ftpdetectinfo_name = 'FTPdetectinfo_' + os.path.basename(
            night_data_dir) + '.txt'

        # Write FTPdetectinfo file
        FTPdetectinfo.writeFTPdetectinfo(meteor_list, night_data_dir, ftpdetectinfo_name, night_data_dir, \
            config.stationID, config.fps)

        # Run calibration check and auto astrometry refinement
        if platepar is not None:

            # Read in the CALSTARS file
            calstars_list = CALSTARS.readCALSTARS(night_data_dir,
                                                  calstars_name)

            # Run astrometry check and refinement
            platepar, fit_status = autoCheckFit(config, platepar,
                                                calstars_list)

            # If the fit was sucessful, apply the astrometry to detected meteors
            if fit_status:

                log.info('Astrometric calibration SUCCESSFUL!')

                # Save the refined platepar to the night directory and as default
                platepar.write(os.path.join(night_data_dir,
                                            config.platepar_name),
                               fmt=platepar_fmt)
                platepar.write(platepar_path, fmt=platepar_fmt)

            else:
                log.info(
                    'Astrometric calibration FAILED!, Using old platepar for calibration...'
                )

            # Calculate astrometry for meteor detections
            applyAstrometryFTPdetectinfo(night_data_dir, ftpdetectinfo_name,
                                         platepar_path)

    log.info('Plotting field sums...')

    # Plot field sums to a graph
    plotFieldsums(night_data_dir, config)

    # Archive all fieldsums to one archive
    archiveFieldsums(night_data_dir)

    # List for any extra files which will be copied to the night archive directory. Full paths have to be
    #   given
    extra_files = []

    log.info('Making a flat...')

    # Make a new flat field
    flat_img = makeFlat(night_data_dir, config)

    # If making flat was sucessfull, save it
    if flat_img is not None:

        # Save the flat in the root directory, to keep the operational flat updated
        scipy.misc.imsave(config.flat_file, flat_img)
        flat_path = os.path.join(os.getcwd(), config.flat_file)
        log.info('Flat saved to: ' + flat_path)

        # Copy the flat to the night's directory as well
        extra_files.append(flat_path)

    else:
        log.info('Making flat image FAILED!')

    # Add the platepar to the archive if it exists
    if os.path.exists(platepar_path):
        extra_files.append(platepar_path)

    night_archive_dir = os.path.join(os.path.abspath(config.data_dir),
                                     config.archived_dir, night_data_dir_name)

    log.info('Archiving detections to ' + night_archive_dir)

    # Archive the detections
    archive_name = archiveDetections(night_data_dir, night_archive_dir, ff_detected, config, \
        extra_files=extra_files)

    # Put the archive up for upload
    if upload_manager is not None:
        log.info('Adding file on upload list: ' + archive_name)
        upload_manager.addFiles([archive_name])

    # If capture was manually stopped, end program
    if STOP_CAPTURE:

        log.info('Ending program')

        # Stop the upload manager
        if upload_manager is not None:
            if upload_manager.is_alive():
                upload_manager.stop()
                log.info('Closing upload manager...')

        sys.exit()
예제 #13
0
        s += "\n"
        s += " logVig = log( cos( Vignetting_coef * Rpixels * pi/180 )^4 )\n"
        s += "\n"
        s += "\n"
        s += " Star    RA (deg)  DEC (deg)    row      col       V      B-V      R      IR    logInt  logVig  logFlux  O-C arcmin \n"
        s += " ----   ---------  ---------  -------  -------  ------  ------  ------  ------  ------  ------  -------  ---------- \n"

        # Write CAL content
        f.write(s)

        return file_name


if __name__ == "__main__":

    import RMS.ConfigReader as cr
    from RMS.Formats.Platepar import Platepar

    # Load the default configuration file
    config = cr.parse(".config")

    # Load a platepar file
    pp = Platepar()
    pp.read("/home/dvida/Desktop/HR0010_20190216_170146_265550_detected/platepar_cmn2010.cal", \
        use_flat=config.use_flat)

    night_dir = "/home/dvida/Desktop/HR0010_20190216_170146_265550_detected"
    #night_dir = "D:/Dropbox/RPi_Meteor_Station/data/CA0004_20180516_040459_588816_detected"

    # Write the CAL file
    writeCAL(night_dir, config, pp)
예제 #14
0
def getPlatepar(config, night_data_dir):
    """ Downloads a new platepar from the server of uses an existing one. 
    
    Arguments:
        Config: [Config instance]
        night_data_dir: [str] Full path to the data directory.

    Return:
        platepar, platepar_path, platepar_fmt
    """


    # Download a new platepar from the server, if present
    downloadNewPlatepar(config)


    # Construct path to the platepar in the night directory
    platepar_night_dir_path = os.path.join(night_data_dir, config.platepar_name)

    # Load the default platepar from the RMS if it is available
    platepar = None
    platepar_fmt = None
    platepar_path = os.path.join(os.getcwd(), config.platepar_name)
    if os.path.exists(platepar_path):
        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path)

        log.info('Loaded platepar from RMS directory: ' + platepar_path)


    # Otherwise, try to find the platepar in the data directory
    elif os.path.exists(platepar_night_dir_path):

        platepar_path = platepar_night_dir_path

        platepar = Platepar()
        platepar_fmt = platepar.read(platepar_path)

        log.info('Loaded platepar from night directory: ' + platepar_path)

    else:

        log.info('No platepar file found!')


    if platepar is not None:
        
        # Make sure that the station code from the config and the platepar match
        if platepar.station_code is not None:
            if config.stationID != platepar.station_code:

                # If they don't match, don't use this platepar
                log.info("The station code in the platepar doesn't match the station code in config file! Not using the platepar...")

                platepar = None
                platepar_fmt = None


    # Make sure the image resolution matches
    if platepar is not None:
        if (int(config.width) != int(platepar.X_res)) or (int(config.height) != int(platepar.Y_res)):

            # If they don't match, don't use this platepar
            log.info("The image resolution in config and platepar don't match! Not using the platepar...")

            platepar = None
            platepar_fmt = None

        

    return platepar, platepar_path, platepar_fmt
예제 #15
0
파일: TrackStack.py 프로젝트: tammojan/RMS
def trackStack(dir_path,
               config,
               border=5,
               background_compensation=True,
               hide_plot=False):
    """ Generate a stack with aligned stars, so the sky appears static. The folder should have a
        platepars_all_recalibrated.json file.

    Arguments:
        dir_path: [str] Path to the directory with image files.
        config: [Config instance]

    Keyword arguments:
        border: [int] Border around the image to exclude (px).
        background_compensation: [bool] Normalize the background by applying a median filter to avepixel and
            use it as a flat field. Slows down the procedure and may sometimes introduce artifacts. True
            by default.
    """

    # Load recalibrated platepars, if they exist ###

    # Find recalibrated platepars file per FF file
    platepars_recalibrated_file = None
    for file_name in os.listdir(dir_path):
        if file_name == config.platepars_recalibrated_name:
            platepars_recalibrated_file = file_name
            break

    # Load all recalibrated platepars if the file is available
    recalibrated_platepars = None
    if platepars_recalibrated_file is not None:
        with open(os.path.join(dir_path, platepars_recalibrated_file)) as f:
            recalibrated_platepars = json.load(f)
            print(
                'Loaded recalibrated platepars JSON file for the calibration report...'
            )

    # ###

    # If the recalib platepars is not found, stop
    if recalibrated_platepars is None:
        print("The {:s} file was not found!".format(
            config.platepars_recalibrated_name))
        return False

    # Get a list of FF files in the folder
    ff_list = []
    for file_name in os.listdir(dir_path):
        if validFFName(file_name):
            ff_list.append(file_name)

    # Take the platepar with the middle time as the reference one
    ff_found_list = []
    jd_list = []
    for ff_name_temp in recalibrated_platepars:

        if ff_name_temp in ff_list:

            # Compute the Julian date of the FF middle
            dt = getMiddleTimeFF(ff_name_temp,
                                 config.fps,
                                 ret_milliseconds=True)
            jd = date2JD(*dt)

            jd_list.append(jd)
            ff_found_list.append(ff_name_temp)

    if len(jd_list) < 2:
        print("Not more than 1 FF image!")
        return False

    # Take the FF file with the middle JD
    jd_list = np.array(jd_list)
    jd_middle = np.mean(jd_list)
    jd_mean_index = np.argmin(np.abs(jd_list - jd_middle))
    ff_mid = ff_found_list[jd_mean_index]

    # Load the middle platepar as the reference one
    pp_ref = Platepar()
    pp_ref.loadFromDict(recalibrated_platepars[ff_mid],
                        use_flat=config.use_flat)

    # Try loading the mask
    mask_path = None
    if os.path.exists(os.path.join(dir_path, config.mask_file)):
        mask_path = os.path.join(dir_path, config.mask_file)

    # Try loading the default mask
    elif os.path.exists(config.mask_file):
        mask_path = os.path.abspath(config.mask_file)

    # Load the mask if given
    mask = None
    if mask_path is not None:
        mask = loadMask(mask_path)
        print("Loaded mask:", mask_path)

    # If the shape of the mask doesn't fit, init an empty mask
    if mask is not None:
        if (mask.img.shape[0] != pp_ref.Y_res) or (mask.img.shape[1] !=
                                                   pp_ref.X_res):
            print("Mask is of wrong shape!")
            mask = None

    if mask is None:
        mask = MaskStructure(255 + np.zeros(
            (pp_ref.Y_res, pp_ref.X_res), dtype=np.uint8))

    # Compute the middle RA/Dec of the reference platepar
    _, ra_temp, dec_temp, _ = xyToRaDecPP([jd2Date(jd_middle)],
                                          [pp_ref.X_res / 2],
                                          [pp_ref.Y_res / 2], [1],
                                          pp_ref,
                                          extinction_correction=False)

    ra_mid, dec_mid = ra_temp[0], dec_temp[0]

    # Go through all FF files and find RA/Dec of image corners to find the size of the stack image ###

    # List of corners
    x_corns = [0, pp_ref.X_res, 0, pp_ref.X_res]
    y_corns = [0, 0, pp_ref.Y_res, pp_ref.Y_res]

    ra_list = []
    dec_list = []

    for ff_temp in ff_found_list:

        # Load the recalibrated platepar
        pp_temp = Platepar()
        pp_temp.loadFromDict(recalibrated_platepars[ff_temp],
                             use_flat=config.use_flat)

        for x_c, y_c in zip(x_corns, y_corns):
            _, ra_temp, dec_temp, _ = xyToRaDecPP(
                [getMiddleTimeFF(ff_temp, config.fps, ret_milliseconds=True)],
                [x_c], [y_c], [1],
                pp_ref,
                extinction_correction=False)
            ra_c, dec_c = ra_temp[0], dec_temp[0]

            ra_list.append(ra_c)
            dec_list.append(dec_c)

    # Compute the angular separation from the middle equatorial coordinates of the reference image to all
    #   RA/Dec corner coordinates
    ang_sep_list = []
    for ra_c, dec_c in zip(ra_list, dec_list):
        ang_sep = np.degrees(
            angularSeparation(np.radians(ra_mid), np.radians(dec_mid),
                              np.radians(ra_c), np.radians(dec_c)))

        ang_sep_list.append(ang_sep)

    # Find the maximum angular separation and compute the image size using the plate scale
    #   The image size will be resampled to 1/2 of the original size to avoid interpolation
    scale = 0.5
    ang_sep_max = np.max(ang_sep_list)
    img_size = int(scale * 2 * ang_sep_max * pp_ref.F_scale)

    #

    # Create the stack platepar with no distortion and a large image size
    pp_stack = copy.deepcopy(pp_ref)
    pp_stack.resetDistortionParameters()
    pp_stack.X_res = img_size
    pp_stack.Y_res = img_size
    pp_stack.F_scale *= scale
    pp_stack.refraction = False

    # Init the image
    avg_stack_sum = np.zeros((img_size, img_size), dtype=float)
    avg_stack_count = np.zeros((img_size, img_size), dtype=int)
    max_deaveraged = np.zeros((img_size, img_size), dtype=np.uint8)

    # Load individual FFs and map them to the stack
    for i, ff_name in enumerate(ff_found_list):

        print("Stacking {:s}, {:.1f}% done".format(
            ff_name, 100 * i / len(ff_found_list)))

        # Read the FF file
        ff = readFF(dir_path, ff_name)

        # Load the recalibrated platepar
        pp_temp = Platepar()
        pp_temp.loadFromDict(recalibrated_platepars[ff_name],
                             use_flat=config.use_flat)

        # Make a list of X and Y image coordinates
        x_coords, y_coords = np.meshgrid(
            np.arange(border, pp_ref.X_res - border),
            np.arange(border, pp_ref.Y_res - border))
        x_coords = x_coords.ravel()
        y_coords = y_coords.ravel()

        # Map image pixels to sky
        jd_arr, ra_coords, dec_coords, _ = xyToRaDecPP(
            len(x_coords) *
            [getMiddleTimeFF(ff_name, config.fps, ret_milliseconds=True)],
            x_coords,
            y_coords,
            len(x_coords) * [1],
            pp_temp,
            extinction_correction=False)

        # Map sky coordinates to stack image coordinates
        stack_x, stack_y = raDecToXYPP(ra_coords, dec_coords, jd_middle,
                                       pp_stack)

        # Round pixel coordinates
        stack_x = np.round(stack_x, decimals=0).astype(int)
        stack_y = np.round(stack_y, decimals=0).astype(int)

        # Cut the image to limits
        filter_arr = (stack_x > 0) & (stack_x < img_size) & (stack_y > 0) & (
            stack_y < img_size)
        x_coords = x_coords[filter_arr].astype(int)
        y_coords = y_coords[filter_arr].astype(int)
        stack_x = stack_x[filter_arr]
        stack_y = stack_y[filter_arr]

        # Apply the mask to maxpixel and avepixel
        maxpixel = copy.deepcopy(ff.maxpixel)
        maxpixel[mask.img == 0] = 0
        avepixel = copy.deepcopy(ff.avepixel)
        avepixel[mask.img == 0] = 0

        # Compute deaveraged maxpixel
        max_deavg = maxpixel - avepixel

        # Normalize the backgroud brightness by applying a large-kernel median filter to avepixel
        if background_compensation:

            # # Apply a median filter to the avepixel to get an estimate of the background brightness
            # avepixel_median = scipy.ndimage.median_filter(ff.avepixel, size=101)
            avepixel_median = cv2.medianBlur(ff.avepixel, 301)

            # Make sure to avoid zero division
            avepixel_median[avepixel_median < 1] = 1

            # Normalize the avepixel by subtracting out the background brightness
            avepixel = avepixel.astype(float)
            avepixel /= avepixel_median
            avepixel *= 50  # Normalize to a good background value, which is usually 50
            avepixel = np.clip(avepixel, 0, 255)
            avepixel = avepixel.astype(np.uint8)

            # plt.imshow(avepixel, cmap='gray', vmin=0, vmax=255)
            # plt.show()

        # Add the average pixel to the sum
        avg_stack_sum[stack_y, stack_x] += avepixel[y_coords, x_coords]

        # Increment the counter image where the avepixel is not zero
        ones_img = np.ones_like(avepixel)
        ones_img[avepixel == 0] = 0
        avg_stack_count[stack_y, stack_x] += ones_img[y_coords, x_coords]

        # Set pixel values to the stack, only take the max values
        max_deaveraged[stack_y, stack_x] = np.max(np.dstack(
            [max_deaveraged[stack_y, stack_x], max_deavg[y_coords, x_coords]]),
                                                  axis=2)

    # Compute the blended avepixel background
    stack_img = avg_stack_sum
    stack_img[avg_stack_count > 0] /= avg_stack_count[avg_stack_count > 0]
    stack_img += max_deaveraged
    stack_img = np.clip(stack_img, 0, 255)
    stack_img = stack_img.astype(np.uint8)

    # Crop image
    non_empty_columns = np.where(stack_img.max(axis=0) > 0)[0]
    non_empty_rows = np.where(stack_img.max(axis=1) > 0)[0]
    crop_box = (np.min(non_empty_rows), np.max(non_empty_rows),
                np.min(non_empty_columns), np.max(non_empty_columns))
    stack_img = stack_img[crop_box[0]:crop_box[1] + 1,
                          crop_box[2]:crop_box[3] + 1]

    # Plot and save the stack ###

    dpi = 200
    plt.figure(figsize=(stack_img.shape[1] / dpi, stack_img.shape[0] / dpi),
               dpi=dpi)

    plt.imshow(stack_img,
               cmap='gray',
               vmin=0,
               vmax=256,
               interpolation='nearest')

    plt.axis('off')
    plt.gca().get_xaxis().set_visible(False)
    plt.gca().get_yaxis().set_visible(False)

    plt.xlim([0, stack_img.shape[1]])
    plt.ylim([stack_img.shape[0], 0])

    # Remove the margins (top and right are set to 0.9999, as setting them to 1.0 makes the image blank in
    #   some matplotlib versions)
    plt.subplots_adjust(left=0,
                        bottom=0,
                        right=0.9999,
                        top=0.9999,
                        wspace=0,
                        hspace=0)

    filenam = os.path.join(dir_path,
                           os.path.basename(dir_path) + "_track_stack.jpg")
    plt.savefig(filenam, bbox_inches='tight', pad_inches=0, dpi=dpi)

    #

    if hide_plot is False:
        plt.show()
예제 #16
0
def add_fffits_metadata(ff_filename, config, platepars_recalibrated,
                        fallback_platepar):
    """
    Add FITS metadata and WCS to FF files generated by RMS

    Args:
        ff_filename (str): full or relative path to FF file
        config (RMS.Config): config instance
        platepars_recalibrated (dict): dictionary with recalibrated platepars
        fallback_platepar (RMS.Platepar): platepar with fitted stars

    Returns:
        None
    """
    ff_basename = os.path.basename(ff_filename)
    platepar_recalibrated = Platepar()
    try:
        platepar_data = platepars_recalibrated[ff_basename]
        with open("platepar_tmp.cal", "w") as f:
            json.dump(platepar_data, f)
        platepar_recalibrated.read("platepar_tmp.cal")
    except (FileNotFoundError, KeyError):
        platepar_recalibrated = fallback_platepar
        logger.warning(f"Using non-recalibrated platepar for {ff_basename}")

    fftime = getMiddleTimeFF(ff_basename, config.fps)

    fit_xy = np.array(fallback_platepar.star_list)[:, 1:3]

    _, fit_ra, fit_dec, _ = xyToRaDecPP([fftime] * len(fit_xy),
                                        fit_xy[:, 0],
                                        fit_xy[:, 1], [1] * len(fit_xy),
                                        platepar_recalibrated,
                                        extinction_correction=False)

    x0 = platepar_recalibrated.X_res / 2
    y0 = platepar_recalibrated.Y_res / 2
    _, ra0, dec0, _ = xyToRaDecPP([fftime], [x0], [y0], [1],
                                  platepar_recalibrated,
                                  extinction_correction=False)
    w = fit_wcs(fit_xy[:, 0],
                fit_xy[:, 1],
                fit_ra,
                fit_dec,
                x0,
                y0,
                ra0[0],
                dec0[0],
                5,
                projection="ZEA")

    hdu_list = fits.open(ff_filename, scale_back=True)
    obstime = Time(filenameToDatetime(ff_basename))

    header_meta = {}
    header_meta["OBSERVER"] = config.stationID.strip()
    header_meta["INSTRUME"] = "Global Meteor Network"
    header_meta["MJD-OBS"] = obstime.mjd
    header_meta["DATE-OBS"] = obstime.fits
    header_meta["NFRAMES"] = 256
    header_meta["EXPTIME"] = 256 / config.fps
    header_meta["SITELONG"] = round(config.longitude, 2)
    header_meta["SITELAT"] = round(config.latitude, 2)

    for hdu in hdu_list:
        if hdu.header[
                "NAXIS"] == 0:  # First header is not an image so should not get WCS
            new_header = Header()
        else:
            new_header = w.to_fits(relax=True)[0].header

        for key, value in header_meta.items():
            new_header.append((key, value))

        for key, value in new_header.items():
            if key in hdu.header:
                continue
            hdu.header[key] = value

    hdu_list.writeto(ff_filename, overwrite=True)