Ejemplo n.º 1
0
def solveTrajectoryMet(met, solver='original', velmodel=3, **kwargs):
        """ Runs the trajectory solver on points of the given type. 

        Keyword arguments:
            solver: [str] Trajectory solver to use:
                - 'original' (default) - "in-house" trajectory solver implemented in Python
                - 'gural' - Pete Gural's PSO solver
            velmodel: [int] Velocity propagation model for the Gural solver
                0 = constant   v(t) = vinf
                1 = linear     v(t) = vinf - |acc1| * t
                2 = quadratic  v(t) = vinf - |acc1| * t + acc2 * t^2
                3 = exponent   v(t) = vinf - |acc1| * |acc2| * exp( |acc2| * t ) (default)
        """


        # Check that there are at least two stations present
        if len(met.sites) < 2:
            print('ERROR! The .met file does not contain multistation data!')

            return False



        time_data = {}
        theta_data = {}
        phi_data = {}
        mag_data = {}

        # Go through all sites
        for site in met.sites:

            time_picks = []
            theta_picks = []
            phi_picks = []
            mag_picks = []

            # Go through all picks
            for pick in met.picks_objs[site]:

                # Add the pick to the picks list
                theta_picks.append(pick.theta)
                phi_picks.append(pick.phi)

                # Add the time of the pick to a list
                time_picks.append(pick.unix_time)

                # Add magnitude
                mag_picks.append(pick.mag)


            # Add the picks to the list of picks of both sites
            time_data[site] = np.array(time_picks).ravel()
            theta_data[site] = np.array(theta_picks).ravel()
            phi_data[site] = np.array(phi_picks).ravel()
            mag_data[site] = np.array(mag_picks).ravel()


        # Take the earliest time of all sites as the reference time
        ref_unix_time = min([time_data[key][0] for key in time_data.keys()])

        # Normalize all times with respect to the reference times
        for site in met.sites:
            time_data[site] = time_data[site] - ref_unix_time


        # Convert the reference Unix time to Julian date
        ts = int(ref_unix_time)
        tu = (ref_unix_time - ts)*1e6
        ref_JD = unixTime2JD(ts, tu)


        if solver == 'original':

            # Init the new trajectory solver object
            traj = Trajectory(ref_JD, output_dir=met.dir_path, **kwargs)

        elif solver == 'gural':

            # Select extra keyword arguments that are present only for the gural solver
            gural_keys = ['max_toffset', 'nummonte', 'meastype', 'verbose', 'show_plots']
            gural_kwargs = {key: kwargs[key] for key in gural_keys if key in kwargs}

            # Init the new Gural trajectory solver object
            traj = GuralTrajectory(len(met.sites), ref_JD, velmodel, verbose=1, output_dir=met.dir_path, 
                **gural_kwargs)


        # Infill trajectories from each site
        for site in met.sites:

            theta_picks = theta_data[site]
            phi_picks = phi_data[site]
            time_picks = time_data[site]
            mag_picks = mag_data[site]

            if not np.any(mag_picks):
                mag_picks = None

            lat = met.lat[site]
            lon = met.lon[site]
            elev = met.elev[site]


            # MC solver
            if solver == 'original':

                traj.infillTrajectory(phi_picks, theta_picks, time_picks, lat, lon, elev, \
                    station_id=str(site), magnitudes=mag_picks)
            
            # Gural solver
            else:
                traj.infillTrajectory(phi_picks, theta_picks, time_picks, lat, lon, elev)


        print('Filling done!')


        # # Dump measurements to a file
        # traj.dumpMeasurements(self.met.dir_path.split(os.sep)[-1] + '_dump.txt')


        # Solve the trajectory
        traj = traj.run()

        return traj
Ejemplo n.º 2
0
def readEvFile(dir_path, file_name):
    """ Given the path of the UWO-style event file, read it into the StationData object. 

    Arguments:
        dir_path: [str] Path to the directory with the ev file.
        file_name: [str] Name of the ev file.

    Return:
        [StationData instance]
    """


    with open(os.path.join(dir_path, file_name)) as f:


        jdt_ref = None
        lat = None
        lon = None
        elev = None
        site = None
        stream = None


        time_data = []
        theta_data = []
        phi_data = []
        mag_data = []

        for line in f:

            if not line:
                continue

            # Read metadata
            if line.startswith("#"):

                entry = line[1:].split()

                if not entry:
                    continue

                # Read reference time
                if entry[0] == "unix":
                    ts, tu = list(map(int, entry[2].split(".")))
                    jdt_ref = unixTime2JD(ts, tu)

                elif entry[0] == "site":
                    site = entry[2]

                elif entry[0] == "latlon":
                    lat, lon, elev = list(map(float, entry[2:5]))

                elif entry[0] == "stream":
                    stream = entry[2]


            # Read data
            else:

                line = line.split()

                time_data.append(float(line[1]))
                theta_data.append(float(line[6]))
                phi_data.append(float(line[7]))

                # Check if the magnitude is NaN and set None instead
                mag = line[9]
                if 'nan' in mag:
                    mag = None
                else:
                    mag = float(mag)

                mag_data.append(mag)


        # If there is a NaN in the magnitude data, interpolate it
        if None in mag_data:

            # Get a list of clean data
            mag_data_clean = [entry for entry in enumerate(mag_data) if entry[1] is not None]
            clean_indices, clean_mags = np.array(mag_data_clean).T

            # If there aren't at least 2 good points, return None
            if len(clean_indices) < 2:
                return None

            # Interpolate in linear units
            intens_interpol = scipy.interpolate.PchipInterpolator(clean_indices, 10**(clean_mags/(-2.5)))


            # Interpolate missing magnitudes
            for i, mag in enumerate(mag_data):
                if mag is None:
                    mag_data[i] = -2.5*np.log10(intens_interpol(i))

            # none_index = mag_data.index(None)

            # # If the first magnitude is NaN, take the magnitude of the second point
            # if none_index == 0:
            #     mag_data[none_index] = mag_data[none_index + 1]

            # # If the last magnitude is NaN, use the magnitude of the previous point
            # elif none_index == (len(mag_data) - 1):
            #     mag_data[none_index] = mag_data[none_index - 1]

            # # If the magnitude is in between, interpolate it
            # else:

            #     mag_prev = float(mag_data[none_index - 1])
            #     mag_next = float(mag_data[none_index + 1])

            #     # Interpolate in linear units
            #     intens_prev = 10**(mag_prev/(-2.5))
            #     intens_next = 10**(mag_next/(-2.5))
            #     intens_interpol = (intens_prev + intens_next)/2
            #     mag_interpol = -2.5*np.log10(intens_interpol)

            #     mag_data[none_index] = mag_interpol


        

        # Change the relative time to 0 and update the reference Julian date
        time_data = np.array(time_data)
        jdt_ref += time_data[0]/86400
        time_data -= time_data[0]

        # Init the StationData object
        sd = StationData(jdt_ref, np.radians(lat), np.radians(lon), elev, site + stream)
        sd.time_data = np.array(time_data)
        #sd.time_data -= sd.time_data[0] # Normalize to 0
        sd.theta_data = np.radians(theta_data)
        sd.phi_data = np.radians(phi_data)
        sd.mag_data = np.array(mag_data)


        return sd
Ejemplo n.º 3
0
def markFragments(out_dir, vid, met, site_id, traj=None, crop=None):
    """ Mark fragments on .vid file frames and save them as images. If the trajectory structure is given,
        the approximate height at every frame will be plotted on the image as well.
    
    Arguments:
        out_dir: [str] Path to the directory where the images will be saved.
        vid: [VidStruct object] vid object containing loaded video frames.
        met: [MetStruct object] met object containing picks.
        site_id: [str] ID of the site used for loading proper picks from the met object.

    Keyword arguments:
        traj: [Trajectory object] Optional trajectory object from which the height of the meteor at evey frame
            will be estimated and plotted on the image. None by default (no height will be plotted on the
            image).
        crop: [list] A list of Xmin, Xmax, Ymin, Ymax crop window.

    Return:
        None
    """


    # Make the output directory
    mkdirP(out_dir)

    # Extract site picks
    picks = np.array(met.picks[site_id])

    # Find unique fragments
    fragments = np.unique(picks[:, 1])


    # Generate a unique color for every fragment
    colors = plt.cm.rainbow(np.linspace(0, 1, len(fragments)))

    # Create a dictionary for every fragment-color pair
    colors_frags = {frag: color for frag, color in zip(fragments, colors)}



    ### Height fit ###

    height_interp = None

    # If the trajectory was given, do interpolation on time vs. height
    if traj is not None:


        jd_data = []
        height_data = []

        # Take all observed points on the trajectory
        for obs in traj.observations:

            for jd, height in zip(obs.JD_data, obs.model_ht):
                jd_data.append(jd)
                height_data.append(height)


        jd_data = np.array(jd_data)
        height_data = np.array(height_data)

        # Sort the points by Julian date
        jd_ht_data = np.c_[jd_data, height_data]
        jd_ht_data = jd_ht_data[np.argsort(jd_ht_data[:, 0])]
        jd_data, height_data = jd_ht_data.T

        # Initerpolate Julian date vs. heights
        height_interp = scipy.interpolate.PchipInterpolator(jd_data, height_data, extrapolate=True)

        # # Plot JD vs. height
        # plt.scatter(jd_data, height_data)

        # jd_plot = np.linspace(min(jd_data), max(jd_data), 1000)
        # plt.plot(jd_plot, height_interp(jd_plot))

        # plt.show()




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


    frag_count = 1
    frag_dict = {}

    # Go through all frames
    for i, fr_vid in enumerate(vid.frames):


        # Determine crop
        if crop is not None:
            xmin, xmax, ymin, ymax = crop

            if xmin == None:
                xmin = 0

            if xmax == None:
                xmax = fr_vid.wid

            if ymin == None:
                ymin = 0

            if ymax == None:
                ymax == fr_vid.ht


        else:
            xmin, ymin = 0, 0
            xmax, ymax = fr_vid.wid, fr_vid.ht



        # LIMIT FRAMES
        if (i < 218) or (i > 512):
            continue


        # CROP IMAGE
        if crop is not None:
            img = fr_vid.img_data[ymin:ymax, xmin:xmax]
        
        else:
            img = fr_vid.img_data


        # Plot the frame
        plt.imshow(img, cmap='gray', vmin=0, vmax=255, interpolation='nearest')


        # Convert the frame time from UNIX timestamp to human readable format
        timestamp = unixTime2Date(fr_vid.ts, fr_vid.tu, dt_obj=True).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] \
            + ' UTC'

        # Calculate the Julian date of every frame
        frame_jd = unixTime2JD(fr_vid.ts, fr_vid.tu)


        # Plot the timestamp
        plt.text(10, ymax - 10, timestamp, ha='left', va='bottom', color='0.7', size=10)


        # If the trajectory was given, plot the meteor height at every frame
        if height_interp is not None:

            # Get the height at the given frame
            frame_height = height_interp(frame_jd)

            height_str = 'Height = {:7.3f} km'.format(frame_height/1000)

            # Plot the height
            plt.text(img.shape[1] - 10, img.shape[0] - 10, height_str, ha='right', va='bottom', color='0.7', size=10)


        # Hide axes
        plt.gca().set_axis_off()




        # Extract picks on this frame
        fr_picks = picks[picks[:, 0] == i]

        # Check if there are any picks on the this frame and plot them by fragment number
        if len(fr_picks):

            # Go through every pick
            for pick in fr_picks:

                # Choose the appropriate colour by fragment
                frag_color = colors_frags[pick[1]]

                # Extract pick coordinates and apply crop
                cx = pick[2] - xmin
                cy = pick[3] - ymin

                # Extract fragment number
                frag = pick[1]

                # Assign this fragment a sequential number if it was not yet plotted
                if not frag in frag_dict:
                    frag_dict[frag] = frag_count
                    frag_count += 1

                
                # Look up the fragment sequential number
                frag_no = frag_dict[frag]

                # Move the markers a bit to the left/right, intermittently for every fragment
                if frag%2 == 0:
                    cx -= 10
                    txt_cx = cx - 5
                    marker = '>'
                    txt_align = 'right'
                else:
                    cx += 10
                    txt_cx = cx + 5
                    marker = '<'
                    txt_align = 'left'


                # Plot the pick
                plt.scatter(cx, cy - 1, c=frag_color, marker=marker, s=5)

                # Plot the fragment number
                plt.text(txt_cx, cy, str(int(frag_no)), horizontalalignment=txt_align, verticalalignment='center', color=frag_color, size=8)
        

        # Set limits
        plt.xlim([0, img.shape[1]])
        plt.ylim([img.shape[0], 0])

        # Save the plot
        extent = plt.gca().get_window_extent().transformed(plt.gcf().dpi_scale_trans.inverted())
        plt.savefig(os.path.join(out_dir, str(i) + '.png'), transparent=True, bbox_inches=extent, pad_inches=0, dpi=300)

        plt.clf()
        #plt.show()


    # IMPORTANT!!! COPY THE OUTPUT OF THIS TO ProjectNarrowPicksToWideTraj!!!!
    # Print the fragment dictionary, where the original fragment IDs are mapped into sequential numbers
    print('FRAG DICT:', frag_dict)
Ejemplo n.º 4
0
    def solveTrajectory(self,
                        pick_type='original',
                        velmodel=3,
                        solver='original',
                        **kwargs):
        """ Runs the trajectory solver on points of the given type. 

        Keyword arguments:
            pick_type: [str] Can be:
                - 'original' (default) original manual picks
                - 'gc' original picks projected onto a great circle
                - 'draw' picks drawn from a probability distribution
            velmodel: [int] Velocity propagation model
                0 = constant   v(t) = vinf
                1 = linear     v(t) = vinf - |acc1| * t
                2 = quadratic  v(t) = vinf - |acc1| * t + acc2 * t^2
                3 = exponent   v(t) = vinf - |acc1| * |acc2| * exp( |acc2| * t ) (default)
            solver: [str] Trajectory solver to use:
                - 'original' (default) - "in-house" trajectory solver implemented in Python
                - 'gural' - Pete Gural's PSO solver
        """

        time_data = {}
        theta_data = {}
        phi_data = {}

        # Go through all sites
        for site in self.met.sites:

            # Extract picks of the given type
            time_picks, theta_picks, phi_picks = self.extractPicks(
                site, pick_type=pick_type)

            # Add the picks to the list of picks of both sites
            time_data[site] = np.array(time_picks).ravel()
            theta_data[site] = np.array(theta_picks).ravel()
            phi_data[site] = np.array(phi_picks).ravel()

        # Take the earliest time of all sites as the reference time
        ref_unix_time = min([time_data[key][0] for key in time_data.keys()])

        # Normalize all times with respect to the reference times
        for site in self.met.sites:
            time_data[site] = time_data[site] - ref_unix_time

        # Convert the reference Unix time to Julian date
        ts = int(ref_unix_time)
        tu = (ref_unix_time - ts) * 1e6
        ref_JD = unixTime2JD(ts, tu)

        if solver == 'original':

            # Init the new trajectory solver object
            traj_solve = Trajectory(ref_JD,
                                    show_plots=True,
                                    output_dir=self.met.dir_path,
                                    **kwargs)

        elif solver == 'gural':

            # Init the new Gural trajectory solver object
            traj_solve = GuralTrajectory(len(self.met.sites),
                                         ref_JD,
                                         velmodel,
                                         verbose=1)

        # Infill trajectories from each site
        for site in self.met.sites:

            theta_picks = theta_data[site]
            phi_picks = phi_data[site]
            time_picks = time_data[site]

            lat = self.met.exact_plates[site].lat
            lon = self.met.exact_plates[site].lon
            elev = self.met.exact_plates[site].elev

            traj_solve.infillTrajectory(phi_picks, theta_picks, time_picks,
                                        lat, lon, elev)

        print('Filling done!')

        # # Dump measurements to a file
        # traj_solve.dumpMeasurements(self.met.dir_path.split(os.sep)[-1] + '_dump.txt')

        # Solve the trajectory
        traj_solve.run()

        return traj_solve
def projectNarrowPicks(dir_path, met, traj, traj_uncert, metal_mags,
                       frag_info):
    """ Projects picks done in the narrow-field to the given trajectory. """

    # Adjust initial velocity
    frag_v_init = traj.v_init + frag_info.v_init_adjust

    # List for computed values to be stored in a file
    computed_values = []

    # Generate the file name prefix from the time (take from trajectory)
    file_name_prefix = traj.file_name

    # List that holds datetimes of fragmentations, used for the light curve plot
    fragmentations_datetime = []

    # Go through picks from all sites
    for site_no in met.picks:

        # Extract site exact plate
        exact = met.exact_plates[site_no]

        # Extract site picks
        picks = np.array(met.picks[site_no])

        # Skip the site if there are no picks
        if not len(picks):
            continue

        print()
        print('Processing site:', site_no)

        # Find unique fragments
        fragments = np.unique(picks[:, 1])

        # If the fragmentation dictionary is empty, generate one
        if frag_info.frag_dict is None:
            frag_info.frag_dict = {
                float(i): i + 1
                for i in range(len(fragments))
            }

        # A list with results of finding the closest point on the trajectory
        cpa_list = []

        # Go thorugh all fragments and calculate the coordinates of the closest points on the trajectory and
        # the line of sight
        for frag in fragments:

            # Take only those picks from current fragment
            frag_picks = picks[picks[:, 1] == frag]

            # Sort by frame
            frag_picks = frag_picks[np.argsort(frag_picks[:, 0])]

            # Extract Unix timestamp
            ts = frag_picks[:, 11]
            tu = frag_picks[:, 12]

            # Extract theta, phi
            theta = np.radians(frag_picks[:, 4])
            phi = np.radians(frag_picks[:, 5])

            # Calculate azimuth +E of N
            azim = (np.pi / 2.0 - phi) % (2 * np.pi)

            # Calculate elevation
            elev = np.pi / 2.0 - theta

            # Calculate Julian date from Unix timestamp
            jd_data = np.array([unixTime2JD(s, u) for s, u in zip(ts, tu)])

            # Convert azim/elev to RA/Dec
            ra, dec = altAz2RADec_vect(azim, elev, jd_data, exact.lat,
                                       exact.lon)

            # Convert RA/Dec to ECI direction vector
            x_eci, y_eci, z_eci = raDec2ECI(ra, dec)

            # Convert station geocoords to ECEF coordinates
            x_stat_vect, y_stat_vect, z_stat_vect = geo2Cartesian_vect(exact.lat, exact.lon, exact.elev, \
                jd_data)

            # Find closest points of aproach for all measurements
            for jd, x, y, z, x_stat, y_stat, z_stat in np.c_[jd_data, x_eci, y_eci, z_eci, x_stat_vect, \
                y_stat_vect, z_stat_vect]:

                # Find the closest point of approach of every narrow LoS to the wide trajectory
                obs_cpa, rad_cpa, d = findClosestPoints(np.array([x_stat, y_stat, z_stat]), \
                    np.array([x, y, z]), traj.state_vect_mini, traj.radiant_eci_mini)

                # Calculate the height of each fragment for the given time
                rad_lat, rad_lon, height = cartesian2Geo(jd, *rad_cpa)

                cpa_list.append(
                    [frag, jd, obs_cpa, rad_cpa, d, rad_lat, rad_lon, height])

        # Find the coordinates of the first point in time on the trajectory and the first JD
        first_jd_indx = np.argmin([entry[1] for entry in cpa_list])
        jd_ref = cpa_list[first_jd_indx][1]
        rad_cpa_ref = cpa_list[first_jd_indx][3]

        print(jd_ref)

        # Set the beginning time to the beginning of the widefield trajectory
        ref_beg_time = (traj.jdt_ref - jd_ref) * 86400

        length_list = []
        decel_list = []

        # Go through all fragments and calculate the length from the reference point
        for frag in fragments:

            # Select only the data points of the current fragment
            cpa_data = [entry for entry in cpa_list if entry[0] == frag]

            # Lengths of the current fragment
            length_frag = []

            # Go through all projected points on the trajectory
            for entry in cpa_data:

                jd = entry[1]
                rad_cpa = entry[3]
                rad_lat = entry[5]
                rad_lon = entry[6]
                height = entry[7]

                # Calculate the distance from the first point on the trajectory and the given point
                dist = vectMag(rad_cpa - rad_cpa_ref)

                # Calculate the time in seconds
                time_sec = (jd - jd_ref) * 24 * 3600

                length_frag.append([time_sec, dist, rad_lat, rad_lon, height])
                length_list.append(
                    [frag, time_sec, dist, rad_lat, rad_lon, height])

            ### Fit the deceleration model to the length ###
            ##################################################################################################

            length_frag = np.array(length_frag)

            # Extract JDs and lengths into individual arrays
            time_data, length_data, lat_data, lon_data, height_data = length_frag.T

            if frag_info.fit_full_exp_model:

                # Fit the full exp deceleration model

                # First guess of the lag parameters
                p0 = [
                    frag_v_init, 0, 0, traj.jacchia_fit[0], traj.jacchia_fit[1]
                ]

                # Length residuals function
                def _lenRes(params, time_data, length_data):
                    return np.sum(
                        (length_data -
                         exponentialDeceleration(time_data, *params))**2)

                # Fit an exponential to the data
                res = scipy.optimize.basinhopping(_lenRes, p0, \
                    minimizer_kwargs={"method": "BFGS", 'args':(time_data, length_data)}, \
                    niter=1000)
                decel_fit = res.x

            else:

                # Fit only the deceleration parameters

                # First guess of the lag parameters
                p0 = [0, 0, traj.jacchia_fit[0], traj.jacchia_fit[1]]

                # Length residuals function
                def _lenRes(params, time_data, length_data, v_init):
                    return np.sum((length_data - exponentialDeceleration(
                        time_data, v_init, *params))**2)

                # Fit an exponential to the data
                res = scipy.optimize.basinhopping(_lenRes, p0, \
                    minimizer_kwargs={"method": "Nelder-Mead", 'args':(time_data, length_data, frag_v_init)}, \
                    niter=100)
                decel_fit = res.x

                # Add the velocity to the deceleration fit
                decel_fit = np.append(np.array([frag_v_init]), decel_fit)

            decel_list.append(decel_fit)

            print('---------------')
            print('Fragment', frag_info.frag_dict[frag], 'fit:')
            print(decel_fit)

            # plt.plot(time_data, length_data, label='Observed')
            # plt.plot(time_data, exponentialDeceleration(time_data, *decel_fit), label='fit')
            # plt.legend()
            # plt.xlabel('Time (s)')
            # plt.ylabel('Length (m)')
            # plt.title('Fragment {:d} fit'.format(frag_info.frag_dict[frag]))
            # plt.show()

            # # Plot the residuals
            # plt.plot(time_data, length_data - exponentialDeceleration(time_data, *decel_fit))
            # plt.xlabel('Time (s)')
            # plt.ylabel('Length O - C (m)')
            # plt.title('Fragment {:d} fit residuals'.format(frag_info.frag_dict[frag]))
            # plt.show()

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

        # Generate a unique color for every fragment
        colors = plt.cm.rainbow(np.linspace(0, 1, len(fragments)))

        # Create a dictionary for every fragment-color pair
        colors_frags = {frag: color for frag, color in zip(fragments, colors)}

        # Make sure lags start at 0
        offset_vel_max = 0

        # Plot the positions of fragments from the beginning to the end
        # Calculate and plot the lag of all fragments
        for frag, decel_fit in zip(fragments, decel_list):

            # Select only the data points of the current fragment
            length_frag = [entry for entry in length_list if entry[0] == frag]

            # Find the last time of the fragment appearance
            last_time = max([entry[1] for entry in length_frag])

            # Extract the observed data
            _, time_data, length_data, lat_data, lon_data, height_data = np.array(
                length_frag).T

            # Plot the positions of fragments from the first time to the end, using fitted parameters
            # The lag is calculated by subtracting an "average" velocity length from the observed length
            time_array = np.linspace(ref_beg_time, last_time, 1000)
            plt.plot(exponentialDeceleration(time_array, *decel_fit) - exponentialDeceleration(time_array, \
                frag_v_init, 0, offset_vel_max, 0, 0), time_array, linestyle='--', color=colors_frags[frag], \
                linewidth=0.75)

            # Plot the observed data
            fake_lag = length_data - exponentialDeceleration(
                time_data, frag_v_init, 0, offset_vel_max, 0, 0)
            plt.plot(fake_lag,
                     time_data,
                     color=colors_frags[frag],
                     linewidth=0.75)

            # Plot the fragment number at the end of each lag
            plt.text(fake_lag[-1] - 10, time_data[-1] + 0.02, str(frag_info.frag_dict[frag]), color=colors_frags[frag], \
                size=7, va='center', ha='right')

            # Check if the fragment has a fragmentation point and plot it
            if site_no in frag_info.fragmentation_points:
                if frag_info.frag_dict[frag] in frag_info.fragmentation_points[
                        site_no]:

                    # Get the lag of the fragmentation point
                    frag_point_time, fragments_list = frag_info.fragmentation_points[
                        site_no][frag_info.frag_dict[frag]]
                    frag_point_lag = exponentialDeceleration(frag_point_time, *decel_fit) \
                        - exponentialDeceleration(frag_point_time, frag_v_init, 0, offset_vel_max, 0, 0)

                    fragments_list = list(map(str, fragments_list))

                    # Save the fragmentation time in the list for light curve plot
                    fragmentations_datetime.append([jd2Date(jd_ref + frag_point_time/86400, dt_obj=True), \
                        fragments_list])

                    # Plot the fragmentation point
                    plt.scatter(frag_point_lag, frag_point_time, s=20, zorder=4, color=colors_frags[frag], \
                        edgecolor='k', linewidth=0.5, label='Fragmentation: ' + ",".join(fragments_list))

        # Plot reference time
        plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True)))

        plt.gca().invert_yaxis()
        plt.grid(color='0.9')

        plt.xlabel('Lag (m)')
        plt.ylabel('Time (s)')

        plt.ylim(ymax=ref_beg_time)

        plt.legend()

        plt.savefig(os.path.join(dir_path, file_name_prefix \
            + '_fragments_deceleration_site_{:s}.png'.format(str(site_no))), dpi=300)

        plt.show()

        time_min = np.inf
        time_max = -np.inf
        ht_min = np.inf
        ht_max = -np.inf

        ### PLOT DYNAMIC PRESSURE FOR EVERY FRAGMENT
        for frag, decel_fit in zip(fragments, decel_list):

            # Select only the data points of the current fragment
            length_frag = [entry for entry in length_list if entry[0] == frag]

            # Extract the observed data
            _, time_data, length_data, lat_data, lon_data, height_data = np.array(
                length_frag).T

            # Fit a linear dependance of time vs. height
            line_fit, _ = scipy.optimize.curve_fit(lineFunc, time_data,
                                                   height_data)

            # Get the time and height limits
            time_min = min(time_min, min(time_data))
            time_max = max(time_max, max(time_data))
            ht_min = min(ht_min, min(height_data))
            ht_max = max(ht_max, max(height_data))

            ### CALCULATE OBSERVED DYN PRESSURE

            # Get the velocity at every point in time
            velocities = exponentialDecelerationVel(time_data, *decel_fit)

            # Calculate the dynamic pressure
            dyn_pressure = dynamicPressure(lat_data, lon_data, height_data,
                                           jd_ref, velocities)

            ###

            # Plot Observed height vs. dynamic pressure
            plt.plot(dyn_pressure / 10**3,
                     height_data / 1000,
                     color=colors_frags[frag],
                     zorder=3,
                     linewidth=0.75)

            # Plot the fragment number at the end of each lag
            plt.text(dyn_pressure[-1]/10**3, height_data[-1]/1000 - 0.02, str(frag_info.frag_dict[frag]), \
                color=colors_frags[frag], size=7, va='top', zorder=3)

            ### CALCULATE MODELLED DYN PRESSURE

            time_array = np.linspace(ref_beg_time, max(time_data), 1000)

            # Calculate the modelled height
            height_array = lineFunc(time_array, *line_fit)

            # Get the time and height limits
            time_min = min(time_min, min(time_array))
            time_max = max(time_max, max(time_array))
            ht_min = min(ht_min, min(height_array))
            ht_max = max(ht_max, max(height_array))

            # Get the atmospheric densities at every heights
            atm_dens_model = getAtmDensity_vect(np.zeros_like(time_array) + np.mean(lat_data), \
                np.zeros_like(time_array) + np.mean(lon_data), height_array, jd_ref)

            # Get the velocity at every point in time
            velocities_model = exponentialDecelerationVel(
                time_array, *decel_fit)

            # Calculate the dynamic pressure
            dyn_pressure_model = atm_dens_model * DRAG_COEFF * velocities_model**2

            ###

            # Plot Modelled height vs. dynamic pressure
            plt.plot(dyn_pressure_model/10**3, height_array/1000, color=colors_frags[frag], zorder=3, \
                linewidth=0.75, linestyle='--')

            # Check if the fragment has a fragmentation point and plot it
            if site_no in frag_info.fragmentation_points:
                if frag_info.frag_dict[frag] in frag_info.fragmentation_points[
                        site_no]:

                    # Get the lag of the fragmentation point
                    frag_point_time, fragments_list = frag_info.fragmentation_points[
                        site_no][frag_info.frag_dict[frag]]

                    # Get the fragmentation height
                    frag_point_height = lineFunc(frag_point_time, *line_fit)

                    # Calculate the velocity at fragmentation
                    frag_point_velocity = exponentialDecelerationVel(
                        frag_point_time, *decel_fit)

                    # Calculate the atm. density at the fragmentation point
                    frag_point_atm_dens = getAtmDensity(np.mean(lat_data), np.mean(lon_data), frag_point_height, \
                        jd_ref)

                    # Calculate the dynamic pressure at fragmentation in kPa
                    frag_point_dyn_pressure = frag_point_atm_dens * DRAG_COEFF * frag_point_velocity**2
                    frag_point_dyn_pressure /= 10**3

                    # Compute height in km
                    frag_point_height_km = frag_point_height / 1000

                    fragments_list = map(str, fragments_list)

                    # Plot the fragmentation point
                    plt.scatter(frag_point_dyn_pressure, frag_point_height_km, s=20, zorder=5, \
                        color=colors_frags[frag], edgecolor='k', linewidth=0.5, \
                        label='Fragmentation: ' + ",".join(fragments_list))

                    ### Plot the errorbar

                    # Compute the lower veloicty estimate
                    stddev_multiplier = 2.0

                    # Check if the uncertainty exists
                    if traj_uncert.v_init is None:
                        v_init_uncert = 0
                    else:
                        v_init_uncert = traj_uncert.v_init

                    # Compute the range of velocities
                    lower_vel = frag_point_velocity - stddev_multiplier * v_init_uncert
                    higher_vel = frag_point_velocity + stddev_multiplier * v_init_uncert

                    # Assume the atmosphere density can vary +/- 25% (Gunther's analysis)
                    lower_atm_dens = 0.75 * frag_point_atm_dens
                    higher_atm_dens = 1.25 * frag_point_atm_dens

                    # Compute lower and higher range for dyn pressure in kPa
                    lower_frag_point_dyn_pressure = (
                        lower_atm_dens * DRAG_COEFF * lower_vel**2) / 10**3
                    higher_frag_point_dyn_pressure = (
                        higher_atm_dens * DRAG_COEFF * higher_vel**2) / 10**3

                    # Compute errors
                    lower_error = abs(frag_point_dyn_pressure -
                                      lower_frag_point_dyn_pressure)
                    higher_error = abs(frag_point_dyn_pressure -
                                       higher_frag_point_dyn_pressure)

                    print(frag_point_dyn_pressure, frag_point_height_km, [
                        lower_frag_point_dyn_pressure,
                        higher_frag_point_dyn_pressure
                    ])

                    # Plot the errorbar
                    plt.errorbar(frag_point_dyn_pressure, frag_point_height_km, \
                        xerr=[[lower_error], [higher_error]], fmt='--', capsize=5, zorder=4, \
                        color=colors_frags[frag], label='+/- 25% $\\rho_{atm}$, 2$\\sigma_v$ ')

                    # Save the computed fragmentation values to list
                    # Site, Reference JD, Relative time, Fragment ID, Height, Dyn pressure, Dyn pressure lower \
                    #   bound, Dyn pressure upper bound
                    computed_values.append([site_no, jd_ref, frag_point_time, frag_info.frag_dict[frag], \
                        frag_point_height_km, frag_point_dyn_pressure, lower_frag_point_dyn_pressure, \
                        higher_frag_point_dyn_pressure])

                    ######

        # Plot reference time
        plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True)))

        plt.xlabel('Dynamic pressure (kPa)')
        plt.ylabel('Height (km)')

        plt.ylim([ht_min / 1000, ht_max / 1000])

        # Remove repeating labels and plot the legend
        handles, labels = plt.gca().get_legend_handles_labels()
        by_label = OrderedDict(zip(labels, handles))
        plt.legend(by_label.values(), by_label.keys())

        plt.grid(color='0.9')

        # Create the label for seconds
        ax2 = plt.gca().twinx()
        ax2.set_ylim([time_max, time_min])
        ax2.set_ylabel('Time (s)')

        plt.savefig(os.path.join(dir_path, file_name_prefix \
            + '_fragments_dyn_pressures_site_{:s}.png'.format(str(site_no))), dpi=300)

        plt.show()

        ### PLOT DYNAMICS MASSES FOR ALL FRAGMENTS
        for frag, decel_fit in zip(fragments, decel_list):

            # Select only the data points of the current fragment
            length_frag = [entry for entry in length_list if entry[0] == frag]

            # Extract the observed data
            _, time_data, length_data, lat_data, lon_data, height_data = np.array(
                length_frag).T

            # Fit a linear dependance of time vs. height
            line_fit, _ = scipy.optimize.curve_fit(lineFunc, time_data,
                                                   height_data)

            ### CALCULATE OBSERVED DYN MASS

            # Get the velocity at every point in time
            velocities = exponentialDecelerationVel(time_data, *decel_fit)

            decelerations = np.abs(
                exponentialDecelerationDecel(time_data, *decel_fit))

            # Calculate the dynamic mass
            dyn_mass = dynamicMass(frag_info.bulk_density, lat_data, lon_data, height_data, jd_ref, \
                velocities, decelerations)

            ###

            # Plot Observed height vs. dynamic pressure
            plt.plot(dyn_mass * 1000,
                     height_data / 1000,
                     color=colors_frags[frag],
                     zorder=3,
                     linewidth=0.75)

            # Plot the fragment number at the end of each lag
            plt.text(dyn_mass[-1]*1000, height_data[-1]/1000 - 0.02, str(frag_info.frag_dict[frag]), \
                color=colors_frags[frag], size=7, va='top', zorder=3)

            ### CALCULATE MODELLED DYN MASS

            time_array = np.linspace(ref_beg_time, max(time_data), 1000)

            # Calculate the modelled height
            height_array = lineFunc(time_array, *line_fit)

            # Get the velocity at every point in time
            velocities_model = exponentialDecelerationVel(
                time_array, *decel_fit)

            # Get the deceleration
            decelerations_model = np.abs(
                exponentialDecelerationDecel(time_array, *decel_fit))

            # Calculate the modelled dynamic mass
            dyn_mass_model = dynamicMass(frag_info.bulk_density,
                np.zeros_like(time_array) + np.mean(lat_data),
                np.zeros_like(time_array) + np.mean(lon_data), height_array, jd_ref, \
                velocities_model, decelerations_model)

            ###

            # Plot Modelled height vs. dynamic mass
            plt.plot(dyn_mass_model*1000, height_array/1000, color=colors_frags[frag], zorder=3, \
                linewidth=0.75, linestyle='--', \
                label='Frag {:d} initial dyn mass = {:.1e} g'.format(frag_info.frag_dict[frag], \
                    1000*dyn_mass_model[0]))

        # Plot reference time
        plt.title('Reference time: ' + str(jd2Date(jd_ref, dt_obj=True)) \
            + ', $\\rho_m = ${:d} $kg/m^3$'.format(frag_info.bulk_density))

        plt.xlabel('Dynamic mass (g)')
        plt.ylabel('Height (km)')

        plt.ylim([ht_min / 1000, ht_max / 1000])

        # Remove repeating labels and plot the legend
        handles, labels = plt.gca().get_legend_handles_labels()
        by_label = OrderedDict(zip(labels, handles))
        plt.legend(by_label.values(), by_label.keys())

        plt.grid(color='0.9')

        # Create the label for seconds
        ax2 = plt.gca().twinx()
        ax2.set_ylim([time_max, time_min])
        ax2.set_ylabel('Time (s)')

        plt.savefig(os.path.join(dir_path, file_name_prefix \
            + '_fragments_dyn_mass_site_{:s}.png'.format(str(site_no))), dpi=300)

        plt.show()

    # Plot the light curve if the METAL .met file was given
    if (metal_mags is not None):

        # Make sure there are lightcurves in the data
        if len(metal_mags):

            lc_min = np.inf
            lc_max = -np.inf

            # Plot the lightcurves
            for site_entry in metal_mags:

                site_id, time, mags = site_entry

                # Track the minimum and maximum magnitude
                lc_min = np.min([lc_min, np.min(mags)])
                lc_max = np.max([lc_max, np.max(mags)])

                plt.plot(time,
                         mags,
                         marker='+',
                         label='Site: ' + str(site_id),
                         zorder=4,
                         linewidth=1)

            # Plot times of fragmentation
            for frag_dt, fragments_list in fragmentations_datetime:

                # Plot the lines of fragmentation
                y_arr = np.linspace(lc_min, lc_max, 10)
                x_arr = [frag_dt] * len(y_arr)

                plt.plot(x_arr, y_arr, linestyle='--', zorder=4, \
                    label='Fragmentation: ' + ",".join(fragments_list))

            plt.xlabel('Time (UTC)')
            plt.ylabel('Absolute magnitude (@100km)')

            plt.grid()

            plt.gca().invert_yaxis()

            plt.legend()

            ### Format the X axis datetimes
            import matplotlib

            def formatDT(x, pos=None):

                x = matplotlib.dates.num2date(x)

                # Add date to the first tick
                if pos == 0:
                    fmt = '%D %H:%M:%S.%f'
                else:
                    fmt = '%H:%M:%S.%f'

                label = x.strftime(fmt)[:-3]
                label = label.rstrip("0")
                label = label.rstrip(".")

                return label

            from matplotlib.ticker import FuncFormatter

            plt.gca().xaxis.set_major_formatter(FuncFormatter(formatDT))
            plt.gca().xaxis.set_minor_formatter(FuncFormatter(formatDT))

            ###

            plt.tight_layout()

            # Save the figure
            plt.savefig(os.path.join(dir_path, file_name_prefix + '_fragments_light_curve_comparison.png'), \
                dpi=300)

            plt.show()

    # Save the computed values to file
    with open(
            os.path.join(dir_path, file_name_prefix +
                         "_fragments_dyn_pressure_info.txt"), 'w') as f:

        # Site, Reference JD, Relative time, Fragment ID, Height, Dyn pressure, Dyn pressure lower \
        #   bound, Dyn pressure upper bound

        # Write the header
        f.write(
            "# Site,               Ref JD,  Rel time, Frag ID, Ht (km),  DP (kPa),   DP low,  DP high\n"
        )

        # Write computed values for every fragment
        for entry in computed_values:
            f.write(
                " {:>5s}, {:20.12f}, {:+8.6f}, {:7d}, {:7.3f}, {:9.2f}, {:8.2f}, {:8.2f}\n"
                .format(*entry))