コード例 #1
0
    def fitGC(self):
        """ Fits great circle to observations. """

        self.cartesian_points = []

        self.ra_array = np.array(self.ra_array)
        self.dec_array = np.array(self.dec_array)

        for ra, dec in zip(self.ra_array, self.dec_array):

            vect = vectNorm(raDec2Vector(ra, dec))

            self.cartesian_points.append(vect)


        self.cartesian_points = np.array(self.cartesian_points)

        # Set begin and end pointing vectors
        self.beg_vect = self.cartesian_points[0]
        self.end_vect = self.cartesian_points[-1]

        # Compute alt of the begining and the last point
        self.beg_azim, self.beg_alt = raDec2AltAz(self.ra_array[0], self.dec_array[0], self.jd_array[0], \
            self.lat, self.lon)
        self.end_azim, self.end_alt = raDec2AltAz(self.ra_array[-1], self.dec_array[-1], self.jd_array[-1], \
            self.lat, self.lon)


        # Fit a great circle through observations
        x_arr, y_arr, z_arr = self.cartesian_points.T
        coeffs, self.theta0, self.phi0 = fitGreatCircle(x_arr, y_arr, z_arr)

        # Calculate the plane normal
        self.normal = np.array([coeffs[0], coeffs[1], -1.0])

        # Norm the normal vector to unit length
        self.normal = vectNorm(self.normal)

        # Compute RA/Dec of the normal direction
        self.normal_ra, self.normal_dec = vector2RaDec(self.normal)


        # Take pointing directions of the beginning and the end of the meteor
        self.meteor_begin_cartesian = vectNorm(self.cartesian_points[0])
        self.meteor_end_cartesian = vectNorm(self.cartesian_points[-1])

        # Compute angular distance between begin and end (radians)
        self.ang_be = angularSeparationVect(self.beg_vect, self.end_vect)

        # Compute meteor duration in seconds
        self.duration = (self.jd_array[-1] - self.jd_array[0])*86400.0

        # Set the reference JD as the JD of the beginning
        self.jdt_ref = self.jd_array[0]

        # Compute the solar longitude of the beginning (degrees)
        self.lasun = np.degrees(jd2SolLonSteyaert(self.jdt_ref))
コード例 #2
0
def vector2RaDec(eci):
    """ Convert Earth-centered intertial vector to right ascension and declination.
    Arguments:
        eci: [3 element ndarray] Vector coordinates in Earth-centered inertial system
    Return:
        (ra, dec): [tuple of floats] right ascension and declinaton (degrees)
    """

    # Normalize the ECI coordinates
    eci = vectNorm(eci)

    # Calculate declination
    dec = np.arcsin(eci[2])

    # Calculate right ascension
    ra = np.arctan2(eci[1], eci[0]) % (2 * np.pi)

    return np.degrees(ra), np.degrees(dec)
コード例 #3
0
ファイル: DetectionTools.py プロジェクト: tammojan/RMS
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
コード例 #4
0
def geocentricToApparentRadiantAndVelocity(ra_g,
                                           dec_g,
                                           vg,
                                           lat,
                                           lon,
                                           elev,
                                           jd,
                                           include_rotation=True):
    """ Converts the geocentric into apparent meteor radiant and velocity. The conversion is not perfect
        as the zenith attraction correction should be done after the radiant has been derotated for Earth's
        velocity, but it's precise to about 0.1 deg.
    
    Arguments:
        ra_g: [float] Geocentric right ascension (deg).
        dec_g: [float] Declination (deg).
        vg: [float] Geocentric velocity (m/s).
        lat: [float] State vector latitude (deg)
        lon: [float] State vector longitude (deg).
        ele: [float] State vector elevation (meters).
        jd: [float] Julian date.

    Keyword arguments:
        include_rotation: [bool] Whether the velocity should be corrected for Earth's rotation.
            True by default.

    Return:
        (ra, dec, v_init): Apparent radiant (deg) and velocity (m/s).

    """

    # Compute ECI coordinates of the meteor state vector
    state_vector = geo2Cartesian(lat, lon, elev, jd)

    eci_x, eci_y, eci_z = state_vector

    # Assume that the velocity at infinity corresponds to the initial velocity
    v_init = np.sqrt(vg**2 +
                     (2 * 6.67408 * 5.9722) * 1e13 / vectMag(state_vector))

    # Calculate the geocentric latitude (latitude which considers the Earth as an elipsoid) of the reference
    # trajectory point
    lat_geocentric = np.degrees(
        math.atan2(eci_z, math.sqrt(eci_x**2 + eci_y**2)))

    ### Uncorrect for zenith attraction ###

    # Compute the radiant in the local coordinates
    #eta, rho = raDec2EtaRho(ra_g, dec_g, lat_geocentric, lon, jd)
    azim, elev = raDec2AltAz(ra_g, dec_g, jd, lat_geocentric, lon)

    # Compute the zenith angle
    eta = np.radians(90.0 - elev)

    # Numerically correct for zenith attraction
    diff = 10e-5
    zc = eta
    while diff > 10e-6:

        # Update the zenith distance
        zc -= diff

        # Calculate the zenith attraction correction
        delta_zc = 2 * math.atan(
            (v_init - vg) * math.tan(zc / 2.0) / (v_init + vg))
        diff = zc + delta_zc - eta

    # Compute the uncorrected geocentric radiant for zenith attraction
    ra, dec = altAz2RADec(azim, 90.0 - np.degrees(zc), jd, lat_geocentric, lon)

    ### ###

    # Apply the rotation correction
    if include_rotation:

        # Calculate the velocity of the Earth rotation at the position of the reference trajectory point (m/s)
        v_e = 2 * math.pi * vectMag(state_vector) * math.cos(
            np.radians(lat_geocentric)) / 86164.09053

        # Calculate the equatorial coordinates of east from the reference position on the trajectory
        azimuth_east = 90.0
        altitude_east = 0
        ra_east, dec_east = altAz2RADec(azimuth_east, altitude_east, jd, lat,
                                        lon)

        # Compute the radiant vector in ECI coordinates of the apparent radiant
        v_ref_vect = v_init * np.array(raDec2Vector(ra, dec))

        v_ref_nocorr = np.zeros(3)

        # Calculate the derotated reference velocity vector/radiant
        v_ref_nocorr[0] = v_ref_vect[0] + v_e * np.cos(np.radians(ra_east))
        v_ref_nocorr[1] = v_ref_vect[1] + v_e * np.sin(np.radians(ra_east))
        v_ref_nocorr[2] = v_ref_vect[2]

        # Compute the radiant without Earth's rotation included
        ra_norot, dec_norot = vector2RaDec(vectNorm(v_ref_nocorr))
        v_init_norot = vectMag(v_ref_nocorr)

        ra = ra_norot
        dec = dec_norot
        v_init = v_init_norot

    return ra, dec, v_init
コード例 #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
コード例 #6
0
def showerAssociation(config, ftpdetectinfo_list, shower_code=None, show_plot=False, save_plot=False, \
    plot_activity=False):
    """ Do single station shower association based on radiant direction and height. 
    
    Arguments:
        config: [Config instance]
        ftpdetectinfo_list: [list] A list of paths to FTPdetectinfo files.

    Keyword arguments:
        shower_code: [str] Only use this one shower for association (e.g. ETA, PER, SDA). None by default,
            in which case all active showers will be associated.
        show_plot: [bool] Show the plot on the screen. False by default.
        save_plot: [bool] Save the plot in the folder with FTPdetectinfos. False by default.
        plot_activity: [bool] Whether to plot the shower activity plot of not. False by default.

    Return:
        associations, shower_counts: [tuple]
            - associations: [dict] A dictionary where the FF name and the meteor ordinal number on the FF
                file are keys, and the associated Shower object are values.
            - shower_counts: [list] A list of shower code and shower count pairs.
    """

    # Load the list of meteor showers
    shower_list = loadShowers(config.shower_path, config.shower_file_name)

    # Load FTPdetectinfos
    meteor_data = []
    for ftpdetectinfo_path in ftpdetectinfo_list:

        if not os.path.isfile(ftpdetectinfo_path):
            print('No such file:', ftpdetectinfo_path)
            continue

        meteor_data += readFTPdetectinfo(*os.path.split(ftpdetectinfo_path))

    if not len(meteor_data):
        return {}, []

    # Dictionary which holds FF names as keys and meteor measurements + associated showers as values
    associations = {}

    for meteor in meteor_data:

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

        # Skip very short meteors
        if len(meteor_meas) < 4:
            continue

        # Check if the data is calibrated
        if not meteor_meas[0][0]:
            print(
                'Data is not calibrated! Meteors cannot be associated to showers!'
            )
            break

        # Init container for meteor observation
        meteor_obj = MeteorSingleStation(cam_code, config.latitude,
                                         config.longitude, ff_name)

        # Infill the meteor structure
        for entry in meteor_meas:

            calib_status, frame_n, x, y, ra, dec, azim, elev, inten, mag = entry

            # Compute the Julian data of every point
            jd = datetime2JD(
                filenameToDatetime(ff_name) +
                datetime.timedelta(seconds=float(frame_n) / fps))

            meteor_obj.addPoint(jd, ra, dec, mag)

        # Fit the great circle and compute the geometrical parameters
        meteor_obj.fitGC()

        # Skip all meteors with beginning heights below 15 deg
        if meteor_obj.beg_alt < 15:
            continue

        # Go through all showers in the list and find the best match
        best_match_shower = None
        best_match_dist = np.inf
        for shower_entry in shower_list:

            # Extract shower parameters
            shower = Shower(shower_entry)

            # If the shower code was given, only check this one shower
            if shower_code is not None:
                if shower.name.lower() != shower_code.lower():
                    continue

            ### Solar longitude filter

            # If the shower doesn't have a stated beginning or end, check if the meteor is within a preset
            # threshold solar longitude difference
            if np.any(np.isnan([shower.lasun_beg, shower.lasun_end])):

                shower.lasun_beg = (shower.lasun_max -
                                    config.shower_lasun_threshold) % 360
                shower.lasun_end = (shower.lasun_max +
                                    config.shower_lasun_threshold) % 360

            # Filter out all showers which are not active
            if not isAngleBetween(np.radians(shower.lasun_beg),
                                  np.radians(meteor_obj.lasun),
                                  np.radians(shower.lasun_end)):

                continue

            ### ###

            ### Radiant filter ###

            # Assume a fixed meteor height for an approximate apparent radiant
            meteor_fixed_ht = 100000  # 100 km
            shower.computeApparentRadiant(config.latitude, config.longitude, meteor_obj.jdt_ref, \
                meteor_fixed_ht=meteor_fixed_ht)

            # Compute the angle between the meteor radiant and the great circle normal
            radiant_separation = meteor_obj.angularSeparationFromGC(
                shower.ra, shower.dec)

            # Make sure the meteor is within the radiant distance threshold
            if radiant_separation > config.shower_max_radiant_separation:
                continue

            # Compute angle between the meteor's beginning and end, and the shower radiant
            shower.radiant_vector = vectNorm(
                raDec2Vector(shower.ra, shower.dec))
            begin_separation = np.degrees(angularSeparationVect(shower.radiant_vector, \
                meteor_obj.meteor_begin_cartesian))
            end_separation = np.degrees(angularSeparationVect(shower.radiant_vector, \
                meteor_obj.meteor_end_cartesian))

            # Make sure the beginning of the meteor is closer to the radiant than it's end
            if begin_separation > end_separation:
                continue

            ### ###

            ### Height filter ###

            # Estimate the limiting meteor height from the velocity (meters)
            filter_beg_ht = heightModel(shower.v_init, ht_type='beg')
            filter_end_ht = heightModel(shower.v_init, ht_type='end')

            ### Estimate the meteor beginning height with +/- 1 frame, otherwise some short meteor may get
            ###   rejected

            meteor_obj_orig = copy.deepcopy(meteor_obj)

            # Shorter
            meteor_obj_m1 = copy.deepcopy(meteor_obj_orig)
            meteor_obj_m1.duration -= 1.0 / config.fps
            meteor_beg_ht_m1 = estimateMeteorHeight(config, meteor_obj_m1,
                                                    shower)

            # Nominal
            meteor_beg_ht = estimateMeteorHeight(config, meteor_obj_orig,
                                                 shower)

            # Longer
            meteor_obj_p1 = copy.deepcopy(meteor_obj_orig)
            meteor_obj_p1.duration += 1.0 / config.fps
            meteor_beg_ht_p1 = estimateMeteorHeight(config, meteor_obj_p1,
                                                    shower)

            meteor_obj = meteor_obj_orig

            ### ###

            # If all heights (even those with +/- 1 frame) are outside the height range, reject the meteor
            if ((meteor_beg_ht_p1 < filter_end_ht) or (meteor_beg_ht_p1 > filter_beg_ht)) and \
                ((meteor_beg_ht    < filter_end_ht) or (meteor_beg_ht    > filter_beg_ht)) and \
                ((meteor_beg_ht_m1 < filter_end_ht) or (meteor_beg_ht_m1 > filter_beg_ht)):

                continue

            ### ###

            # Compute the radiant elevation above the horizon
            shower.azim, shower.elev = raDec2AltAz(shower.ra, shower.dec, meteor_obj.jdt_ref, \
                config.latitude, config.longitude)

            # Take the shower that's closest to the great circle if there are multiple candidates
            if radiant_separation < best_match_dist:
                best_match_dist = radiant_separation
                best_match_shower = copy.deepcopy(shower)

        # If a shower is given and the match is not this shower, skip adding the meteor to the list
        # If no specific shower is give for association, add all meteors
        if ((shower_code is not None) and
            (best_match_shower is not None)) or (shower_code is None):

            # Store the associated shower
            associations[(ff_name,
                          meteor_No)] = [meteor_obj, best_match_shower]

    # Find shower frequency and sort by count
    shower_name_list_temp = []
    shower_list_temp = []
    for key in associations:
        _, shower = associations[key]

        if shower is None:
            shower_name = '...'
        else:
            shower_name = shower.name

        shower_name_list_temp.append(shower_name)
        shower_list_temp.append(shower)

    _, unique_showers_indices = np.unique(shower_name_list_temp,
                                          return_index=True)
    unique_shower_names = np.array(
        shower_name_list_temp)[unique_showers_indices]
    unique_showers = np.array(shower_list_temp)[unique_showers_indices]
    shower_counts = [[shower_obj, shower_name_list_temp.count(shower_name)] for shower_obj, \
        shower_name in zip(unique_showers, unique_shower_names)]
    shower_counts = sorted(shower_counts, key=lambda x: x[1], reverse=True)

    # Create a plot of showers
    if show_plot or save_plot:
        # Generate consistent colours
        colors_by_name = makeShowerColors(shower_list)

        def get_shower_color(shower):
            try:
                return colors_by_name[shower.name] if shower else "0.4"
            except KeyError:
                return 'gray'

        # Init the figure
        plt.figure()

        # Init subplots depending on if the activity plot is done as well
        if plot_activity:
            gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])
            ax_allsky = plt.subplot(gs[0], facecolor='black')
            ax_activity = plt.subplot(gs[1], facecolor='black')
        else:
            ax_allsky = plt.subplot(111, facecolor='black')

        # Init the all-sky plot
        allsky_plot = AllSkyPlot(ax_handle=ax_allsky)

        # Plot all meteors
        for key in associations:

            meteor_obj, shower = associations[key]

            ### Plot the observed meteor points ###
            color = get_shower_color(shower)
            allsky_plot.plot(meteor_obj.ra_array,
                             meteor_obj.dec_array,
                             color=color,
                             linewidth=1,
                             zorder=4)

            # Plot the peak of shower meteors a different color
            peak_color = 'blue'
            if shower is not None:
                peak_color = 'tomato'

            allsky_plot.scatter(meteor_obj.ra_array[-1], meteor_obj.dec_array[-1], c=peak_color, marker='+', \
                s=5, zorder=5)

            ### ###

            ### Plot fitted great circle points ###

            # Find the GC phase angle of the beginning of the meteor
            gc_beg_phase = meteor_obj.findGCPhase(
                meteor_obj.ra_array[0], meteor_obj.dec_array[0])[0] % 360

            # If the meteor belongs to a shower, find the GC phase which ends at the shower
            if shower is not None:
                gc_end_phase = meteor_obj.findGCPhase(shower.ra,
                                                      shower.dec)[0] % 360

                # Fix 0/360 wrap
                if abs(gc_end_phase - gc_beg_phase) > 180:
                    if gc_end_phase > gc_beg_phase:
                        gc_end_phase -= 360
                    else:
                        gc_beg_phase -= 360

                gc_alpha = 1.0

            else:

                # If it's a sporadic, find the direction to which the meteor should extend
                gc_end_phase = meteor_obj.findGCPhase(meteor_obj.ra_array[-1], \
                    meteor_obj.dec_array[-1])[0]%360

                # Find the correct direction
                if (gc_beg_phase - gc_end_phase) % 360 > (gc_end_phase -
                                                          gc_beg_phase) % 360:
                    gc_end_phase = gc_beg_phase - 170

                else:
                    gc_end_phase = gc_beg_phase + 170

                gc_alpha = 0.7

            # Store great circle beginning and end phase
            meteor_obj.gc_beg_phase = gc_beg_phase
            meteor_obj.gc_end_phase = gc_end_phase

            # Get phases 180 deg before the meteor
            phase_angles = np.linspace(gc_end_phase, gc_beg_phase, 100) % 360

            # Compute RA/Dec of points on the great circle
            ra_gc, dec_gc = meteor_obj.sampleGC(phase_angles)

            # Cull all points below the horizon
            azim_gc, elev_gc = raDec2AltAz(ra_gc, dec_gc, meteor_obj.jdt_ref, config.latitude, \
                config.longitude)
            temp_arr = np.c_[ra_gc, dec_gc]
            temp_arr = temp_arr[elev_gc > 0]
            ra_gc, dec_gc = temp_arr.T

            # Plot the great circle fitted on the radiant
            gc_color = get_shower_color(shower)
            allsky_plot.plot(ra_gc,
                             dec_gc,
                             linestyle='dotted',
                             color=gc_color,
                             alpha=gc_alpha,
                             linewidth=1)

            # Plot the point closest to the shower radiant
            if shower is not None:
                allsky_plot.plot(ra_gc[0],
                                 dec_gc[0],
                                 color='r',
                                 marker='+',
                                 ms=5,
                                 mew=1)

                # Store shower radiant point
                meteor_obj.radiant_ra = ra_gc[0]
                meteor_obj.radiant_dec = dec_gc[0]

            ### ###

        ### Plot all showers ###

        # Find unique showers and their apparent radiants computed at highest radiant elevation
        # (otherwise the apparent radiants can be quite off)
        shower_dict = {}
        for key in associations:
            meteor_obj, shower = associations[key]

            if shower is None:
                continue

            # If the shower name is in dict, find the shower with the highest radiant elevation
            if shower.name in shower_dict:
                if shower.elev > shower_dict[shower.name].elev:
                    shower_dict[shower.name] = shower

            else:
                shower_dict[shower.name] = shower

        # Plot the location of shower radiants
        for shower_name in shower_dict:

            shower = shower_dict[shower_name]

            heading_arr = np.linspace(0, 360, 50)

            # Compute coordinates on a circle around the given RA, Dec
            ra_circle, dec_circle = sphericalPointFromHeadingAndDistance(shower.ra, shower.dec, \
                heading_arr, config.shower_max_radiant_separation)

            # Plot the shower circle
            allsky_plot.plot(ra_circle,
                             dec_circle,
                             color=colors_by_name[shower_name])

            # Plot the shower name
            x_text, y_text = allsky_plot.raDec2XY(shower.ra, shower.dec)
            allsky_plot.ax.text(x_text, y_text, shower.name, color='w', size=8, va='center', \
                ha='center', zorder=6)

        # Plot station name and solar longiutde range
        allsky_plot.ax.text(-180,
                            89,
                            "{:s}".format(cam_code),
                            color='w',
                            family='monospace')

        # Get a list of JDs of meteors
        jd_list = [associations[key][0].jdt_ref for key in associations]

        if len(jd_list):

            # Get the range of solar longitudes
            jd_min = min(jd_list)
            sol_min = np.degrees(jd2SolLonSteyaert(jd_min))
            jd_max = max(jd_list)
            sol_max = np.degrees(jd2SolLonSteyaert(jd_max))

            # Plot the date and solar longitude range
            date_sol_beg = u"Beg: {:s} (sol = {:.2f}\u00b0)".format(
                jd2Date(jd_min, dt_obj=True).strftime("%Y%m%d %H:%M:%S"),
                sol_min)
            date_sol_end = u"End: {:s} (sol = {:.2f}\u00b0)".format(
                jd2Date(jd_max, dt_obj=True).strftime("%Y%m%d %H:%M:%S"),
                sol_max)

            allsky_plot.ax.text(-180,
                                85,
                                date_sol_beg,
                                color='w',
                                family='monospace')
            allsky_plot.ax.text(-180,
                                81,
                                date_sol_end,
                                color='w',
                                family='monospace')
            allsky_plot.ax.text(-180,
                                77,
                                "-" * len(date_sol_end),
                                color='w',
                                family='monospace')

            # Plot shower counts
            for i, (shower, count) in enumerate(shower_counts):

                if shower is not None:
                    shower_name = shower.name
                else:
                    shower_name = "..."

                allsky_plot.ax.text(-180, 73 - i*4, "{:s}: {:d}".format(shower_name, count), color='w', \
                    family='monospace')

            ### ###

            # Plot yearly meteor shower activity
            if plot_activity:

                # Plot the activity diagram
                generateActivityDiagram(config, shower_list, ax_handle=ax_activity, \
                    sol_marker=[sol_min, sol_max], colors=colors_by_name)

        # Save plot and text file
        if save_plot:

            dir_path, ftpdetectinfo_name = os.path.split(ftpdetectinfo_path)
            ftpdetectinfo_base_name = ftpdetectinfo_name.replace(
                'FTPdetectinfo_', '').replace('.txt', '')
            plot_name = ftpdetectinfo_base_name + '_radiants.png'

            # Increase figure size
            allsky_plot.fig.set_size_inches(18, 9, forward=True)

            allsky_plot.beautify()

            plt.savefig(os.path.join(dir_path, plot_name),
                        dpi=100,
                        facecolor='k')

            # Save the text file with shower info
            if len(jd_list):
                with open(
                        os.path.join(dir_path, ftpdetectinfo_base_name +
                                     "_radiants.txt"), 'w') as f:

                    # Print station code
                    f.write("# RMS single station association\n")
                    f.write("# \n")
                    f.write("# Station: {:s}\n".format(cam_code))

                    # Print date range
                    f.write(
                        "#                    Beg          |            End            \n"
                    )
                    f.write(
                        "#      -----------------------------------------------------\n"
                    )
                    f.write("# Date | {:24s} | {:24s} \n".format(jd2Date(jd_min, \
                        dt_obj=True).strftime("%Y%m%d %H:%M:%S.%f"), jd2Date(jd_max, \
                        dt_obj=True).strftime("%Y%m%d %H:%M:%S.%f")))
                    f.write("# Sol  | {:>24.2f} | {:>24.2f} \n".format(
                        sol_min, sol_max))

                    # Write shower counts
                    f.write("# \n")
                    f.write("# Shower counts:\n")
                    f.write("# --------------\n")
                    f.write("# Code, Count, IAU link\n")

                    for i, (shower, count) in enumerate(shower_counts):

                        if shower is not None:
                            shower_name = shower.name

                            # Create link to the IAU database of showers
                            iau_link = "https://www.ta3.sk/IAUC22DB/MDC2007/Roje/pojedynczy_obiekt.php?kodstrumienia={:05d}".format(
                                shower.iau_code)

                        else:
                            shower_name = "..."
                            iau_link = "None"

                        f.write("# {:>4s}, {:>5d}, {:s}\n".format(
                            shower_name, count, iau_link))

                    f.write("# \n")
                    f.write("# Meteor parameters:\n")
                    f.write("# ------------------\n")
                    f.write(
                        "#          Date And Time,      Beg Julian date,     La Sun, Shower, RA beg, Dec beg, RA end, Dec end, RA rad, Dec rad, GC theta0,  GC phi0, GC beg phase, GC end phase,  Mag\n"
                    )

                    # Create a sorted list of meteor associations by time
                    associations_list = [
                        associations[key] for key in associations
                    ]
                    associations_list = sorted(associations_list,
                                               key=lambda x: x[0].jdt_ref)

                    # Write out meteor parameters
                    for meteor_obj, shower in associations_list:

                        # Find peak magnitude
                        if np.any(meteor_obj.mag_array):
                            peak_mag = "{:+.1f}".format(
                                np.min(meteor_obj.mag_array))

                        else:
                            peak_mag = "None"

                        if shower is not None:

                            f.write("{:24s}, {:20.12f}, {:>10.6f}, {:>6s}, {:6.2f}, {:+7.2f}, {:6.2f}, {:+7.2f}, {:6.2f}, {:+7.2f}, {:9.3f}, {:8.3f}, {:12.3f}, {:12.3f}, {:4s}\n".format(jd2Date(meteor_obj.jdt_ref, dt_obj=True).strftime("%Y%m%d %H:%M:%S.%f"), \
                                meteor_obj.jdt_ref, meteor_obj.lasun, shower.name, \
                                meteor_obj.ra_array[0]%360, meteor_obj.dec_array[0], \
                                meteor_obj.ra_array[-1]%360, meteor_obj.dec_array[-1], \
                                meteor_obj.radiant_ra%360, meteor_obj.radiant_dec, \
                                np.degrees(meteor_obj.theta0), np.degrees(meteor_obj.phi0), \
                                meteor_obj.gc_beg_phase, meteor_obj.gc_end_phase, peak_mag))

                        else:
                            f.write("{:24s}, {:20.12f}, {:>10.6f}, {:>6s}, {:6.2f}, {:+7.2f}, {:6.2f}, {:+7.2f}, {:>6s}, {:>7s}, {:9.3f}, {:8.3f}, {:12.3f}, {:12.3f}, {:4s}\n".format(jd2Date(meteor_obj.jdt_ref, dt_obj=True).strftime("%Y%m%d %H:%M:%S.%f"), \
                                meteor_obj.jdt_ref, meteor_obj.lasun, '...', meteor_obj.ra_array[0]%360, \
                                meteor_obj.dec_array[0], meteor_obj.ra_array[-1]%360, \
                                meteor_obj.dec_array[-1], "None", "None", np.degrees(meteor_obj.theta0), \
                                np.degrees(meteor_obj.phi0), meteor_obj.gc_beg_phase, \
                                meteor_obj.gc_end_phase, peak_mag))

        if show_plot:
            allsky_plot.show()

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

    return associations, shower_counts
コード例 #7
0
def estimateMeteorHeight(config, meteor_obj, shower):
    """ Estimate the height of a meteor from single station give a candidate shower. 

    Arguments:
        config: [Config instance]
        meteor_obj: [MeteorSingleStation instance]
        shower: [Shower instance]

    Return:
        ht: [float] Estimated height in meters.
    """

    ### Compute all needed values in alt/az coordinates ###

    # Compute beginning point vector in alt/az
    beg_ra, beg_dec = vector2RaDec(meteor_obj.beg_vect)
    beg_azim, beg_alt = raDec2AltAz(beg_ra, beg_dec, meteor_obj.jdt_ref,
                                    meteor_obj.lat, meteor_obj.lon)
    beg_vect_horiz = raDec2Vector(beg_azim, beg_alt)

    # Compute end point vector in alt/az
    end_ra, end_dec = vector2RaDec(meteor_obj.end_vect)
    end_azim, end_alt = raDec2AltAz(end_ra, end_dec, meteor_obj.jdt_ref,
                                    meteor_obj.lat, meteor_obj.lon)
    end_vect_horiz = raDec2Vector(end_azim, end_alt)

    # Compute radiant vector in alt/az
    radiant_azim, radiant_alt = raDec2AltAz(shower.ra, shower.dec, meteor_obj.jdt_ref, meteor_obj.lat, \
        meteor_obj.lon)
    radiant_vector_horiz = raDec2Vector(radiant_azim, radiant_alt)

    # Reject the pairing if the radiant is below the horizon
    if radiant_alt < 0:
        return -1

    # Get distance from Earth's centre to the position given by geographical coordinates for the
    #   observer's latitude
    earth_radius = EARTH.EQUATORIAL_RADIUS / np.sqrt(
        1.0 - (EARTH.E**2) * np.sin(np.radians(config.latitude))**2)

    # Compute the distance from Earth's centre to the station (including the sea level height of the station)
    re_dist = earth_radius + config.elevation

    ### ###

    # Compute the distance the meteor traversed during its duration (meters)
    dist = shower.v_init * meteor_obj.duration

    # Compute the angle between the begin and the end point of the meteor (rad)
    ang_beg_end = np.arccos(
        np.dot(vectNorm(beg_vect_horiz), vectNorm(end_vect_horiz)))

    # Compute the angle between the radiant vector and the begin point (rad)
    ang_beg_rad = np.arccos(
        np.dot(vectNorm(radiant_vector_horiz), -vectNorm(beg_vect_horiz)))

    # Compute the distance from the station to the begin point (meters)
    dist_beg = dist * np.sin(ang_beg_rad) / np.sin(ang_beg_end)

    # Compute the height using the law of cosines
    ht = np.sqrt(dist_beg**2 + re_dist**2 - 2 * dist_beg * re_dist *
                 np.cos(np.radians(90 + meteor_obj.beg_alt)))
    ht -= earth_radius
    ht = abs(ht)

    return ht
コード例 #8
0
def estimateMeteorHeight(meteor_obj, shower):
    """ Estimate the height of a meteor from single station give a candidate shower. 

    Arguments:
        meteor_obj: [MeteorSingleStation instance]
        shower: [Shower instance]

    Return:
        ht: [float] Estimated height in meters.
    """

    ### Compute all needed values in alt/az coordinates ###

    # Compute beginning point vector in alt/az
    beg_ra, beg_dec = vector2RaDec(meteor_obj.beg_vect)
    beg_azim, beg_alt = raDec2AltAz(beg_ra, beg_dec, meteor_obj.jdt_ref,
                                    meteor_obj.lat, meteor_obj.lon)
    beg_vect_horiz = raDec2Vector(beg_azim, beg_alt)

    # Compute end point vector in alt/az
    end_ra, end_dec = vector2RaDec(meteor_obj.end_vect)
    end_azim, end_alt = raDec2AltAz(end_ra, end_dec, meteor_obj.jdt_ref,
                                    meteor_obj.lat, meteor_obj.lon)
    end_vect_horiz = raDec2Vector(end_azim, end_alt)

    # Compute normal vector in alt/az
    normal_azim, normal_alt = raDec2AltAz(meteor_obj.normal_ra, meteor_obj.normal_dec, meteor_obj.jdt_ref, \
        meteor_obj.lat, meteor_obj.lon)
    normal_horiz = raDec2Vector(normal_azim, normal_alt)

    # Compute radiant vector in alt/az
    radiant_azim, radiant_alt = raDec2AltAz(shower.ra, shower.dec, meteor_obj.jdt_ref, meteor_obj.lat, \
        meteor_obj.lon)
    radiant_vector_horiz = raDec2Vector(radiant_azim, radiant_alt)

    # Reject the pairing if the radiant is below the horizon
    if radiant_alt < 0:
        return -1

    ### ###

    # Compute cartesian coordinates of the pointing at the beginning of the meteor
    pt = vectNorm(beg_vect_horiz)

    # Compute reference vector perpendicular to the plane normal and the radiant
    vec = vectNorm(np.cross(normal_horiz, radiant_vector_horiz))

    # Compute angles between the reference vector and the pointing
    dot_vb = np.dot(vec, beg_vect_horiz)
    dot_ve = np.dot(vec, end_vect_horiz)
    dot_vp = np.dot(vec, pt)

    # Compute distance to the radiant intersection line
    r_mag = 1.0 / (dot_vb**2)
    r_mag += 1.0 / (dot_ve**2)
    r_mag += -2 * np.cos(meteor_obj.ang_be) / (dot_vb * dot_ve)
    r_mag = np.sqrt(r_mag)
    r_mag = shower.v_init * meteor_obj.duration / r_mag
    pt_mag = r_mag / dot_vp

    # Compute the height
    ht  = pt_mag**2 + EARTH.EQUATORIAL_RADIUS**2 \
        - 2*pt_mag*EARTH.EQUATORIAL_RADIUS*np.cos(np.radians(90 - meteor_obj.beg_alt))
    ht = np.sqrt(ht)
    ht -= EARTH.EQUATORIAL_RADIUS
    ht = abs(ht)

    return ht