Beispiel #1
0
def stackFFs(dir_path,
             file_format,
             deinterlace=False,
             subavg=False,
             filter_bright=False,
             flat_path=None,
             file_list=None,
             mask=None):
    """ Stack FF files in the given folder. 

    Arguments:
        dir_path: [str] Path to the directory with FF files.
        file_format: [str] Image format for the stack. E.g. jpg, png, bmp

    Keyword arguments:
        deinterlace: [bool] True if the image shoud be deinterlaced prior to stacking. False by default.
        subavg: [bool] Whether the average pixel image should be subtracted form the max pixel image. False
            by default. 
        filter_bright: [bool] Whether images with bright backgrounds (after average subtraction) should be
            skipped. False by defualt.
        flat_path: [str] Path to the flat calibration file. None by default. Will only be used if subavg is
            False.
        file_list: [list] A list of file for stacking. False by default, in which case all FF files in the
            given directory will be used.
        mask: [MaskStructure] Mask to apply to the stack. None by default.

    Return:
        stack_path, merge_img:
            - stack_path: [str] Path of the save stack.
            - merge_img: [ndarray] Numpy array of the stacked image.
    """

    # Load the flat if it was given
    flat = None
    if flat_path != '':

        # Try finding the default flat
        if flat_path is None:
            flat_path = dir_path
            flat_file = 'flat.bmp'

        else:
            flat_path, flat_file = os.path.split(flat_path)

        flat_full_path = os.path.join(flat_path, flat_file)
        if os.path.isfile(flat_full_path):

            # Load the flat
            flat = loadFlat(flat_path, flat_file)

            print('Loaded flat:', flat_full_path)

    first_img = True

    n_stacked = 0
    total_ff_files = 0
    merge_img = None

    # If the list of files was not given, take all files in the given folder
    if file_list is None:
        file_list = sorted(os.listdir(dir_path))

    # List all FF files in the current dir
    for ff_name in file_list:
        if validFFName(ff_name):

            # Load FF file
            ff = readFF(dir_path, ff_name)

            # Skip the file if it is corruped
            if ff is None:
                continue

            total_ff_files += 1

            maxpixel = ff.maxpixel
            avepixel = ff.avepixel

            # Dinterlace the images
            if deinterlace:
                maxpixel = deinterlaceBlend(maxpixel)
                avepixel = deinterlaceBlend(avepixel)

            # If the flat was given, apply it to the image, only if no subtraction is done
            if (flat is not None) and not subavg:
                maxpixel = applyFlat(maxpixel, flat)
                avepixel = applyFlat(avepixel, flat)

            # Reject the image if the median subtracted image is too bright. This usually means that there
            #   are clouds on the image which can ruin the stack
            if filter_bright:

                img = maxpixel - avepixel

                # Compute surface brightness
                median = np.median(img)

                # Compute top detection pixels
                top_brightness = np.percentile(img, 99.9)

                # Reject all images where the median brightness is high
                # Preserve images with very bright detections
                if (median > 10) and (top_brightness <
                                      (2**(8 * img.itemsize) - 10)):
                    print('Skipping: ', ff_name, 'median:', median,
                          'top brightness:', top_brightness)
                    continue

            # Subtract the average from maxpixel
            if subavg:
                img = maxpixel - avepixel

            else:
                img = maxpixel

            if first_img:
                merge_img = np.copy(img)
                first_img = False
                continue

            print('Stacking: ', ff_name)

            # Blend images 'if lighter'
            merge_img = blendLighten(merge_img, img)

            n_stacked += 1

    # If the number of stacked image is less than 20% of the given images, stack without filtering
    if filter_bright and (n_stacked < 0.2 * total_ff_files):
        return stackFFs(dir_path,
                        file_format,
                        deinterlace=deinterlace,
                        subavg=subavg,
                        filter_bright=False,
                        flat_path=flat_path,
                        file_list=file_list)

    # If no images were stacked, do nothing
    if n_stacked == 0:
        return None, None

    # Extract the name of the night directory which contains the FF files
    night_dir = os.path.basename(dir_path)

    stack_path = os.path.join(
        dir_path,
        night_dir + '_stack_{:d}_meteors.'.format(n_stacked) + file_format)

    print("Saving stack to:", stack_path)

    # Stretch the levels
    merge_img = adjustLevels(merge_img, np.percentile(merge_img, 0.5), 1.3,
                             np.percentile(merge_img, 99.9))

    # Apply the mask, if given
    if mask is not None:
        merge_img = MaskImage.applyMask(merge_img, mask)

    # Save the blended image
    saveImage(stack_path, merge_img)

    return stack_path, merge_img
Beispiel #2
0
def archiveDetections(captured_path,
                      archived_path,
                      ff_detected,
                      config,
                      extra_files=None):
    """ Create thumbnails and compress all files with detections and the accompanying files in one archive.

    Arguments:
        captured_path: [str] Path where the captured files are located.
        archived_path: [str] Path where the detected files will be archived to.
        ff_detected: [str] A list of FF files with detections.
        config: [conf object] Configuration.

    Keyword arguments:
        extra_files: [list] A list of extra files (with fill paths) which will be be saved to the night 
            archive.

    Return:
        archive_name: [str] Name of the archive where the files were compressed to.

    """

    # Get the list of files to archive
    file_list = selectFiles(config, captured_path, ff_detected)

    log.info('Generating thumbnails...')

    try:

        # Generate captured thumbnails
        captured_mosaic_file = generateThumbnails(captured_path, config,
                                                  'CAPTURED')

        # Generate detected thumbnails
        detected_mosaic_file = generateThumbnails(captured_path, config, 'DETECTED', \
            file_list=sorted(file_list), no_stack=True)

        # Add the detected mosaic file to the selected list
        file_list.append(captured_mosaic_file)
        file_list.append(detected_mosaic_file)

    except Exception as e:
        log.error('Generating thumbnails failed with error:' + repr(e))
        log.error("".join(traceback.format_exception(*sys.exc_info())))

    log.info('Generating a stack of detections...')

    try:

        # Load the mask for stack
        mask = None
        if os.path.exists(config.mask_file) and config.stack_mask:
            mask_path = os.path.abspath(config.mask_file)
            mask = MaskImage.loadMask(mask_path)

        # Make a co-added image of all detection. Filter out possible clouds
        stack_path, _ = stackFFs(captured_path, 'jpg', deinterlace=(config.deinterlace_order > 0), subavg=True, \
            filter_bright=True, file_list=sorted(file_list), mask=mask)

        if stack_path is not None:

            log.info("Stack saved to: {:s}".format(stack_path))

            # Extract the name of the stack image
            stack_file = os.path.basename(stack_path)

            # Add the stack path to the list of files to put in the archive
            file_list.append(stack_file)

        else:
            log.info("Stack could not be saved!")

    except Exception as e:
        log.error('Generating stack failed with error:' + repr(e))
        log.error("".join(traceback.format_exception(*sys.exc_info())))

    if file_list:

        # Create the archive ZIP in the parent directory of the archive directory
        archive_name = os.path.join(
            os.path.abspath(os.path.join(archived_path, os.pardir)),
            os.path.basename(captured_path) + '_detected')

        # Archive the files
        archive_name = archiveDir(captured_path, file_list, archived_path, archive_name, \
            extra_files=extra_files)

        return archive_name

    return None
Beispiel #3
0
        metavar='MASK_PATH',
        type=str,
        help=
        "Apply a given flat frame. If no path to the flat is given, flat.bmp from the folder will be taken."
    )

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

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

    # Load the mask
    mask = None
    if cml_args.mask is not None:
        if os.path.exists(cml_args.mask):
            mask_path = os.path.abspath(cml_args.mask)
            print('Loading mask:', mask_path)
            mask = MaskImage.loadMask(mask_path)

    # Run stacking
    stack_path, merge_img = stackFFs(cml_args.dir_path[0], cml_args.file_format[0], \
        deinterlace=cml_args.deinterlace, subavg=cml_args.subavg, filter_bright=cml_args.brightfilt, \
        flat_path=cml_args.flat, mask=mask)

    if not cml_args.hideplot:

        # Plot the blended image
        plt.imshow(merge_img, cmap='gray', vmin=0, vmax=255)

        plt.show()
Beispiel #4
0
def loadImageCalibration(dir_path, config, dtype=None, byteswap=False):
    """ Load the mask, dark and flat. 
    
    Arguments:
        dir_path: [str] Path to the directory with calibration.
        config: [ConfigStruct]

    Keyword arguments:
        dtype: [object] Numpy array dtype for the image. None by default, if which case it will be determined
            from the input image.
        byteswap: [bool] If the dark and flat should be byteswapped. False by default, and should be True for
            UWO PNGs.

    Return:
        mask, dark, flat_struct: [tuple of ndarrays]
    """

    mask_path = None
    mask = None

    # Try loading the mask
    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
    if mask_path:
        mask = MaskImage.loadMask(mask_path)

    if mask is not None:
        print('Loaded mask:', mask_path)
        log.info('Loaded mask: {:s}'.format(mask_path))

    # Try loading the dark frame
    dark = None
    if config.use_dark:

        dark_path = None

        # Check if dark is in the data directory
        if os.path.exists(os.path.join(dir_path, config.dark_file)):
            dark_path = os.path.join(dir_path, config.dark_file)

        # Try loading the default dark
        elif os.path.exists(config.dark_file):
            dark_path = os.path.abspath(config.dark_file)

        if dark_path is not None:

            # Load the dark
            dark = Image.loadDark(*os.path.split(dark_path),
                                  dtype=dtype,
                                  byteswap=byteswap)

        if dark is not None:
            print('Loaded dark:', dark_path)
            log.info('Loaded dark: {:s}'.format(dark_path))

    # Try loading a flat field image
    flat_struct = None
    if config.use_flat:

        flat_path = None

        # Check if there is flat in the data directory
        if os.path.exists(os.path.join(dir_path, config.flat_file)):
            flat_path = os.path.join(dir_path, config.flat_file)

        # Try loading the default flat
        elif os.path.exists(config.flat_file):
            flat_path = os.path.abspath(config.flat_file)

        if flat_path is not None:

            # Load the flat
            flat_struct = Image.loadFlat(*os.path.split(flat_path),
                                         dtype=dtype,
                                         byteswap=byteswap)

        if flat_struct is not None:
            print('Loaded flat:', flat_path)
            log.info('Loaded flat: {:s}'.format(flat_path))

    return mask, dark, flat_struct
Beispiel #5
0
def getThresholdedStripe3DPoints(config, img_handle, frame_min, frame_max, rho, theta, mask, flat_struct, \
    dark, stripe_width_factor=1.0, centroiding=False, point1=None, point2=None, debug=False):
    """ Threshold the image and get a list of pixel positions and frames of threshold passers. 
        This function handles all input types of data.

    Arguments;
        config: [config object] configuration object (loaded from the .config file).
        img_handle: [FrameInterface instance] Object which has a common interface to various input files.
        frame_min: [int] First frame to process.
        frame_max: [int] Last frame to process.
        rho: [float] Line distance from the center in HT space (pixels).
        theta: [float] Angle in degrees in HT space.
        mask: [ndarray] Image mask.
        flat_struct: [Flat struct] Structure containing the flat field. None by default.
        dark: [ndarray] Dark frame.

    Keyword arguments:
        stripe_width_factor: [float] Multipler by which the default stripe width will be multiplied. Default
            is 1.0
        centroiding: [bool] If True, the indices will be returned in the centroiding mode, which means
            that point1 and point2 arguments must be given.
        point1: [list] (x, y, frame) Of the first reference point of the detection.
        point2: [list] (x, y, frame) Of the second reference point of the detection.
        debug: [bool] If True, extra debug messages and plots will be shown.
    
    Return:
        xs, ys, zs: [tuple of lists] Indices of (x, y, frame) of threshold passers for every frame.
    """

    # Get indices of stripe pixels around the line of the meteor
    img_h, img_w = img_handle.ff.maxpixel.shape
    stripe_indices = getStripeIndices(
        rho, theta, stripe_width_factor * config.stripe_width, img_h, img_w)

    # If centroiding should be done, prepare everything for cutting out parts of the image for photometry
    if centroiding:

        # Compute the unit vector which describes the motion of the meteor in the image domain
        point1 = np.array(point1)
        point2 = np.array(point2)
        motion_vect = point2[:2] - point1[:2]
        motion_vect_unit = vectNorm(motion_vect)

        # Get coordinates of 2 points that describe the line
        x1, y1, z1 = point1
        x2, y2, z2 = point2

        # Compute the average angular velocity in px per frame
        ang_vel = np.sqrt((x2 - x1)**2 + (y2 - y1)**2) / (z2 - z1)

        # Compute the vector describing the length and direction of the meteor per frame
        motion_vect = ang_vel * motion_vect_unit

    # If the FF files is given, extract the points from FF after threshold
    if img_handle.input_type == 'ff':

        # Threshold the FF file
        img_thres = Image.thresholdFF(img_handle.ff, config.k1_det, config.j1_det, mask=mask, \
            mask_ave_bright=False)

        # Extract the thresholded image by min and max frames from FF file
        img = selectFFFrames(np.copy(img_thres), img_handle.ff, frame_min,
                             frame_max)

        # Remove lonely pixels
        img = morph.clean(img)

        # Extract the stripe from the thresholded image
        stripe = np.zeros(img.shape, img.dtype)
        stripe[stripe_indices] = img[stripe_indices]

        # Show stripe
        # show2("stripe", stripe*255)

        # Show 3D could
        # show3DCloud(ff, stripe)

        # Get stripe positions (x, y, frame)
        stripe_positions = stripe.nonzero()
        xs = stripe_positions[1]
        ys = stripe_positions[0]
        zs = img_handle.ff.maxframe[stripe_positions]

        return xs, ys, zs

    # If video frames are available, extract indices on all frames in the given range
    else:

        xs_array = []
        ys_array = []
        zs_array = []

        # Go through all frames in the frame range
        for fr in range(frame_min, frame_max + 1):

            # Break the loop if outside frame size
            if fr == (img_handle.total_frames - 1):
                break

            # Set the frame number
            img_handle.setFrame(fr)

            # Load the frame
            fr_img = img_handle.loadFrame()

            # Apply the dark frame
            if dark is not None:
                fr_img = Image.applyDark(fr_img, dark)

            # Apply the flat to frame
            if flat_struct is not None:
                fr_img = Image.applyFlat(fr_img, flat_struct)

            # Mask the image
            fr_img = MaskImage.applyMask(fr_img, mask)

            # Threshold the frame
            img_thres = Image.thresholdImg(fr_img, img_handle.ff.avepixel, img_handle.ff.stdpixel, \
                config.k1_det, config.j1_det, mask=mask, mask_ave_bright=False)

            # Remove lonely pixels
            img_thres = morph.clean(img_thres)

            # Extract the stripe from the thresholded image
            stripe = np.zeros(img_thres.shape, img_thres.dtype)
            stripe[stripe_indices] = img_thres[stripe_indices]

            # Include more pixels for centroiding and photometry and mask out per frame pixels
            if centroiding:

                # Dilate the pixels in the stripe twice, to include more pixels for photometry
                stripe = morph.dilate(stripe)
                stripe = morph.dilate(stripe)

                # Get indices of the stripe that is perpendicular to the meteor, and whose thickness is the
                # length of the meteor on this particular frame - this is called stripe_indices_motion

                # Compute the previous, current, and the next linear model position of the meteor on the
                #   image
                model_pos_prev = point1[:2] + (fr - 1 - z1) * motion_vect
                model_pos = point1[:2] + (fr - z1) * motion_vect
                model_pos_next = point1[:2] + (fr + 1 - z1) * motion_vect

                # Get the rho, theta of the line perpendicular to the meteor line
                x_inters, y_inters = model_pos

                # Check if the previous, current or the next centroids are outside bounds, and if so, skip the
                #   frame
                if (not checkCentroidBounds(model_pos_prev, img_w, img_h)) or \
                    (not checkCentroidBounds(model_pos, img_w, img_h)) or \
                    (not checkCentroidBounds(model_pos_next, img_w, img_h)):

                    continue

                # Get parameters of the perpendicular line to the meteor line
                rho2, theta2 = htLinePerpendicular(rho, theta, x_inters,
                                                   y_inters, img_h, img_w)

                # Compute the image indices of this position which will be the intersection with the stripe
                #   The width of the line will be 2x the angular velocity
                stripe_length = 6 * ang_vel
                if stripe_length < stripe_width_factor * config.stripe_width:
                    stripe_length = stripe_width_factor * config.stripe_width
                stripe_indices_motion = getStripeIndices(
                    rho2, theta2, stripe_length, img_h, img_w)

                # Mark only those parts which overlap both lines, which effectively creates a mask for
                #    photometry an centroiding, excluding other influences
                stripe_new = np.zeros_like(stripe)
                stripe_new[stripe_indices_motion] = stripe[
                    stripe_indices_motion]
                stripe = stripe_new

                if debug:

                    # Show the extracted stripe
                    img_stripe = np.zeros_like(stripe)
                    img_stripe[stripe_indices] = 1
                    final_stripe = np.zeros_like(stripe)
                    final_stripe[stripe_indices_motion] = img_stripe[
                        stripe_indices_motion]

                    plt.imshow(final_stripe)
                    plt.show()

            if debug and centroiding:

                print(fr)
                print('mean stdpixel3:', np.mean(img_handle.ff.stdpixel))
                print('mean avepixel3:', np.mean(img_handle.ff.avepixel))
                print('mean frame:', np.mean(fr_img))
                fig, (ax1, ax2) = plt.subplots(nrows=2,
                                               sharex=True,
                                               sharey=True)

                fr_img_noavg = Image.applyDark(fr_img, img_handle.ff.avepixel)
                #fr_img_noavg = fr_img

                # Auto levels
                min_lvl = np.percentile(fr_img_noavg[2:, :], 1)
                max_lvl = np.percentile(fr_img_noavg[2:, :], 99.0)

                # Adjust levels
                fr_img_autolevel = Image.adjustLevels(fr_img_noavg, min_lvl,
                                                      1.0, max_lvl)

                ax1.imshow(stripe, cmap='gray')
                ax2.imshow(fr_img_autolevel, cmap='gray')
                plt.show()

                pass

            # Get stripe positions (x, y, frame)
            stripe_positions = stripe.nonzero()
            xs = stripe_positions[1]
            ys = stripe_positions[0]
            zs = np.zeros_like(xs) + fr

            # Add the points to the list
            xs_array.append(xs)
            ys_array.append(ys)
            zs_array.append(zs)

            if debug:
                print('---')
                print(stripe.nonzero())
                print(xs, ys, zs)

        if len(xs_array) > 0:

            # Flatten the arrays
            xs_array = np.concatenate(xs_array)
            ys_array = np.concatenate(ys_array)
            zs_array = np.concatenate(zs_array)

        else:
            xs_array = np.array(xs_array)
            ys_array = np.array(ys_array)
            zs_array = np.array(zs_array)

        return xs_array, ys_array, zs_array
def loadImageCalibration(dir_path, config, dtype=None, byteswap=False):
    """ Load the mask, dark and flat. 
    
    Arguments:
        dir_path: [str] Path to the directory with calibration.
        config: [ConfigStruct]

    Keyword arguments:
        dtype: [object] Numpy array dtype for the image. None by default, if which case it will be determined
            from the input image.
        byteswap: [bool] If the dark and flat should be byteswapped. False by default, and should be True for
            UWO PNGs.

    Return:
        mask, dark, flat_struct: [tuple of ndarrays]
    """

    mask_path = None
    mask = None

    # Try loading the mask
    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
    if mask_path:
        mask = MaskImage.loadMask(mask_path)

    if mask is not None:
        print('Loaded mask:', mask_path)
        log.info('Loaded mask: {:s}'.format(mask_path))




    # Try loading the dark frame
    dark = None
    if config.use_dark:

        dark_path = None

        # Check if dark is in the data directory
        if os.path.exists(os.path.join(dir_path, config.dark_file)):
            dark_path = os.path.join(dir_path, config.dark_file)

        # Try loading the default dark
        elif os.path.exists(config.dark_file):
            dark_path = os.path.abspath(config.dark_file)

        if dark_path is not None:

            # Load the dark
            dark = Image.loadDark(*os.path.split(dark_path), dtype=dtype, byteswap=byteswap)

        if dark is not None:
            print('Loaded dark:', dark_path)
            log.info('Loaded dark: {:s}'.format(dark_path))



    # Try loading a flat field image
    flat_struct = None
    if config.use_flat:

        flat_path = None
        
        # Check if there is flat in the data directory
        if os.path.exists(os.path.join(dir_path, config.flat_file)):
            flat_path = os.path.join(dir_path, config.flat_file)
            
        # Try loading the default flat
        elif os.path.exists(config.flat_file):
            flat_path = os.path.abspath(config.flat_file)

        if flat_path is not None:
            
            # Load the flat
            flat_struct = Image.loadFlat(*os.path.split(flat_path), dtype=dtype, byteswap=byteswap)


        if flat_struct is not None:
            print('Loaded flat:', flat_path)
            log.info('Loaded flat: {:s}'.format(flat_path))



    return mask, dark, flat_struct
def getThresholdedStripe3DPoints(config, img_handle, frame_min, frame_max, rho, theta, mask, flat_struct, \
    dark, stripe_width_factor=1.0, centroiding=False, point1=None, point2=None, debug=False):
    """ Threshold the image and get a list of pixel positions and frames of threshold passers. 
        This function handles all input types of data.

    Arguments;
        config: [config object] configuration object (loaded from the .config file).
        img_handle: [FrameInterface instance] Object which has a common interface to various input files.
        frame_min: [int] First frame to process.
        frame_max: [int] Last frame to process.
        rho: [float] Line distance from the center in HT space (pixels).
        theta: [float] Angle in degrees in HT space.
        mask: [ndarray] Image mask.
        flat_struct: [Flat struct] Structure containing the flat field. None by default.
        dark: [ndarray] Dark frame.

    Keyword arguments:
        stripe_width_factor: [float] Multipler by which the default stripe width will be multiplied. Default
            is 1.0
        centroiding: [bool] If True, the indices will be returned in the centroiding mode, which means
            that point1 and point2 arguments must be given.
        point1: [list] (x, y, frame) Of the first reference point of the detection.
        point2: [list] (x, y, frame) Of the second reference point of the detection.
        debug: [bool] If True, extra debug messages and plots will be shown.
    
    Return:
        xs, ys, zs: [tuple of lists] Indices of (x, y, frame) of threshold passers for every frame.
    """


    # Get indices of stripe pixels around the line of the meteor
    img_h, img_w = img_handle.ff.maxpixel.shape
    stripe_indices = getStripeIndices(rho, theta, stripe_width_factor*config.stripe_width, img_h, img_w)

    # If centroiding should be done, prepare everything for cutting out parts of the image for photometry
    if centroiding:

        # Compute the unit vector which describes the motion of the meteor in the image domain
        point1 = np.array(point1)
        point2 = np.array(point2)
        motion_vect = point2[:2] - point1[:2]
        motion_vect_unit = vectNorm(motion_vect)

        # Get coordinates of 2 points that describe the line
        x1, y1, z1 = point1
        x2, y2, z2 = point2

        # Compute the average angular velocity in px per frame
        ang_vel = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)/(z2 - z1)

        # Compute the vector describing the length and direction of the meteor per frame
        motion_vect = ang_vel*motion_vect_unit


    # If the FF files is given, extract the points from FF after threshold
    if img_handle.input_type == 'ff':

        # Threshold the FF file
        img_thres = Image.thresholdFF(img_handle.ff, config.k1_det, config.j1_det, mask=mask, \
            mask_ave_bright=False)

        # Extract the thresholded image by min and max frames from FF file
        img = selectFFFrames(np.copy(img_thres), img_handle.ff, frame_min, frame_max)

        # Remove lonely pixels
        img = morph.clean(img)

        # Extract the stripe from the thresholded image
        stripe = np.zeros(img.shape, img.dtype)
        stripe[stripe_indices] = img[stripe_indices]

        # Show stripe
        # show2("stripe", stripe*255)

        # Show 3D could
        # show3DCloud(ff, stripe)

        # Get stripe positions (x, y, frame)
        stripe_positions = stripe.nonzero()
        xs = stripe_positions[1]
        ys = stripe_positions[0]
        zs = img_handle.ff.maxframe[stripe_positions]

        return xs, ys, zs


    # If video frames are available, extract indices on all frames in the given range
    else:

        xs_array = []
        ys_array = []
        zs_array = []

        # Go through all frames in the frame range
        for fr in range(frame_min, frame_max + 1):


            # Break the loop if outside frame size
            if fr == (img_handle.total_frames - 1):
                break

            # Set the frame number
            img_handle.setFrame(fr)

            # Load the frame
            fr_img = img_handle.loadFrame()


            # Apply the dark frame
            if dark is not None:
                fr_img = Image.applyDark(fr_img, dark)

            # Apply the flat to frame
            if flat_struct is not None:
                fr_img = Image.applyFlat(fr_img, flat_struct)

            # Mask the image
            fr_img = MaskImage.applyMask(fr_img, mask)
                

            # Threshold the frame
            img_thres = Image.thresholdImg(fr_img, img_handle.ff.avepixel, img_handle.ff.stdpixel, \
                config.k1_det, config.j1_det, mask=mask, mask_ave_bright=False)


            # Remove lonely pixels
            img_thres = morph.clean(img_thres)

            # Extract the stripe from the thresholded image
            stripe = np.zeros(img_thres.shape, img_thres.dtype)
            stripe[stripe_indices] = img_thres[stripe_indices]


            # Include more pixels for centroiding and photometry and mask out per frame pixels
            if centroiding:
                
                # Dilate the pixels in the stripe twice, to include more pixels for photometry
                stripe = morph.dilate(stripe)
                stripe = morph.dilate(stripe)

                # Get indices of the stripe that is perpendicular to the meteor, and whose thickness is the 
                # length of the meteor on this particular frame - this is called stripe_indices_motion

                # Compute the previous, current, and the next linear model position of the meteor on the 
                #   image
                model_pos_prev = point1[:2] + (fr - 1 - z1)*motion_vect
                model_pos = point1[:2] + (fr - z1)*motion_vect
                model_pos_next = point1[:2] + (fr + 1 - z1)*motion_vect

                # Get the rho, theta of the line perpendicular to the meteor line
                x_inters, y_inters = model_pos

                # Check if the previous, current or the next centroids are outside bounds, and if so, skip the
                #   frame
                if (not checkCentroidBounds(model_pos_prev, img_w, img_h)) or \
                    (not checkCentroidBounds(model_pos, img_w, img_h)) or \
                    (not checkCentroidBounds(model_pos_next, img_w, img_h)):

                    continue

                # Get parameters of the perpendicular line to the meteor line
                rho2, theta2 = htLinePerpendicular(rho, theta, x_inters, y_inters, img_h, img_w)

                # Compute the image indices of this position which will be the intersection with the stripe
                #   The width of the line will be 2x the angular velocity
                stripe_length = 6*ang_vel
                if stripe_length < stripe_width_factor*config.stripe_width:
                    stripe_length = stripe_width_factor*config.stripe_width
                stripe_indices_motion = getStripeIndices(rho2, theta2, stripe_length, img_h, img_w)

                # Mark only those parts which overlap both lines, which effectively creates a mask for
                #    photometry an centroiding, excluding other influences
                stripe_new = np.zeros_like(stripe)
                stripe_new[stripe_indices_motion] = stripe[stripe_indices_motion]
                stripe = stripe_new


                if debug:

                    # Show the extracted stripe
                    img_stripe = np.zeros_like(stripe)
                    img_stripe[stripe_indices] = 1
                    final_stripe = np.zeros_like(stripe)
                    final_stripe[stripe_indices_motion] = img_stripe[stripe_indices_motion]

                    plt.imshow(final_stripe)
                    plt.show()


            if debug and centroiding:

                print(fr)
                print('mean stdpixel3:', np.mean(img_handle.ff.stdpixel))
                print('mean avepixel3:', np.mean(img_handle.ff.avepixel))
                print('mean frame:', np.mean(fr_img))
                fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True, sharey=True)


                fr_img_noavg = Image.applyDark(fr_img, img_handle.ff.avepixel)
                #fr_img_noavg = fr_img

                # Auto levels
                min_lvl = np.percentile(fr_img_noavg[2:, :], 1)
                max_lvl = np.percentile(fr_img_noavg[2:, :], 99.0)

                # Adjust levels
                fr_img_autolevel = Image.adjustLevels(fr_img_noavg, min_lvl, 1.0, max_lvl)

                ax1.imshow(stripe, cmap='gray')
                ax2.imshow(fr_img_autolevel, cmap='gray')
                plt.show()

                pass


            # Get stripe positions (x, y, frame)
            stripe_positions = stripe.nonzero()
            xs = stripe_positions[1]
            ys = stripe_positions[0]
            zs = np.zeros_like(xs) + fr

            # Add the points to the list
            xs_array.append(xs)
            ys_array.append(ys)
            zs_array.append(zs)


            if debug:
                print('---')
                print(stripe.nonzero())
                print(xs, ys, zs)


        if len(xs_array) > 0:
            
            # Flatten the arrays
            xs_array = np.concatenate(xs_array)
            ys_array = np.concatenate(ys_array)
            zs_array = np.concatenate(zs_array)

        else:
            xs_array = np.array(xs_array)
            ys_array = np.array(ys_array)
            zs_array = np.array(zs_array)


        return xs_array, ys_array, zs_array
Beispiel #8
0
def extractStars(ff_dir,
                 ff_name,
                 config=None,
                 max_global_intensity=150,
                 border=10,
                 neighborhood_size=10,
                 intensity_threshold=5,
                 flat_struct=None):
    """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star 
        confirmation.

        Source of one part of the code: 
    http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value
    
    Arguments:
        ff: [ff bin struct] FF bin file loaded in the FF bin structure
        config: [config object] configuration object (loaded from the .config file)
        max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright
        border: [int] apply a mask on the detections by removing all that are too close to the given image 
            border (in pixels)
        neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels)
        intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255)
        flat_struct: [Flat struct] Structure containing the flat field. None by default.

    Return:
        x2, y2, background, intensity: [list of ndarrays]
            - x2: X axis coordinates of the star
            - y2: Y axis coordinates of the star
            - background: background intensity
            - intensity: intensity of the star
    """

    # Load parameters from config if given
    if config:
        max_global_intensity = config.max_global_intensity
        border = config.border
        neighborhood_size = config.neighborhood_size
        intensity_threshold = config.intensity_threshold

    # Load the FF bin file
    ff = FFfile.read(ff_dir, ff_name)

    # Load the mask file
    mask = MaskImage.loadMask(config.mask_file)

    # Mask the FF file
    ff = MaskImage.applyMask(ff, mask, ff_flag=True)

    # Apply the flat to maxpixel and avepixel
    if flat_struct is not None:

        ff.maxpixel = Image.applyFlat(ff.maxpixel, flat_struct)
        ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct)

    # Calculate image mean and stddev
    global_mean = np.mean(ff.avepixel)

    # Check if the image is too bright and skip the image
    if global_mean > max_global_intensity:
        return [[], [], [], []]

    data = ff.avepixel.astype(np.float32)

    # Apply a mean filter to the image to reduce noise
    data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0 / 4))

    # Locate local maxima on the image
    data_max = filters.maximum_filter(data, neighborhood_size)
    maxima = (data == data_max)
    data_min = filters.minimum_filter(data, neighborhood_size)
    diff = ((data_max - data_min) > intensity_threshold)
    maxima[diff == 0] = 0

    # Apply a border mask
    border_mask = np.ones_like(maxima) * 255
    border_mask[:border, :] = 0
    border_mask[-border:, :] = 0
    border_mask[:, :border] = 0
    border_mask[:, -border:] = 0
    maxima = MaskImage.applyMask(maxima, (True, border_mask))

    # Find and label the maxima
    labeled, num_objects = ndimage.label(maxima)

    # Skip the image if there are too many maxima to process
    if num_objects > config.max_stars:
        print('Too many candidate stars to process! {:d}/{:d}'.format(
            num_objects, config.max_stars))
        return [[], [], [], []]

    # Find centres of mass of each labeled objects
    xy = np.array(
        ndimage.center_of_mass(data, labeled, range(1, num_objects + 1)))

    # Remove all detection on the border
    #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))]

    # Unpack star coordinates
    y, x = np.hsplit(xy, 2)

    # # Plot stars before the PSF fit
    # plotStars(ff, x, y)

    # Fit a PSF to each star
    x2, y2, amplitude, intensity = fitPSF(ff, global_mean, x, y, config=config)
    # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit

    # # Plot stars after PSF fit filtering
    # plotStars(ff, x2, y2)

    return x2, y2, amplitude, intensity
Beispiel #9
0
def detectMeteors(ff_directory, ff_name, config, flat_struct=None):
    """ Detect meteors on the given FF bin image. Here are the steps in the detection:
            - input image (FF bin format file) is thresholded (converted to black and white)
            - several morphological operations are applied to clean the image
            - image is then broken into several image "windows" (these "windows" are reconstructed from the input FF file, given
              an input frame range (e.g. 64-128) which helps reduce the noise further)
            - on each "window" the Kernel-based Hough transform is performed to find any lines on the image
            - similar lines are joined
            - stripe around the lines is extracted
            - 3D line finding (third dimension is time) is applied to check if the line propagates in time
            - centroiding is performed, which calculates the position and intensity of meteor on each frame
    
    Arguments:
        ff_directory: [string] an absolute path to the input FF bin file
        ff_name: [string] file name of the FF bin file on which to run the detection on
        config: [config object] configuration object (loaded from the .config file)

    Keyword arguments:
        flat_struct: [Flat struct] Structure containing the flat field. None by default.
    
    Return:
        meteor_detections: [list] a list of detected meteors, with these elements:
            - rho: [float] meteor line distance from image center (polar coordinates, in pixels)
            - theta: [float] meteor line angle from image center (polar coordinates, in degrees)
            - centroids: [list] [frame, X, Y, level] list of meteor points
    """

    t1 = time()
    t_all = time()

    # Load the FF bin file
    ff = FFfile.read(ff_directory, ff_name)

    # Load the mask file
    mask = MaskImage.loadMask(config.mask_file)

    # Mask the FF file
    ff = MaskImage.applyMask(ff, mask, ff_flag=True)

    # Apply the flat to maxpixel and avepixel
    if flat_struct is not None:

        ff.maxpixel = Image.applyFlat(ff.maxpixel, flat_struct)
        ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct)

    # At the end, a check that the detection has a surface brightness above the background will be performed.
    # The assumption here is that the peak of the meteor should have the intensity which is at least
    # that of a patch of 4x4 pixels that are of the mean background brightness
    min_patch_intensity = 4 * 4 * (np.mean(ff.maxpixel - ff.avepixel) +
                                   config.k1_det * np.mean(ff.stdpixel) +
                                   config.j1)

    # # Show the maxpixel image
    # show2(ff_name+' maxpixel', ff.maxpixel)

    # Get lines on the image
    line_list = getLines(ff, config.k1_det, config.j1_det, config.time_slide,
                         config.time_window_size, config.max_lines_det,
                         config.max_white_ratio, config.kht_lib_path)

    logDebug('List of lines:', line_list)

    # Init meteor list
    meteor_detections = []

    # Only if there are some lines in the image
    if len(line_list):

        # Join similar lines
        line_list = mergeLines(line_list, config.line_min_dist, ff.ncols,
                               ff.nrows)

        logDebug('Time for finding lines:', time() - t1)

        logDebug('Number of KHT lines: ', len(line_list))
        logDebug(line_list)

        # Plot lines
        # plotLines(ff, line_list)

        # Threshold the image
        img_thres = thresholdImg(ff, config.k1_det, config.j1_det)

        filtered_lines = []

        # Analyze stripes of each line
        for line in line_list:
            rho, theta, frame_min, frame_max = line

            logDebug('rho, theta, frame_min, frame_max')
            logDebug(rho, theta, frame_min, frame_max)

            # Bounded the thresholded image by min and max frames
            img = selectFrames(np.copy(img_thres), ff, frame_min, frame_max)

            # Remove lonely pixels
            img = morph.clean(img)

            # Get indices of stripe pixels around the line
            stripe_indices = getStripeIndices(rho, theta, config.stripe_width,
                                              img.shape[0], img.shape[1])

            # Extract the stripe from the thresholded image
            stripe = np.zeros((ff.nrows, ff.ncols), np.uint8)
            stripe[stripe_indices] = img[stripe_indices]

            # Show stripe
            #COMMENTED
            # show2("stripe", stripe*255)

            # Show 3D could
            # show3DCloud(ff, stripe)

            # Get stripe positions
            stripe_positions = stripe.nonzero()
            xs = stripe_positions[1]
            ys = stripe_positions[0]
            zs = ff.maxframe[stripe_positions]

            # Limit the number of points to search if too large
            if len(zs) > config.max_points_det:

                # Extract weights of each point
                maxpix_elements = ff.maxpixel[ys, xs].astype(np.float64)
                weights = maxpix_elements / np.sum(maxpix_elements)

                # Random sample the point, sampling is weighted by pixel intensity
                indices = np.random.choice(len(zs),
                                           config.max_points_det,
                                           replace=False,
                                           p=weights)
                ys = ys[indices]
                xs = xs[indices]
                zs = zs[indices]

            # Make an array to feed into the gropuing algorithm
            stripe_points = np.vstack((xs, ys, zs))
            stripe_points = np.swapaxes(stripe_points, 0, 1)

            # Sort stripe points by frame
            stripe_points = stripe_points[stripe_points[:, 2].argsort()]

            t1 = time()

            logDebug('finding lines...')

            # Find a single line in the point cloud
            detected_line = find3DLines(stripe_points,
                                        time(),
                                        config,
                                        fireball_detection=False)

            logDebug('time for GROUPING: ', time() - t1)

            # Extract the first and only line if any
            if detected_line:
                detected_line = detected_line[0]

                # logDebug(detected_line)

                # Show 3D cloud
                # show3DCloud(ff, stripe, detected_line, stripe_points, config)

                # Add the line to the results list
                filtered_lines.append(detected_line)

        # Merge similar lines in 3D
        filtered_lines = merge3DLines(filtered_lines, config.vect_angle_thresh)

        logDebug('after filtering:')
        logDebug(filtered_lines)

        for detected_line in filtered_lines:

            # Get frame range
            frame_min = detected_line[4]
            frame_max = detected_line[5]

            # Check if the line covers a minimum frame range
            if (abs(frame_max - frame_min) + 1 <
                    config.line_minimum_frame_range_det):
                continue

            # Extand the frame range for several frames, just to be sure to catch all parts of a meteor
            frame_min -= config.frame_extension
            frame_max += config.frame_extension

            # Cap values to 0-255
            frame_min = max(frame_min, 0)
            frame_max = min(frame_max, 255)

            logDebug(detected_line)

            # Get coordinates of 2 points that describe the line
            x1, y1, z1 = detected_line[0]
            x2, y2, z2 = detected_line[1]

            # Convert Cartesian line coordinates to polar
            rho, theta = getPolarLine(x1, y1, x2, y2, ff.nrows, ff.ncols)

            # Convert Cartesian line coordinate to CAMS compatible polar coordinates (flipped Y axis)
            rho_cams, theta_cams = getPolarLine(x1, ff.nrows - y1, x2,
                                                ff.nrows - y2, ff.nrows,
                                                ff.ncols)

            logDebug('converted rho, theta')
            logDebug(rho, theta)

            # Bounded the thresholded image by min and max frames
            img = selectFrames(np.copy(img_thres), ff, frame_min, frame_max)

            # Remove lonely pixels
            img = morph.clean(img)

            # Get indices of stripe pixels around the line
            stripe_indices = getStripeIndices(rho, theta,
                                              int(config.stripe_width * 1.5),
                                              img.shape[0], img.shape[1])

            # Extract the stripe from the thresholded image
            stripe = np.zeros((ff.nrows, ff.ncols), np.uint8)
            stripe[stripe_indices] = img[stripe_indices]

            # Show detected line
            # show('detected line: '+str(frame_min)+'-'+str(frame_max), stripe)

            # Get stripe positions
            stripe_positions = stripe.nonzero()
            xs = stripe_positions[1]
            ys = stripe_positions[0]
            zs = ff.maxframe[stripe_positions]

            # Make an array to feed into the centroiding algorithm
            stripe_points = np.vstack((xs, ys, zs))
            stripe_points = np.swapaxes(stripe_points, 0, 1)

            # Sort stripe points by frame
            stripe_points = stripe_points[stripe_points[:, 2].argsort()]

            # Show 3D cloud
            # show3DCloud(ff, stripe, detected_line, stripe_points, config)

            # Get points of the given line
            line_points = getAllPoints(stripe_points,
                                       x1,
                                       y1,
                                       z1,
                                       x2,
                                       y2,
                                       z2,
                                       config,
                                       fireball_detection=False)

            # Skip if no points were returned
            if not line_points.any():
                continue

            # Skip if the points cover too small a frame range
            if abs(np.max(line_points[:, 2]) - np.min(line_points[:, 2])
                   ) + 1 < config.line_minimum_frame_range_det:
                continue

            # Calculate centroids
            centroids = []

            for i in range(frame_min, frame_max + 1):

                # Select pixel indicies belonging to a given frame
                frame_pixels_inds = np.where(line_points[:, 2] == i)

                # Get pixel positions in a given frame (pixels belonging to a found line)
                frame_pixels = line_points[frame_pixels_inds].astype(np.int64)

                # Get pixel positions in a given frame (pixels belonging to the whole stripe)
                frame_pixels_stripe = stripe_points[np.where(
                    stripe_points[:, 2] == i)].astype(np.int64)

                # Skip if there are no pixels in the frame
                if not len(frame_pixels):
                    continue

                # Calculate weights for centroiding
                max_avg_corrected = ff.maxpixel - ff.avepixel
                flattened_weights = (max_avg_corrected).astype(
                    np.float32) / ff.stdpixel

                # Calculate centroids by half-frame
                for half_frame in range(2):

                    # Apply deinterlacing if it is present in the video
                    if config.deinterlace_order >= 0:

                        # Deinterlace by fields (line lixels)
                        half_frame_pixels = frame_pixels[
                            frame_pixels[:, 1] %
                            2 == (config.deinterlace_order + half_frame) % 2]

                        # Deinterlace by fields (stripe pixels)
                        half_frame_pixels_stripe = frame_pixels_stripe[
                            frame_pixels_stripe[:, 1] %
                            2 == (config.deinterlace_order + half_frame) % 2]

                        # Skip if there are no pixels in the half-frame
                        if not len(half_frame_pixels):
                            continue

                        # Calculate half-frame value
                        frame_no = i + half_frame * 0.5

                    # No deinterlacing
                    else:

                        # Skip the second half frame
                        if half_frame == 1:
                            continue

                        half_frame_pixels = frame_pixels
                        half_frame_pixels_stripe = frame_pixels_stripe
                        frame_no = i

                    # Get maxpixel-avepixel values of given pixel indices (this will be used as weights)
                    max_weights = flattened_weights[half_frame_pixels[:, 1],
                                                    half_frame_pixels[:, 0]]

                    # Calculate weighted centroids
                    x_weighted = half_frame_pixels[:, 0] * np.transpose(
                        max_weights)
                    x_centroid = np.sum(x_weighted) / float(
                        np.sum(max_weights))

                    y_weighted = half_frame_pixels[:, 1] * np.transpose(
                        max_weights)
                    y_centroid = np.sum(y_weighted) / float(
                        np.sum(max_weights))

                    # Calculate intensity as the sum of threshold passer pixels on the stripe
                    #intensity_values = max_avg_corrected[half_frame_pixels[:,1], half_frame_pixels[:,0]]
                    intensity_values = max_avg_corrected[
                        half_frame_pixels_stripe[:, 1],
                        half_frame_pixels_stripe[:, 0]]
                    intensity = np.sum(intensity_values)

                    logDebug("centroid: ", frame_no, x_centroid, y_centroid,
                             intensity)

                    centroids.append(
                        [frame_no, x_centroid, y_centroid, intensity])

            # Filter centroids
            centroids = filterCentroids(centroids,
                                        config.centroids_max_deviation,
                                        config.centroids_max_distance)

            # Convert to numpy array for easy slicing
            centroids = np.array(centroids)

            # Reject the solution if there are too few centroids
            if len(centroids) < config.line_minimum_frame_range_det:
                continue

            # Check that the detection has a surface brightness above the background
            # The assumption here is that the peak of the meteor should have the intensity which is at least
            # that of a patch of 4x4 pixels that are of the mean background brightness
            if np.max(centroids[:, 3]) < min_patch_intensity:
                continue

            # Check the detection if it has the proper angular velocity
            if not checkAngularVelocity(centroids, config):
                continue

            # Append the result to the meteor detections
            meteor_detections.append([rho_cams, theta_cams, centroids])

            logDebug('time for processing:', time() - t_all)

            # # Plot centroids to image
            # fig, (ax1, ax2) = plt.subplots(nrows=2)

            # ax1.imshow(ff.maxpixel - ff.avepixel, cmap='gray')
            # ax1.scatter(centroids[:,1], centroids[:,2], s=5, c='r', edgecolors='none')

            # # Plot lightcurve
            # ax2.plot(centroids[:,0], centroids[:,3])

            # # # Plot relative angular velocity
            # # ang_vels = []
            # # fr_prev, x_prev, y_prev, _ = centroids[0]
            # # for fr, x, y, _ in centroids[1:]:
            # #     dx = x - x_prev
            # #     dy = y - y_prev
            # #     dfr = fr - fr_prev

            # #     ddist = np.sqrt(dx**2 + dy**2)
            # #     dt = dfr/config.fps

            # #     ang_vels.append(ddist/dt)

            # #     x_prev = x
            # #     y_prev = y
            # #     fr_prev = fr

            # # ax2.plot(ang_vels)

            # plt.show()

    return meteor_detections
def archiveDetections(captured_path, archived_path, ff_detected, config, extra_files=None):
    """ Create thumbnails and compress all files with detections and the accompanying files in one archive.

    Arguments:
        captured_path: [str] Path where the captured files are located.
        archived_path: [str] Path where the detected files will be archived to.
        ff_detected: [str] A list of FF files with detections.
        config: [conf object] Configuration.

    Keyword arguments:
        extra_files: [list] A list of extra files (with fill paths) which will be be saved to the night 
            archive.

    Return:
        archive_name: [str] Name of the archive where the files were compressed to.

    """

    log.info('Generating thumbnails...')

    try:
        # Generate captured thumbnails
        generateThumbnails(captured_path, config, 'CAPTURED')

        # Get the list of files to archive
        file_list = selectFiles(captured_path, ff_detected)


        # Generate detected thumbnails
        mosaic_file = generateThumbnails(captured_path, config, 'DETECTED', file_list=sorted(file_list))

        # Add the detected mosaic file to the selected list
        file_list.append(mosaic_file)

    except Exception as e:
        log.error('Generating thumbnails failed with error:' + repr(e))
        log.error(*traceback.format_exception(*sys.exc_info()))



    log.info('Generating a stack of detections...')

    try:

        # Load the mask for stack
        mask = None
        if os.path.exists(config.mask_file) and config.stack_mask:
            mask_path = os.path.abspath(config.mask_file)
            mask = MaskImage.loadMask(mask_path)
            

        # Make a co-added image of all detection. Filter out possible clouds
        stack_path, _ = stackFFs(captured_path, 'jpg', deinterlace=(config.deinterlace_order > 0), subavg=True, \
            filter_bright=True, file_list=sorted(file_list), mask=mask)

        if stack_path is not None:

            # Extract the name of the stack image
            stack_file = os.path.basename(stack_path)
            
            # Add the stack path to the list of files to put in the archive
            file_list.append(stack_file)

    except Exception as e:
        log.error('Generating stack failed with error:' + repr(e))
        log.error(*traceback.format_exception(*sys.exc_info()))



    if file_list:

        # Create the archive ZIP in the parent directory of the archive directory
        archive_name = os.path.join(os.path.abspath(os.path.join(archived_path, os.pardir)), 
            os.path.basename(captured_path) + '_detected')

        # Archive the files
        archive_name = archiveDir(captured_path, file_list, archived_path, archive_name, \
            extra_files=extra_files)

        return archive_name

    return None
Beispiel #11
0
def extractStars(ff_dir,
                 ff_name,
                 config=None,
                 max_global_intensity=150,
                 border=10,
                 neighborhood_size=10,
                 intensity_threshold=5,
                 flat_struct=None,
                 dark=None,
                 mask=None):
    """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star 
        confirmation.

        Source of one part of the code: 
    http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value
    
    Arguments:
        ff_dir: [str] Path to directory where FF files are.
        ff_name: [str] Name of the FF file.
        config: [config object] configuration object (loaded from the .config file)
        max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright
        border: [int] apply a mask on the detections by removing all that are too close to the given image 
            border (in pixels)
        neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels)
        intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255)
        flat_struct: [Flat struct] Structure containing the flat field. None by default.
        dark: [ndarray] Dark frame. None by default.
        mask: [ndarray] Mask image. None by default.

    Return:
        x2, y2, background, intensity, fwhm: [list of ndarrays]
            - x2: X axis coordinates of the star
            - y2: Y axis coordinates of the star
            - background: background intensity
            - intensity: intensity of the star
            - Gaussian Full width at half maximum (FWHM) of fitted stars
    """

    # This will be returned if there was an error
    error_return = [[], [], [], [], [], []]

    # Load parameters from config if given
    if config:
        max_global_intensity = config.max_global_intensity
        border = config.border
        neighborhood_size = config.neighborhood_size
        intensity_threshold = config.intensity_threshold

    # Load the FF bin file
    ff = FFfile.read(ff_dir, ff_name)

    # If the FF file could not be read, skip star extraction
    if ff is None:
        return error_return

    # Apply the dark frame
    if dark is not None:
        ff.avepixel = Image.applyDark(ff.avepixel, dark)

    # Apply the flat
    if flat_struct is not None:
        ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct)

    # Mask the FF file
    if mask is not None:
        ff = MaskImage.applyMask(ff, mask, ff_flag=True)

    # Calculate image mean and stddev
    global_mean = np.mean(ff.avepixel)

    # Check if the image is too bright and skip the image
    if global_mean > max_global_intensity:
        return error_return

    data = ff.avepixel.astype(np.float32)

    # Apply a mean filter to the image to reduce noise
    data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0 / 4))

    # Locate local maxima on the image
    data_max = filters.maximum_filter(data, neighborhood_size)
    maxima = (data == data_max)
    data_min = filters.minimum_filter(data, neighborhood_size)
    diff = ((data_max - data_min) > intensity_threshold)
    maxima[diff == 0] = 0

    # Apply a border mask
    border_mask = np.ones_like(maxima) * 255
    border_mask[:border, :] = 0
    border_mask[-border:, :] = 0
    border_mask[:, :border] = 0
    border_mask[:, -border:] = 0
    maxima = MaskImage.applyMask(maxima, border_mask, image=True)

    # Remove all detections close to the mask image
    if mask is not None:
        erosion_kernel = np.ones((5, 5), mask.img.dtype)
        mask_eroded = cv2.erode(mask.img, erosion_kernel, iterations=1)

        maxima = MaskImage.applyMask(maxima, mask_eroded, image=True)

    # Find and label the maxima
    labeled, num_objects = ndimage.label(maxima)

    # Skip the image if there are too many maxima to process
    if num_objects > config.max_stars:
        print('Too many candidate stars to process! {:d}/{:d}'.format(
            num_objects, config.max_stars))
        return error_return

    # Find centres of mass of each labeled objects
    xy = np.array(
        ndimage.center_of_mass(data, labeled, range(1, num_objects + 1)))

    # Remove all detection on the border
    #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))]

    # Unpack star coordinates
    y, x = np.hsplit(xy, 2)

    # # Plot stars before the PSF fit
    # plotStars(ff, x, y)

    # Fit a PSF to each star
    x2, y2, amplitude, intensity, sigma_y_fitted, sigma_x_fitted = fitPSF(
        ff, global_mean, x, y, config)

    # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit

    # # Plot stars after PSF fit filtering
    # plotStars(ff, x2, y2)

    # Compute FWHM from one dimensional sigma
    sigma_x_fitted = np.array(sigma_x_fitted)
    sigma_y_fitted = np.array(sigma_y_fitted)
    sigma_fitted = np.sqrt(sigma_x_fitted**2 + sigma_y_fitted**2)
    fwhm = 2.355 * sigma_fitted

    return ff_name, x2, y2, amplitude, intensity, fwhm
Beispiel #12
0
def stackFFs(dir_path, file_format, deinterlace=False, subavg=False, filter_bright=False, flat_path=None,
    file_list=None, mask=None):
    """ Stack FF files in the given folder. 

    Arguments:
        dir_path: [str] Path to the directory with FF files.
        file_format: [str] Image format for the stack. E.g. jpg, png, bmp

    Keyword arguments:
        deinterlace: [bool] True if the image shoud be deinterlaced prior to stacking. False by default.
        subavg: [bool] Whether the average pixel image should be subtracted form the max pixel image. False
            by default. 
        filter_bright: [bool] Whether images with bright backgrounds (after average subtraction) should be
            skipped. False by defualt.
        flat_path: [str] Path to the flat calibration file. None by default. Will only be used if subavg is
            False.
        file_list: [list] A list of file for stacking. False by default, in which case all FF files in the
            given directory will be used.
        mask: [MaskStructure] Mask to apply to the stack. None by default.

    Return:
        stack_path, merge_img:
            - stack_path: [str] Path of the save stack.
            - merge_img: [ndarray] Numpy array of the stacked image.
    """

    # Load the flat if it was given
    flat = None
    if flat_path != '':

        # Try finding the default flat
        if flat_path is None:
            flat_path = dir_path
            flat_file = 'flat.bmp'

        else:
            flat_path, flat_file = os.path.split(flat_path)

        flat_full_path = os.path.join(flat_path, flat_file)
        if os.path.isfile(flat_full_path):

            # Load the flat
            flat = loadFlat(flat_path, flat_file)

            print('Loaded flat:', flat_full_path)


    first_img = True

    n_stacked = 0
    total_ff_files = 0
    merge_img = None

    # If the list of files was not given, take all files in the given folder
    if file_list is None:
        file_list = sorted(os.listdir(dir_path))


    # List all FF files in the current dir
    for ff_name in file_list:
        if validFFName(ff_name):

            # Load FF file
            ff = readFF(dir_path, ff_name)

            # Skip the file if it is corruped
            if ff is None:
                continue

            total_ff_files += 1

            maxpixel = ff.maxpixel
            avepixel = ff.avepixel

            # Dinterlace the images
            if deinterlace:
                maxpixel = deinterlaceBlend(maxpixel)
                avepixel = deinterlaceBlend(avepixel)

            # If the flat was given, apply it to the image, only if no subtraction is done
            if (flat is not None) and not subavg:
                maxpixel = applyFlat(maxpixel, flat)
                avepixel = applyFlat(avepixel, flat)


            # Reject the image if the median subtracted image is too bright. This usually means that there
            #   are clouds on the image which can ruin the stack
            if filter_bright:

                img = maxpixel - avepixel

                # Compute surface brightness
                median = np.median(img)

                # Compute top detection pixels
                top_brightness = np.percentile(img, 99.9)

                # Reject all images where the median brightness is high
                # Preserve images with very bright detections
                if (median > 10) and (top_brightness < (2**(8*img.itemsize) - 10)):
                    print('Skipping: ', ff_name, 'median:', median, 'top brightness:', top_brightness)
                    continue


            # Subtract the average from maxpixel
            if subavg:
                img = maxpixel - avepixel

            else:
                img = maxpixel

            if first_img:
                merge_img = np.copy(img)
                first_img = False
                continue

            print('Stacking: ', ff_name)

            # Blend images 'if lighter'
            merge_img = blendLighten(merge_img, img)

            n_stacked += 1


    # If the number of stacked image is less than 20% of the given images, stack without filtering
    if filter_bright and (n_stacked < 0.2*total_ff_files):
        return stackFFs(dir_path, file_format, deinterlace=deinterlace, subavg=subavg, 
            filter_bright=False, flat_path=flat_path, file_list=file_list)

    # If no images were stacked, do nothing
    if n_stacked == 0:
        return None, None


    # Extract the name of the night directory which contains the FF files
    night_dir = os.path.basename(dir_path)

    stack_path = os.path.join(dir_path, night_dir + '_stack_{:d}_meteors.'.format(n_stacked) + file_format)

    print("Saving stack to:", stack_path)

    # Stretch the levels
    merge_img = adjustLevels(merge_img, np.percentile(merge_img, 0.5), 1.3, np.percentile(merge_img, 99.9))


    # Apply the mask, if given
    if mask is not None:
        merge_img = MaskImage.applyMask(merge_img, mask)

    
    # Save the blended image
    scipy.misc.imsave(stack_path, merge_img)


    return stack_path, merge_img
Beispiel #13
0
    arg_parser.add_argument('-m', '--mask', metavar='MASK_PATH', type=str, 
        help="Apply a given flat frame. If no path to the flat is given, flat.bmp from the folder will be taken.")

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

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


    # Load the mask
    mask = None
    if cml_args.mask is not None:
        if os.path.exists(cml_args.mask):
            mask_path = os.path.abspath(cml_args.mask)
            print('Loading mask:', mask_path)
            mask = MaskImage.loadMask(mask_path)


    # Run stacking
    stack_path, merge_img = stackFFs(cml_args.dir_path[0], cml_args.file_format[0], \
        deinterlace=cml_args.deinterlace, subavg=cml_args.subavg, filter_bright=cml_args.brightfilt, \
        flat_path=cml_args.flat, mask=mask)



    if not cml_args.hideplot:
        
        # Plot the blended image
        plt.imshow(merge_img, cmap='gray', vmin=0, vmax=255)

        plt.show()
def extractStars(ff_dir, ff_name, config=None, max_global_intensity=150, border=10, neighborhood_size=10, 
        intensity_threshold=5, flat_struct=None, dark=None, mask=None):
    """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star 
        confirmation.

        Source of one part of the code: 
    http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value
    
    Arguments:
        ff: [ff bin struct] FF bin file loaded in the FF bin structure
        config: [config object] configuration object (loaded from the .config file)
        max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright
        border: [int] apply a mask on the detections by removing all that are too close to the given image 
            border (in pixels)
        neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels)
        intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255)
        flat_struct: [Flat struct] Structure containing the flat field. None by default.
        dark: [ndarray] Dark frame. None by default.
        mask: [ndarray] Mask image. None by default.

    Return:
        x2, y2, background, intensity, sigma_fitted: [list of ndarrays]
            - x2: X axis coordinates of the star
            - y2: Y axis coordinates of the star
            - background: background intensity
            - intensity: intensity of the star
            - Gaussian stddev of fitted stars
    """

    # This will be returned if there was an error
    error_return = [[], [], [], []]

    # Load parameters from config if given
    if config:
        max_global_intensity = config.max_global_intensity
        border = config.border
        neighborhood_size = config.neighborhood_size
        intensity_threshold = config.intensity_threshold
        

    # Load the FF bin file
    ff = FFfile.read(ff_dir, ff_name)


    # If the FF file could not be read, skip star extraction
    if ff is None:
        return error_return


    # Apply the dark frame
    if dark is not None:
        ff.avepixel = Image.applyDark(ff.avepixel, dark)

    # Apply the flat
    if flat_struct is not None:
        ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct)

    # Mask the FF file
    if mask is not None:
        ff = MaskImage.applyMask(ff, mask, ff_flag=True)


    # Calculate image mean and stddev
    global_mean = np.mean(ff.avepixel)

    # Check if the image is too bright and skip the image
    if global_mean > max_global_intensity:
        return error_return

    data = ff.avepixel.astype(np.float32)


    # Apply a mean filter to the image to reduce noise
    data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0/4))

    # Locate local maxima on the image
    data_max = filters.maximum_filter(data, neighborhood_size)
    maxima = (data == data_max)
    data_min = filters.minimum_filter(data, neighborhood_size)
    diff = ((data_max - data_min) > intensity_threshold)
    maxima[diff == 0] = 0

    # Apply a border mask
    border_mask = np.ones_like(maxima)*255
    border_mask[:border,:] = 0
    border_mask[-border:,:] = 0
    border_mask[:,:border] = 0
    border_mask[:,-border:] = 0
    maxima = MaskImage.applyMask(maxima, border_mask, image=True)


    # Find and label the maxima
    labeled, num_objects = ndimage.label(maxima)

    # Skip the image if there are too many maxima to process
    if num_objects > config.max_stars:
        print('Too many candidate stars to process! {:d}/{:d}'.format(num_objects, config.max_stars))
        return error_return

    # Find centres of mass of each labeled objects
    xy = np.array(ndimage.center_of_mass(data, labeled, range(1, num_objects+1)))

    # Remove all detection on the border
    #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))]

    # Unpack star coordinates
    y, x = np.hsplit(xy, 2)

    # # Plot stars before the PSF fit
    # plotStars(ff, x, y)

    # Fit a PSF to each star
    x2, y2, amplitude, intensity, sigma_y_fitted, sigma_x_fitted = fitPSF(ff, global_mean, x, y, config)
    
    # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit

    # # Plot stars after PSF fit filtering
    # plotStars(ff, x2, y2)
    

    # Compute one dimensional sigma
    sigma_x_fitted = np.array(sigma_x_fitted)
    sigma_y_fitted = np.array(sigma_y_fitted)
    sigma_fitted = np.sqrt(sigma_x_fitted**2 + sigma_y_fitted**2)


    return ff_name, x2, y2, amplitude, intensity, sigma_fitted