├── .gitattributes ├── .gitignore ├── .gitmodules ├── ErosionSimParametersCAMOWide_lm+5.4_mintime0.125s_good_files.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── UpdateOrbitFiles.py ├── report.log ├── requirements.txt ├── setup.py ├── test.py └── wmpl ├── Analysis ├── FitPopulationAndMassIndex.py └── __init__.py ├── CAMO ├── MarkFragments.py ├── Mirfit.py ├── ProjectNarrowPicksToWideTraj.py ├── __init__.py └── docs │ └── narrow_project_example_file.json ├── CMN ├── CMNCalcTrajectory.py ├── CMNFormats.py ├── RMSCMNTrajectory.py ├── __init__.py └── cmn_fireball_2017030506 │ ├── M_2017030506APO0001.txt │ └── M_2017030506KOP0001.txt ├── Config.py ├── Formats ├── CAMS.py ├── CSSseismic.py ├── ECSV.py ├── EvUWO.py ├── EventUWO.py ├── Fripon.py ├── GenericFunctions.py ├── Met.py ├── Milig.py ├── Pickle.py ├── Plates.py ├── RMSJSON.py ├── Vid.py ├── WmplTrajectorySummary.py └── __init__.py ├── MetSim ├── AutoRefineFit.py ├── AutoRefineFit_options.txt ├── FitSim.py ├── FitSimAnalyzer.py ├── GUI.py ├── GUI.ui ├── GUITools.py ├── ML │ ├── FitErosion.py │ ├── GenerateSimulations.py │ ├── PostprocessSims.py │ └── __init__.py ├── MetSim.py ├── MetSimErosion.py ├── MetSimErosionCyTools.pyx ├── MetalMass.py ├── Metsim0001_input.txt ├── __init__.py └── native │ ├── METSIM0001_sizes.txt │ ├── METSIM0001_times.txt │ ├── Met_main2016.cpp │ ├── Metsim0001_accel2.txt │ ├── Metsim0001_results.bin │ ├── met_calculate.cpp │ ├── met_calculate.h │ └── test_out.txt ├── Misc ├── Orfeus_stations.csv ├── RomulanOrbits.py ├── __init__.py ├── can_stations.csv ├── isc_stations.csv └── russian_stations.csv ├── Rebound ├── REBOUND.py └── __init__.py ├── TrajSim ├── AnalyzeErrorEstimation.py ├── AnalyzeTrajectories.py ├── MeteorShowerModel.py ├── ShowerSim.py ├── SimMeteorBatchSolve.py ├── SporadicSourcesModel.py ├── TrajConvAngles.py ├── TrajSim.py └── __init__.py ├── Trajectory ├── AggregateAndPlot.py ├── AutoGUI.py ├── CorrelateEngine.py ├── CorrelateRMS.py ├── GuralTrajectory.py ├── Orbit.py ├── Trajectory.py ├── __init__.py └── lib │ ├── README │ ├── common │ ├── AttributeStructure.h │ ├── ClusteringFunctions.h │ ├── DynamicArrayAllocation.h │ ├── EMCCD_Detect_IOfunctions.h │ ├── EMCCD_FITS_IOfunctions.h │ ├── EMCCD_VID_IOfunctions.h │ ├── EZbitmapWriter.h │ ├── ImageProcessingFunctions.h │ ├── JulianCalendarConversions.h │ ├── MTPcompression_Ushort.h │ ├── Makefile │ ├── ParticleSwarmFunctions.c │ ├── ParticleSwarmFunctions.h │ ├── System_FileFunctions.h │ ├── TimeCoordinateFunctions.h │ └── TrackingFunctions.h │ └── trajectory │ ├── Makefile │ ├── TrajectorySolution.c │ ├── TrajectorySolution.h │ └── conf │ └── trajectorysolution.conf ├── Utils ├── AlphaBeta.py ├── AtmosphereDensity.py ├── Dcriteria.py ├── DynamicMassFit.py ├── Earth.py ├── Ephem.py ├── GeoidHeightEGM96.py ├── GreatCircle.py ├── KalmanVelocity.py ├── Math.py ├── MeanOrbit.py ├── Misc.py ├── OSTools.py ├── OptimizePointingsFOV.py ├── ParentBodySearch.py ├── PecinaCeplechaFunction.py ├── Physics.py ├── Pickling.py ├── PlotCelestial.py ├── PlotMap.py ├── PlotMap_OSM.py ├── PlotOrbits.py ├── Plotting.py ├── PyDomainParallelizer.py ├── ReplotMCUnc.py ├── SampleTrajectoryPositions.py ├── ShowerAssociation.py ├── ShowerTableMeasurement.py ├── SolarLongitude.py ├── TrajConversions.py ├── TrajectoryKML.py ├── __init__.py └── remoteDataHandling.py ├── __init__.py └── share ├── Meteorites_with_Orbits.csv ├── VSOP87D.ear ├── WW15MGH.DAC ├── gmn_shower_table_20230518.txt ├── shower_parameters.txt ├── streamfulldata.csv └── tai-utc.dat /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | 5 | # Denote all files that are truly binary and should not be modified. 6 | *.png binary 7 | *.jpg binary 8 | *.bsp binary 9 | *.dat binary 10 | *.ear binary 11 | *.pickle binary 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Sublime project file 2 | *.sublime-project 3 | *.sublime-workspace 4 | 5 | # Python compiled files 6 | *.pyc 7 | 8 | # JPL DE430 ephemerids 9 | de430.bsp 10 | 11 | # Compiled files 12 | *.so 13 | *.so.0 14 | *.o 15 | 16 | # Build directories 17 | build/ 18 | dist/ 19 | *.egg-info/ 20 | 21 | # Original metsim code 22 | wmpl/MetSim/native 23 | # wmpl/share - files are downloaded during setup 24 | wmpl/share/ShowerLookUpTable.npy 25 | wmpl/share/streamfulldata.npy 26 | .vscode/settings.json 27 | wmpl/share/Amors.txt 28 | wmpl/share/Apollos.txt 29 | wmpl/share/Atens.txt 30 | wmpl/share/gmn_shower_table_20230518.npy 31 | wmpl/share/ELEMENTS.COMET 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "PythonNRLMSISE00"] 2 | path = wmpl/PythonNRLMSISE00 3 | url = https://github.com/dvida/Python-NRLMSISE-00.git 4 | -------------------------------------------------------------------------------- /ErosionSimParametersCAMOWide_lm+5.4_mintime0.125s_good_files.txt: -------------------------------------------------------------------------------- 1 | # param_class_name = ErosionSimParametersCAMOWide 2 | # File name, lim mag, lim mag length, length delay (s) 3 | /mnt/bulk/test/metsim/simulations/v42/rho1600/erosion_sim_v42.48_m8.05e-03g_rho1647_z65.1_abl0.264_eh115.4_er0.041_s2.34.pickle, 5.21076002, 5.69181690, 0.00000000 4 | /mnt/bulk/test/metsim/simulations/v19/rho4000/erosion_sim_v19.30_m7.34e-02g_rho4085_z22.3_abl0.435_eh092.7_er0.549_s2.79.pickle, 5.21076002, 5.69181690, 0.00000000 5 | /mnt/bulk/test/metsim/simulations/v37/rho3500/erosion_sim_v37.27_m3.49e-02g_rho3506_z18.9_abl0.347_eh116.7_er0.867_s1.89.pickle, 5.21076002, 5.69181690, 0.00000000 6 | /mnt/bulk/test/metsim/simulations/v61/rho4000/erosion_sim_v61.71_m2.41e-01g_rho4083_z32.7_abl0.441_eh097.1_er0.815_s2.65.pickle, 5.21076002, 5.69181690, 0.00000000 7 | /mnt/bulk/test/metsim/simulations/v30/rho1300/erosion_sim_v30.44_m3.12e-01g_rho1331_z60.0_abl0.052_eh084.4_er0.677_s1.98.pickle, 5.74957464, 5.03488163, 0.00000000 8 | /mnt/bulk/test/metsim/simulations/v35/rho5300/erosion_sim_v35.28_m3.71e-02g_rho5346_z55.7_abl0.262_eh112.3_er0.493_s2.13.pickle, 5.74957464, 5.03488163, 0.00000000 9 | /mnt/bulk/test/metsim/simulations/v64/rho5100/erosion_sim_v64.04_m1.35e-02g_rho5136_z54.6_abl0.037_eh071.0_er0.180_s1.89.pickle, 5.63062865, 5.07489956, 0.00000000 10 | /mnt/bulk/test/metsim/simulations/v34/rho0200/erosion_sim_v34.70_m5.69e-02g_rho0265_z10.9_abl0.155_eh123.0_er0.724_s1.90.pickle, 5.18272842, 5.40322329, 0.00000000 11 | /mnt/bulk/test/metsim/simulations/v50/rho2000/erosion_sim_v50.16_m6.34e-04g_rho2001_z19.3_abl0.141_eh086.5_er0.958_s2.00.pickle, 5.63062865, 5.07489956, 0.00000000 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Denis Vida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include wmpl\share * -------------------------------------------------------------------------------- /UpdateOrbitFiles.py: -------------------------------------------------------------------------------- 1 | """ Updates asteroid and comet orbit files from MPC and JPL website. """ 2 | 3 | from __future__ import print_function, division, absolute_import 4 | 5 | import os 6 | import sys 7 | import ssl 8 | import socket 9 | import shutil 10 | 11 | # Fix certificates error 12 | ssl._create_default_https_context = ssl._create_unverified_context 13 | 14 | 15 | if sys.version_info.major < 3: 16 | import urllib as urllibrary 17 | 18 | else: 19 | import urllib.request as urllibrary 20 | 21 | def updateOrbitFiles(): 22 | """ Updates asteroid and comet orbit files from MPC and JPL website. """ 23 | 24 | # Set a 15 second connection timeout so the compile is not held by a hanging download 25 | socket.setdefaulttimeout(15) 26 | 27 | # Comets 28 | comets_url = "https://ssd.jpl.nasa.gov/dat/ELEMENTS.COMET" 29 | 30 | # Amors 31 | amors_url = "http://cgi.minorplanetcenter.net/cgi-bin/textversion.cgi?f=lists/Amors.html" 32 | 33 | # Apollos 34 | apollos_url = "http://cgi.minorplanetcenter.net/cgi-bin/textversion.cgi?f=lists/Apollos.html" 35 | 36 | # Atens 37 | atens_url = "http://cgi.minorplanetcenter.net/cgi-bin/textversion.cgi?f=lists/Atens.html" 38 | 39 | 40 | dir_path = os.path.split(os.path.abspath(__file__))[0] 41 | dir_path = os.path.join(dir_path, 'wmpl', 'share') 42 | 43 | file_names = ['ELEMENTS.COMET', 'Amors.txt', 'Apollos.txt', 'Atens.txt'] 44 | url_list = [comets_url, amors_url, apollos_url, atens_url] 45 | 46 | # Temporary file path 47 | temp_path = os.path.join(dir_path, "temp.part") 48 | 49 | # Download all orbit files 50 | for fname, url in zip(file_names, url_list): 51 | print('Downloading {:s}...'.format(fname)) 52 | 53 | tries = 0 54 | while tries < 5: 55 | 56 | # Download the file to a temporary location (as not to overwrite the existing file) 57 | try: 58 | 59 | # Download the file to a temporary location 60 | urllibrary.urlretrieve(url, temp_path) 61 | 62 | # Move the downloaded file to a final location 63 | shutil.move(temp_path, os.path.join(dir_path, fname)) 64 | 65 | break 66 | 67 | except Exception as e: 68 | print("Download failed with:" + repr(e)) 69 | tries += 1 70 | 71 | 72 | print(' ... done!') 73 | 74 | 75 | 76 | 77 | if __name__ == "__main__": 78 | 79 | updateOrbitFiles() -------------------------------------------------------------------------------- /report.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/report.log -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy < 2.0 2 | cython 3 | scipy 4 | matplotlib 5 | jplephem 6 | pyephem 7 | # https://github.com/matplotlib/basemap/archive/master.zip 8 | PyQt5 ; platform_machine != 'aarch64' 9 | pyyaml 10 | pyswarms 11 | pkgconfig 12 | ml-dtypes 13 | keras 14 | pytz 15 | pandas 16 | gitpython 17 | numba 18 | watchdog==3.0.0; python_version == '3.7' 19 | watchdog; python_version >= '3.8' 20 | paramiko 21 | rebound==4.3.0 22 | reboundx==4.3.0 ; platform_machine != 'win32' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | if sys.version_info.major < 3: 6 | import urllib as urllibrary 7 | else: 8 | import urllib.request as urllibrary 9 | 10 | import platform 11 | from packaging.requirements import Requirement 12 | from packaging.markers import default_environment 13 | from setuptools import setup, find_packages, Extension 14 | from Cython.Build import cythonize 15 | import numpy 16 | 17 | 18 | from UpdateOrbitFiles import updateOrbitFiles 19 | 20 | 21 | # Utility function to read the README file. 22 | # Used for the long_description. It's nice, because now 1) we have a top level 23 | # README file and 2) it's easier to type in the README file than to put a raw 24 | # string in below ... 25 | def read(fname): 26 | return open(os.path.join(os.path.dirname(__file__), fname), encoding='utf-8').read() 27 | 28 | 29 | 30 | 31 | # Remove all pyc files from the project 32 | for entry in sorted(os.walk("."), key=lambda x: x[0]): 33 | 34 | dir_path, _, file_names = entry 35 | 36 | for fn in file_names: 37 | if fn.lower().endswith(".pyc"): 38 | os.remove(os.path.join(dir_path, fn)) 39 | 40 | 41 | 42 | 43 | packages = [] 44 | 45 | dir_path = os.path.split(os.path.abspath(__file__))[0] 46 | 47 | # Get all folders with Python packages 48 | for dir_name in os.listdir(dir_path): 49 | 50 | local_path = os.path.join(dir_path, dir_name) 51 | 52 | if os.path.isdir(local_path): 53 | 54 | # Check if there is an __init__.py file in those folders 55 | if "__init__.py" in os.listdir(local_path): 56 | 57 | packages.append(dir_name) 58 | 59 | 60 | # Read requirements.txt and drop any reboundx line on Windows 61 | with open('requirements.txt', encoding='utf-8') as f: 62 | raw_reqs = f.read().splitlines() 63 | 64 | requirements = [] 65 | is_windows = platform.system() == "Windows" 66 | 67 | for raw in raw_reqs: 68 | line = raw.strip() 69 | if not line or line.startswith('#'): 70 | continue 71 | 72 | # If this requirement mentions 'reboundx' and we're on Windows, skip it. 73 | if is_windows and 'reboundx' in line.lower(): 74 | print("Skipping reboundx on Windows:", line) 75 | continue 76 | 77 | # If this is PyQt5 on Windows, skip it entirely as it's probably installed via conda 78 | if is_windows and line.lower().startswith('pyqt5'): 79 | print(">> Skipping PyQt5 on Windows (no METADATA present)") 80 | continue 81 | 82 | # Otherwise, keep it verbatim (markers included). pip/setuptools will handle them. 83 | requirements.append(line) 84 | 85 | print("Final install_requires:", requirements) 86 | 87 | 88 | 89 | # Download the DE430 ephemerids 90 | de430_file_path = os.path.join('wmpl', 'share', 'de430.bsp') 91 | 92 | if not os.path.isfile(de430_file_path): 93 | 94 | de430_url = "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de430.bsp" 95 | 96 | print('Downloading DE430 ephemerids...') 97 | urllibrary.urlretrieve(de430_url, de430_file_path) 98 | print('... download done!') 99 | 100 | 101 | # Download the orbit files 102 | updateOrbitFiles() 103 | 104 | 105 | # Delete npy shower files 106 | jenniskens_shower_table_npy = os.path.join('wmpl', 'share', 'ShowerLookUpTable.npy') 107 | if os.path.isfile(jenniskens_shower_table_npy): 108 | print('Deleting Jenniskens shower table npy file...') 109 | os.remove(jenniskens_shower_table_npy) 110 | 111 | iau_shower_table_npy = os.path.join('wmpl', 'share', 'streamfulldata.npy') 112 | if os.path.isfile(iau_shower_table_npy): 113 | print('Deleting IAU shower table npy file...') 114 | os.remove(iau_shower_table_npy) 115 | 116 | gmn_shower_table_npy = os.path.join('wmpl', 'share', 'gmn_shower_table_20230518.npy') 117 | if os.path.isfile(gmn_shower_table_npy): 118 | print('Deleting GMN shower table npy file...') 119 | os.remove(gmn_shower_table_npy) 120 | 121 | 122 | # This will generate the numpy shower tables 123 | from wmpl.Utils.ShowerAssociation import loadGMNShowerTable 124 | 125 | 126 | # Get all data files in 'share' 127 | share_files = [os.path.join('wmpl', 'share', file_name) for file_name in os.listdir(os.path.join(dir_path, 'wmpl', 'share'))] 128 | 129 | # Add MetSim input file 130 | share_files += [os.path.join("wmpl", "MetSim", "Metsim0001_input.txt")] 131 | 132 | # Add MetSim GUI definition file 133 | share_files += [os.path.join("wmpl", "MetSim", "GUI.ui")] 134 | 135 | # Add numpy shower tables to install 136 | share_files += [iau_shower_table_npy, gmn_shower_table_npy] 137 | 138 | 139 | 140 | 141 | # Cython modules which will be compiled on setup 142 | cython_modules = [ 143 | Extension('wmpl.MetSim.MetSimErosionCyTools', sources=['wmpl/MetSim/MetSimErosionCyTools.pyx'], \ 144 | include_dirs=[numpy.get_include()]) 145 | ] 146 | 147 | 148 | # Compile Gural solver under Linux 149 | if "linux" in sys.platform: 150 | 151 | gural_common = os.path.join("wmpl", "Trajectory", "lib", "common") 152 | gural_trajectory = os.path.join("wmpl", "Trajectory", "lib", "trajectory") 153 | 154 | print("Building Gural trajectory solver...") 155 | subprocess.call(["make", "-C", gural_common]) 156 | subprocess.call(["make", "-C", gural_trajectory]) 157 | 158 | # Add gural library files to install - shared libraries & PSO configuration 159 | gural_files = [ 160 | os.path.join(gural_trajectory, 'libtrajectorysolution.so'), 161 | os.path.join(gural_trajectory, 'libtrajectorysolution.so.0'), 162 | os.path.join(gural_trajectory, 'conf', 'trajectorysolution.conf'), 163 | ] 164 | 165 | share_files += [file for file in gural_files if os.path.isfile(file)] 166 | 167 | 168 | setup( 169 | name = "westernmeteorpylib", 170 | version = "1.0", 171 | author = "Denis Vida", 172 | author_email = "denis.vida@gmail.com", 173 | description = ("Python code developed for the Western Meteor Physics Group."), 174 | license = "MIT", 175 | keywords = "meteors", 176 | packages=find_packages(), 177 | ext_modules = cythonize(cython_modules), 178 | data_files=[(os.path.join('wmpl', 'share'), share_files)], 179 | include_package_data=True, 180 | long_description=read('README.md'), 181 | classifiers=[ 182 | "Development Status :: 3 - Alpha", 183 | "Topic :: Utilities", 184 | "License :: OSI Approved :: MIT License", 185 | ], 186 | setup_requires=["numpy"], 187 | install_requires=requirements, 188 | include_dirs=[numpy.get_include()] 189 | ) -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | """ Working script for testing chunks of code. Nothing really of value here. """ 2 | 3 | import numpy as np 4 | 5 | from wmpl.Utils.Math import findClosestPoints, vectNorm, vectMag 6 | from wmpl.Utils.TrajConversions import date2JD, ecef2ENU, enu2ECEF, cartesian2Geo, geo2Cartesian 7 | 8 | 9 | 10 | 11 | def calcSpatialResidual(jd, state_vect, radiant_eci, stat, meas): 12 | """ Calculate horizontal and vertical residuals from the radiant line, for the given observed point. 13 | 14 | Arguments: 15 | jd: [float] Julian date 16 | state_vect: [3 element ndarray] ECI position of the state vector 17 | radiant_eci: [3 element ndarray] radiant direction vector in ECI 18 | stat: [3 element ndarray] position of the station in ECI 19 | meas: [3 element ndarray] line of sight from the station, in ECI 20 | 21 | Return: 22 | (hres, vres): [tuple of floats] residuals in horitontal and vertical direction from the radiant line 23 | 24 | """ 25 | 26 | meas = vectNorm(meas) 27 | 28 | # Calculate closest points of approach (observed line of sight to radiant line) from the state vector 29 | obs_cpa, rad_cpa, d = findClosestPoints(stat, meas, state_vect, radiant_eci) 30 | 31 | 32 | # Vector pointing from the point on the trajectory to the point on the line of sight 33 | p = obs_cpa - rad_cpa 34 | 35 | # Calculate geographical coordinates of the state vector 36 | lat, lon, elev = cartesian2Geo(jd, *state_vect) 37 | 38 | # Calculate ENU (East, North, Up) vector at the position of the state vector, and direction of the radiant 39 | nn = np.array(ecef2ENU(lat, lon, *radiant_eci)) 40 | 41 | # Convert the vector to polar coordinates 42 | theta = np.arctan2(nn[1], nn[0]) 43 | phi = np.arccos(nn[2]/vectMag(nn)) 44 | 45 | # Local reference frame unit vectors 46 | hx = np.array([ -np.cos(theta), np.sin(theta), 0.0]) 47 | vz = np.array([-np.cos(phi)*np.sin(theta), -np.cos(phi)*np.cos(theta), np.sin(phi)]) 48 | hy = np.array([ np.sin(phi)*np.sin(theta), np.sin(phi)*np.cos(theta), np.cos(phi)]) 49 | 50 | # Calculate local reference frame unit vectors in ECEF coordinates 51 | ehorzx = enu2ECEF(lat, lon, *hx) 52 | ehorzy = enu2ECEF(lat, lon, *hy) 53 | evert = enu2ECEF(lat, lon, *vz) 54 | 55 | ehx = np.dot(p, ehorzx) 56 | ehy = np.dot(p, ehorzy) 57 | 58 | # Calculate vertical residuals 59 | vres = np.sign(ehx)*np.hypot(ehx, ehy) 60 | 61 | # Calculate horizontal residuals 62 | hres = np.dot(p, evert) 63 | 64 | return hres, vres 65 | 66 | 67 | 68 | if __name__ == "__main__": 69 | 70 | 71 | import sys 72 | 73 | import matplotlib.pyplot as plt 74 | 75 | import pyximport 76 | pyximport.install(setup_args={'include_dirs':[np.get_include()]}) 77 | from wmpl.MetSim.MetSimErosionCyTools import luminousEfficiency, ionizationEfficiency 78 | 79 | 80 | 81 | ### Plot different lum effs ### 82 | 83 | # Range of velocities 84 | vel_range = np.linspace(2000, 72000, 100) 85 | 86 | # Range of masses 87 | masses = [1e-11, 1e-9, 1e-7, 1e-5, 0.001, 0.01, 0.1, 1, 10, 100, 1000] 88 | 89 | 90 | lum_eff_types = [1, 2, 3, 4, 5, 6, 7] 91 | lum_eff_labels = ["Revelle & Ceplecha (2001) - Type I", 92 | "Revelle & Ceplecha (2001) - Type II", 93 | "Revelle & Ceplecha (2001) - Type III", 94 | "Borovicka et al. (2013) - Kosice", 95 | "CAMO faint meteors", 96 | "Ceplecha & McCrosky (1976)", 97 | "Borovicka et al. (2020) - Two strengths"] 98 | 99 | for i, lum_type in enumerate(lum_eff_types): 100 | 101 | for mass in masses: 102 | 103 | lum_list = [] 104 | for vel in vel_range: 105 | lum = luminousEfficiency(lum_type, 0.0, vel, mass) 106 | 107 | lum_list.append(lum) 108 | 109 | plt.plot(vel_range/1000, 100*np.array(lum_list), label="{:s} kg".format(str(mass)), zorder=4) 110 | 111 | 112 | plt.title(lum_eff_labels[i]) 113 | 114 | plt.xlabel("Velocity (km/s)") 115 | plt.ylabel("Tau (%)") 116 | 117 | plt.legend() 118 | 119 | plt.grid(color='0.9') 120 | 121 | plt.show() 122 | 123 | 124 | 125 | # Plot the ionization efficiency 126 | beta_arr = np.array([ionizationEfficiency(vel) for vel in vel_range]) 127 | plt.semilogy(vel_range/1000, 100*beta_arr, label="Jones (1997)") 128 | plt.xlabel("Velocity (km/s)") 129 | plt.ylabel("Beta (%)") 130 | plt.show() 131 | 132 | 133 | sys.exit() 134 | 135 | ### ### 136 | 137 | 138 | 139 | 140 | from mpl_toolkits.mplot3d import Axes3D 141 | import matplotlib.pyplot as plt 142 | 143 | jd = date2JD(2018, 2, 14, 10, 30, 0) 144 | 145 | lat = np.radians(45.0) 146 | lon = np.radians(13.0) 147 | h = 100000 148 | 149 | state_vect = np.array(geo2Cartesian(lat, lon, h, jd)) 150 | radiant_eci = np.array([0.0, 1.0, 0.0]) 151 | 152 | stat = np.array(geo2Cartesian(lat, lon, h + 10, jd)) 153 | meas = np.array([0.0, 0.0, 1.0]) 154 | 155 | 156 | 157 | fig = plt.figure() 158 | ax = fig.add_subplot(111, projection='3d') 159 | 160 | 161 | print(calcSpatialResidual(jd, state_vect, radiant_eci, stat, meas)) 162 | 163 | 164 | # Plot the origin 165 | #ax.scatter(0, 0, 0) 166 | 167 | 168 | # Plot the first point 169 | ax.scatter(*state_vect) 170 | 171 | # Plot the line from the origin 172 | rad_x, rad_y, rad_z = -vectNorm(state_vect) 173 | rst_x, rst_y, rst_z = state_vect 174 | meteor_len = 1000000 175 | ax.quiver(rst_x, rst_y, rst_z, rad_x, rad_y, rad_z, length=meteor_len, normalize=True, color='b', 176 | arrow_length_ratio=0.1) 177 | 178 | # Plot the radiant direction line 179 | rad_x, rad_y, rad_z = -radiant_eci 180 | rst_x, rst_y, rst_z = state_vect 181 | meteor_len = 1000000 182 | ax.quiver(rst_x, rst_y, rst_z, rad_x, rad_y, rad_z, length=meteor_len, normalize=True, color='r', 183 | arrow_length_ratio=0.1) 184 | 185 | 186 | # Plot the second point 187 | ax.scatter(*stat) 188 | 189 | # Plot the direction of the second vector 190 | rad_x, rad_y, rad_z = -meas 191 | rst_x, rst_y, rst_z = stat 192 | meteor_len = 1000000 193 | ax.quiver(rst_x, rst_y, rst_z, rad_x, rad_y, rad_z, length=meteor_len, normalize=True, color='g', 194 | arrow_length_ratio=0.1) 195 | 196 | 197 | 198 | 199 | 200 | # Calculate closest points of approach (observed line of sight to radiant line) from the state vector 201 | obs_cpa, rad_cpa, d = findClosestPoints(stat, meas, state_vect, radiant_eci) 202 | 203 | # Plot the closest points 204 | ax.scatter(*obs_cpa) 205 | ax.scatter(*rad_cpa) 206 | 207 | 208 | # Set a constant aspect ratio 209 | ax.set_aspect('equal', adjustable='box-forced') 210 | 211 | plt.show() 212 | -------------------------------------------------------------------------------- /wmpl/Analysis/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | # Get the path of the current folder 5 | dir_path = os.path.split(os.path.abspath(__file__))[0] 6 | 7 | # Import all modules 8 | __all__ = [name for name in os.listdir(dir_path) if ((name != '__init__.py') and ('.py' in name)) or os.path.isdir(name)] -------------------------------------------------------------------------------- /wmpl/CAMO/MarkFragments.py: -------------------------------------------------------------------------------- 1 | """ Takes a .vid file and the reduction, and creates video frames with marked position of the fragments. """ 2 | 3 | from __future__ import print_function, division, absolute_import 4 | 5 | import os 6 | 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.interpolate 10 | 11 | from wmpl.Formats.Vid import readVid 12 | from wmpl.Formats.Met import loadMet 13 | from wmpl.Utils.OSTools import mkdirP 14 | from wmpl.Utils.Pickling import loadPickle 15 | from wmpl.Utils.TrajConversions import unixTime2Date, unixTime2JD 16 | 17 | 18 | def markFragments(out_dir, vid, met, site_id, traj=None, crop=None): 19 | """ Mark fragments on .vid file frames and save them as images. If the trajectory structure is given, 20 | the approximate height at every frame will be plotted on the image as well. 21 | 22 | Arguments: 23 | out_dir: [str] Path to the directory where the images will be saved. 24 | vid: [VidStruct object] vid object containing loaded video frames. 25 | met: [MetStruct object] met object containing picks. 26 | site_id: [str] ID of the site used for loading proper picks from the met object. 27 | 28 | Keyword arguments: 29 | traj: [Trajectory object] Optional trajectory object from which the height of the meteor at evey frame 30 | will be estimated and plotted on the image. None by default (no height will be plotted on the 31 | image). 32 | crop: [list] A list of Xmin, Xmax, Ymin, Ymax crop window. 33 | 34 | Return: 35 | None 36 | """ 37 | 38 | 39 | # Make the output directory 40 | mkdirP(out_dir) 41 | 42 | # Extract site picks 43 | picks = np.array(met.picks[site_id]) 44 | 45 | # Find unique fragments 46 | fragments = np.unique(picks[:, 1]) 47 | 48 | 49 | # Generate a unique color for every fragment 50 | colors = plt.cm.rainbow(np.linspace(0, 1, len(fragments))) 51 | 52 | # Create a dictionary for every fragment-color pair 53 | colors_frags = {frag: color for frag, color in zip(fragments, colors)} 54 | 55 | 56 | 57 | ### Height fit ### 58 | 59 | height_interp = None 60 | 61 | # If the trajectory was given, do interpolation on time vs. height 62 | if traj is not None: 63 | 64 | 65 | jd_data = [] 66 | height_data = [] 67 | 68 | # Take all observed points on the trajectory 69 | for obs in traj.observations: 70 | 71 | for jd, height in zip(obs.JD_data, obs.model_ht): 72 | jd_data.append(jd) 73 | height_data.append(height) 74 | 75 | 76 | jd_data = np.array(jd_data) 77 | height_data = np.array(height_data) 78 | 79 | # Sort the points by Julian date 80 | jd_ht_data = np.c_[jd_data, height_data] 81 | jd_ht_data = jd_ht_data[np.argsort(jd_ht_data[:, 0])] 82 | jd_data, height_data = jd_ht_data.T 83 | 84 | # Initerpolate Julian date vs. heights 85 | height_interp = scipy.interpolate.PchipInterpolator(jd_data, height_data, extrapolate=True) 86 | 87 | # # Plot JD vs. height 88 | # plt.scatter(jd_data, height_data) 89 | 90 | # jd_plot = np.linspace(min(jd_data), max(jd_data), 1000) 91 | # plt.plot(jd_plot, height_interp(jd_plot)) 92 | 93 | # plt.show() 94 | 95 | 96 | 97 | 98 | ################## 99 | 100 | 101 | frag_count = 1 102 | frag_dict = {} 103 | 104 | # Go through all frames 105 | for i, fr_vid in enumerate(vid.frames): 106 | 107 | 108 | # Determine crop 109 | if crop is not None: 110 | xmin, xmax, ymin, ymax = crop 111 | 112 | if xmin == None: 113 | xmin = 0 114 | 115 | if xmax == None: 116 | xmax = fr_vid.wid 117 | 118 | if ymin == None: 119 | ymin = 0 120 | 121 | if ymax == None: 122 | ymax == fr_vid.ht 123 | 124 | 125 | else: 126 | xmin, ymin = 0, 0 127 | xmax, ymax = fr_vid.wid, fr_vid.ht 128 | 129 | 130 | 131 | # LIMIT FRAMES 132 | if (i < 218) or (i > 512): 133 | continue 134 | 135 | 136 | # CROP IMAGE 137 | if crop is not None: 138 | img = fr_vid.img_data[ymin:ymax, xmin:xmax] 139 | 140 | else: 141 | img = fr_vid.img_data 142 | 143 | 144 | # Plot the frame 145 | plt.imshow(img, cmap='gray', vmin=0, vmax=255, interpolation='nearest') 146 | 147 | 148 | # Convert the frame time from UNIX timestamp to human readable format 149 | timestamp = unixTime2Date(fr_vid.ts, fr_vid.tu, dt_obj=True).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] \ 150 | + ' UTC' 151 | 152 | # Calculate the Julian date of every frame 153 | frame_jd = unixTime2JD(fr_vid.ts, fr_vid.tu) 154 | 155 | 156 | # Plot the timestamp 157 | plt.text(10, ymax - 10, timestamp, ha='left', va='bottom', color='0.7', size=10) 158 | 159 | 160 | # If the trajectory was given, plot the meteor height at every frame 161 | if height_interp is not None: 162 | 163 | # Get the height at the given frame 164 | frame_height = height_interp(frame_jd) 165 | 166 | height_str = 'Height = {:7.3f} km'.format(frame_height/1000) 167 | 168 | # Plot the height 169 | plt.text(img.shape[1] - 10, img.shape[0] - 10, height_str, ha='right', va='bottom', color='0.7', size=10) 170 | 171 | 172 | # Hide axes 173 | plt.gca().set_axis_off() 174 | 175 | 176 | 177 | 178 | # Extract picks on this frame 179 | fr_picks = picks[picks[:, 0] == i] 180 | 181 | # Check if there are any picks on the this frame and plot them by fragment number 182 | if len(fr_picks): 183 | 184 | # Go through every pick 185 | for pick in fr_picks: 186 | 187 | # Choose the appropriate colour by fragment 188 | frag_color = colors_frags[pick[1]] 189 | 190 | # Extract pick coordinates and apply crop 191 | cx = pick[2] - xmin 192 | cy = pick[3] - ymin 193 | 194 | # Extract fragment number 195 | frag = pick[1] 196 | 197 | # Assign this fragment a sequential number if it was not yet plotted 198 | if not frag in frag_dict: 199 | frag_dict[frag] = frag_count 200 | frag_count += 1 201 | 202 | 203 | # Look up the fragment sequential number 204 | frag_no = frag_dict[frag] 205 | 206 | # Move the markers a bit to the left/right, intermittently for every fragment 207 | if frag%2 == 0: 208 | cx -= 10 209 | txt_cx = cx - 5 210 | marker = '>' 211 | txt_align = 'right' 212 | else: 213 | cx += 10 214 | txt_cx = cx + 5 215 | marker = '<' 216 | txt_align = 'left' 217 | 218 | 219 | # Plot the pick 220 | plt.scatter(cx, cy - 1, c=frag_color, marker=marker, s=5) 221 | 222 | # Plot the fragment number 223 | plt.text(txt_cx, cy, str(int(frag_no)), horizontalalignment=txt_align, verticalalignment='center', color=frag_color, size=8) 224 | 225 | 226 | # Set limits 227 | plt.xlim([0, img.shape[1]]) 228 | plt.ylim([img.shape[0], 0]) 229 | 230 | # Save the plot 231 | extent = plt.gca().get_window_extent().transformed(plt.gcf().dpi_scale_trans.inverted()) 232 | plt.savefig(os.path.join(out_dir, str(i) + '.png'), transparent=True, bbox_inches=extent, pad_inches=0, dpi=300) 233 | 234 | plt.clf() 235 | #plt.show() 236 | 237 | 238 | # IMPORTANT!!! COPY THE OUTPUT OF THIS TO ProjectNarrowPicksToWideTraj!!!! 239 | # Print the fragment dictionary, where the original fragment IDs are mapped into sequential numbers 240 | print('FRAG DICT:', frag_dict) 241 | 242 | 243 | 244 | 245 | if __name__ == "__main__": 246 | 247 | 248 | # Main directory 249 | dir_path = "../MetalPrepare/20170721_070420_met" 250 | 251 | # Output directory 252 | out_dir = os.path.join(dir_path, 'marked_frames') 253 | 254 | # .met file containing narrow-field picks 255 | met_file = 'state_fragment_picks.met' 256 | 257 | # .vid file 258 | vid_file = os.path.join('cut_20170721_070418_01T', 'ev_20170721_070420A_01T.vid') 259 | 260 | # Trajectory file 261 | traj_file = os.path.join('Monte Carlo', '20170721_070419_mc_trajectory.pickle') 262 | 263 | 264 | ########################################################################################################## 265 | 266 | # Load the MET file 267 | met = loadMet(dir_path, met_file) 268 | 269 | # Load the vid file 270 | vid = readVid(dir_path, vid_file) 271 | 272 | # Load the trajectory file 273 | traj = loadPickle(dir_path, traj_file) 274 | 275 | # ID of the site used for loading proper picks from the met object 276 | site_id = '1' 277 | 278 | # Generate images with marked fragments on them 279 | markFragments(out_dir, vid, met, site_id, traj=traj, crop=[175, None, None, 340]) 280 | -------------------------------------------------------------------------------- /wmpl/CAMO/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/CAMO/__init__.py -------------------------------------------------------------------------------- /wmpl/CAMO/docs/narrow_project_example_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "July 21, 2017 event", 3 | "dir_path" : "/mnt/bulk/mirfitprepare/20170721_070420_met", 4 | "met_file" : "state_fragment_picks.met", 5 | "traj_file": "Monte Carlo/20170721_070419_mc_trajectory.pickle", 6 | "frag_dict": {"0.0": 10, "1.0": 11, "2.0": 6, "3.0": 9, "4.0": 8, "5.0": 3, "6.0": 2, "7.0": 4, "8.0": 1, "9.0": 5, "10.0": 12, "11.0": 7}, 7 | "fragmentation_points": { 8 | "1": { 9 | "2" : [0.0854, [2, 3]], 10 | "10": [0.066, [10, 11]], 11 | "7": [0.246, [7, 9, 12]], 12 | "4": [0.26, [4, 3]], 13 | "8": [0.046, [8, 5]] 14 | } 15 | }, 16 | "fit_full_exp_model": true, 17 | "v_init_adjust": 0 18 | } -------------------------------------------------------------------------------- /wmpl/CMN/CMNCalcTrajectory.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | 5 | import matplotlib.pyplot as plt 6 | 7 | from wmpl.CMN.CMNFormats import loadINF 8 | from wmpl.Trajectory.Trajectory import Trajectory 9 | from wmpl.Trajectory.GuralTrajectory import GuralTrajectory 10 | 11 | 12 | if __name__ == "__main__": 13 | 14 | dir_path = "CMN" + os.sep + "cmn_fireball_2017030506" 15 | files = ['M_2017030506APO0001.txt', 'M_2017030506KOP0001.txt'] 16 | 17 | 18 | observations = [] 19 | 20 | # Load observed data 21 | for file_name in files: 22 | 23 | observations.append(loadINF(os.path.join(dir_path, file_name))) 24 | 25 | 26 | # Get the reference JD from the first site 27 | jdt_ref = observations[0].jd_data[0] 28 | 29 | max_first = 0 30 | 31 | # Recalculate time data 32 | for obs in observations: 33 | obs.time_data = (obs.jd_data - jdt_ref)*86400.0 34 | 35 | # # Normalize all time data so their beginning is at 0 36 | # obs.time_data -= obs.time_data[0] 37 | 38 | 39 | 40 | # sys.exit() 41 | 42 | 43 | # Init the Trajectory solver 44 | traj_solve = Trajectory(jdt_ref, max_toffset=1, meastype=1, estimate_timing_vel=False) 45 | 46 | #traj_solve = GuralTrajectory(len(observations), jdt_ref, velmodel=1, max_toffset=1.0, nummonte=1, meastype=1, verbose=1) 47 | 48 | # Infill the observed data 49 | for obs in observations: 50 | traj_solve.infillTrajectory(obs.ra_data, obs.dec_data, obs.time_data, obs.lat, obs.lon, obs.ele) 51 | 52 | 53 | # Solve the trajectory 54 | traj_solve.run() 55 | -------------------------------------------------------------------------------- /wmpl/CMN/CMNFormats.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from wmpl.Utils.TrajConversions import J2000_JD, equatorialCoordPrecession 4 | 5 | class InfData(object): 6 | 7 | def __init__(self, station_id, lat, lon, ele, jd_data, ra_data, dec_data): 8 | 9 | self.station_id = station_id 10 | self.lat = lat 11 | self.lon = lon 12 | self.ele = ele 13 | self.jd_data = np.array(jd_data) 14 | self.ra_data = np.array(ra_data) 15 | self.dec_data = np.array(dec_data) 16 | 17 | self.time_data = None 18 | 19 | 20 | 21 | 22 | def loadINF(file_name): 23 | """ Loads data from the Croatian Meteor Network's INF file. """ 24 | 25 | with open(file_name) as f: 26 | 27 | # Skip first 2 lines 28 | for i in range(2): 29 | f.readline() 30 | 31 | # Read the station ID 32 | station_id = f.readline().split()[1] 33 | 34 | # Read longitude 35 | lon = np.radians(float(f.readline().split()[1])) 36 | 37 | # Read latitude 38 | lat = np.radians(float(f.readline().split()[1])) 39 | 40 | # Read elevation 41 | ele = float(f.readline().split()[1]) 42 | 43 | jd_data = [] 44 | ra_data = [] 45 | dec_data = [] 46 | 47 | # Load JD, Ra and Dec data 48 | for line in f: 49 | 50 | if not line: 51 | continue 52 | 53 | line = line.split() 54 | 55 | line = map(float, line) 56 | 57 | # Extract Julian date 58 | jd = line[0] 59 | 60 | # Extract right ascension 61 | ra = np.radians(line[1]) 62 | 63 | # Extract declination 64 | dec = np.radians(line[2]) 65 | 66 | # Precess ra and dec to epoch of date (from J2000.0 epoch) 67 | ra, dec = equatorialCoordPrecession(J2000_JD.days, jd, ra, dec) 68 | 69 | jd_data.append(jd) 70 | ra_data.append(ra) 71 | dec_data.append(dec) 72 | 73 | 74 | inf = InfData(station_id, lat, lon, ele, jd_data, ra_data, dec_data) 75 | 76 | return inf 77 | 78 | 79 | 80 | if __name__ == "__main__": 81 | 82 | pass -------------------------------------------------------------------------------- /wmpl/CMN/RMSCMNTrajectory.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import os 4 | 5 | import numpy as np 6 | 7 | from wmpl.CMN.CMNFormats import loadINF 8 | from wmpl.Formats.CAMS import loadFTPDetectInfo 9 | 10 | from wmpl.Trajectory.Trajectory import Trajectory 11 | from wmpl.Utils.TrajConversions import equatorialCoordPrecession_vect, J2000_JD 12 | 13 | 14 | if __name__ == "__main__": 15 | 16 | 17 | ftp_stations = {'HR0002': [np.radians(45.34813055555556), np.radians(14.050744444444444), 337]} 18 | 19 | #ftpdetectinfo_file = "C:\\Users\\delorayn1\\Desktop\\HR0002_20180119_162419_928144_detected\\FTPdetectinfo_HR0002_20180119_162419_928144_temporal.txt" 20 | ftpdetectinfo_file = "C:\\Users\\delorayn1\\Desktop\\HR0002_20180119_162419_928144_detected\\FTPdetectinfo_HR0002_20180121_162654_728788_temporal.txt" 21 | 22 | 23 | # Load meteor from FTPdetectinfo 24 | ftp = loadFTPDetectInfo(ftpdetectinfo_file, ftp_stations) 25 | 26 | inf_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\CMN inf files" 27 | 28 | 29 | ## METEOR 1 30 | #inf_files = ["M_2018011920DUI0003.inf", "M_2018011920VIB0002.inf"] 31 | #inf_files = ["M_2018011920DUI0003.inf"] 32 | inf_files = ["M_2018011920VIB0002.inf"] 33 | 34 | ftp_meteor = ftp[2] 35 | 36 | output_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\FF_HR0002_20180119_210212_876_0414720" 37 | ############ 38 | 39 | 40 | ## METEOR 2 41 | inf_files = ["M_2018011920DUI0012.inf"] 42 | 43 | ftp_meteor = ftp[8] 44 | 45 | output_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\FF_HR0002_20180119_233859_048_0641536.fits" 46 | ############ 47 | 48 | 49 | ## METEOR 3 50 | inf_files = ["M_2018011920MLA0009.inf"] 51 | 52 | ftp_meteor = ftp[17] 53 | 54 | output_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\FF_HR0002_20180120_014014_887_0817664" 55 | ############ 56 | 57 | 58 | ## METEOR 4 59 | inf_files = ["M_2018011920CIO0006.inf"] 60 | 61 | ftp_meteor = ftp[29] 62 | 63 | output_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\FF_HR0002_20180120_050021_373_1105920" 64 | ############ 65 | 66 | 67 | ######### 68 | 69 | ## METEOR 5 70 | inf_files = ["M_2018012122VIB0002.inf"] 71 | 72 | ftp_meteor = ftp[7] 73 | 74 | output_dir = "C:\\Users\\delorayn1\\Desktop\\ImplementCorrection\\FF_HR0002_20180121_191911_939_0254720" 75 | ############ 76 | 77 | 78 | 79 | # reference JD 80 | jdt_ref = ftp_meteor.jdt_ref - (ftp_meteor.time_data[0] - 10.0)/86400 81 | ftp_meteor.time_data -= ftp_meteor.time_data[0] 82 | 83 | # Init the new trajectory 84 | traj = Trajectory(jdt_ref, meastype=1, max_toffset=15.0, output_dir=output_dir, monte_carlo=True) 85 | 86 | 87 | # Precess RA/Dec from J2000 to epoch of date 88 | ra_data, dec_data = equatorialCoordPrecession_vect(J2000_JD.days, ftp_meteor.jdt_ref + ftp_meteor.time_data/86400, ftp_meteor.ra_data, ftp_meteor.dec_data) 89 | 90 | ra_data, dec_data = ftp_meteor.ra_data, ftp_meteor.dec_data 91 | # Infill trajectory with RMS data 92 | traj.infillTrajectory(ra_data, dec_data, ftp_meteor.time_data, \ 93 | ftp_meteor.latitude, ftp_meteor.longitude, ftp_meteor.height, station_id=ftp_meteor.station_id) 94 | 95 | 96 | # Load INF files 97 | for inf in inf_files: 98 | 99 | inf_met = loadINF(os.path.join(inf_dir, inf)) 100 | 101 | time_data = (inf_met.jd_data - jdt_ref)*86400 102 | 103 | print('INF time:', time_data) 104 | print('INF ra, dec:', np.degrees(inf_met.ra_data), np.degrees(inf_met.dec_data)) 105 | 106 | # Precess RA/Dec from J2000 to epoch of date 107 | ra_data, dec_data = equatorialCoordPrecession_vect(J2000_JD.days, inf_met.jd_data, inf_met.ra_data, \ 108 | inf_met.dec_data) 109 | #ra_data, dec_data = inf_met.ra_data, inf_met.dec_data 110 | 111 | traj.infillTrajectory(ra_data, dec_data, time_data, inf_met.lat, inf_met.lon, inf_met.ele, \ 112 | station_id=inf_met.station_id) 113 | 114 | 115 | traj.run() 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /wmpl/CMN/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/CMN/__init__.py -------------------------------------------------------------------------------- /wmpl/CMN/cmn_fireball_2017030506/M_2017030506KOP0001.txt: -------------------------------------------------------------------------------- 1 | Date: 2017030506 2 | Time: 22:50:11.919 3 | Station_Code: KOP 4 | Long: 016.841214 E 5 | Lati: 046.163564 N 6 | Height: 0146 m 7 | 2457818.4515268402 157.352 +22.663 +8.0 8 | 2457818.4515270717 157.475 +22.571 +9.2 9 | 2457818.4515273031 157.659 +22.461 -1.9 10 | 2457818.4515275345 157.866 +22.341 -3.6 11 | 2457818.4515277664 158.170 +22.264 -4.4 12 | 2457818.4515279979 158.423 +22.152 -6.0 13 | 2457818.4515282293 158.675 +22.075 -4.2 14 | 2457818.4515284607 159.009 +21.986 -5.2 15 | 2457818.4515286922 159.186 +21.882 -3.2 16 | 2457818.4515289236 159.517 +21.810 -4.0 17 | 2457818.4515291550 159.750 +21.685 -5.5 18 | 2457818.4515293865 159.994 +21.596 -4.0 19 | 2457818.4515296179 160.262 +21.532 -4.6 20 | 2457818.4515298493 160.455 +21.389 -4.2 21 | 2457818.4515300812 160.766 +21.323 -5.0 22 | 2457818.4515303127 161.004 +21.213 -5.8 23 | 2457818.4515305441 161.253 +21.138 -3.2 24 | 2457818.4515307755 161.522 +21.017 -3.9 25 | 2457818.4515310070 161.713 +20.962 -2.7 26 | 2457818.4515312384 162.012 +20.819 -2.6 27 | 2457818.4515314698 162.210 +20.727 -5.0 28 | 2457818.4515317013 162.463 +20.649 -3.7 29 | 2457818.4515319327 162.725 +20.534 -5.2 30 | 2457818.4515321641 162.929 +20.421 -4.1 31 | 2457818.4515323960 163.222 +20.331 -2.9 32 | 2457818.4515326275 163.447 +20.243 -5.7 33 | 2457818.4515328589 163.667 +20.092 -3.7 34 | 2457818.4515330903 163.955 +20.008 -3.3 35 | 2457818.4515333218 164.100 +19.907 -4.0 36 | 2457818.4515335532 164.396 +19.822 -2.4 37 | 2457818.4515337846 164.569 +19.699 -4.0 38 | 2457818.4515340161 164.851 +19.614 -4.6 39 | 2457818.4515342475 165.054 +19.480 -5.7 40 | 2457818.4515344794 165.312 +19.414 -6.4 41 | 2457818.4515347108 165.534 +19.286 -7.0 42 | 2457818.4515349423 165.767 +19.203 -6.5 43 | 2457818.4515351737 166.017 +19.072 -6.2 44 | 2457818.4515354051 166.229 +19.008 -4.8 45 | 2457818.4515356366 166.424 +18.881 -4.6 46 | 2457818.4515358680 166.660 +18.769 -5.3 47 | 2457818.4515360994 166.803 +18.646 -3.7 48 | 2457818.4515363309 167.088 +18.546 -4.4 49 | 2457818.4515365623 167.163 +18.475 -3.7 50 | 2457818.4515367942 167.474 +18.355 -4.1 51 | 2457818.4515370256 167.597 +18.279 -2.6 52 | 2457818.4515372571 167.853 +18.135 -2.9 53 | 2457818.4515374885 168.033 +18.044 -5.2 54 | 2457818.4515377199 168.230 +17.943 -1.8 55 | 2457818.4515379514 168.474 +17.818 -3.7 56 | 2457818.4515381828 168.673 +17.752 -5.0 57 | 2457818.4515384142 168.885 +17.639 -3.1 58 | 2457818.4515386457 169.126 +17.532 -4.7 59 | 2457818.4515388771 169.318 +17.429 -3.8 60 | 2457818.4515391090 169.558 +17.340 -3.9 61 | 2457818.4515393404 169.814 +17.240 -4.3 62 | 2457818.4515395719 169.936 +17.135 -6.1 63 | 2457818.4515398033 170.182 +17.054 -5.4 64 | 2457818.4515400347 170.387 +16.950 -7.7 65 | 2457818.4515402662 170.546 +16.860 -7.3 66 | 2457818.4515404976 170.782 +16.724 -8.3 67 | 2457818.4515407290 170.906 +16.674 -3.9 68 | 2457818.4515409605 171.183 +16.589 -2.9 69 | 2457818.4515411919 171.218 +16.494 -4.8 70 | 2457818.4515414238 171.439 +16.351 -5.5 71 | 2457818.4515416552 171.604 +16.278 -4.8 72 | 2457818.4515418867 171.808 +16.155 -4.8 73 | 2457818.4515421181 172.108 +16.095 -2.0 74 | 2457818.4515423495 172.210 +15.974 -3.2 75 | 2457818.4515425810 172.504 +15.888 -0.7 76 | 2457818.4515428124 172.591 +15.796 -1.7 77 | 2457818.4515430438 172.842 +15.677 -0.2 78 | 2457818.4515432753 172.911 +15.597 -0.9 79 | 2457818.4515435072 173.161 +15.479 -0.1 80 | 2457818.4515437386 173.269 +15.431 -0.6 81 | 2457818.4515439700 173.492 +15.334 +9.7 82 | 2457818.4515442015 173.557 +15.236 -1.4 83 | 2457818.4515444329 173.812 +15.127 -0.1 84 | 2457818.4515446643 173.862 +15.048 -1.2 85 | 2457818.4515448958 174.128 +14.975 +9.7 86 | 2457818.4515451272 174.191 +14.877 -0.7 87 | 2457818.4515453586 174.394 +14.793 +9.4 88 | 2457818.4515455901 174.503 +14.718 -0.3 89 | 2457818.4515458220 174.719 +14.623 +0.4 90 | 2457818.4515460534 174.833 +14.530 -0.5 91 | 2457818.4515462848 175.054 +14.427 +9.5 92 | 2457818.4515465163 175.148 +14.381 +0.0 93 | 2457818.4515467477 175.380 +14.304 +9.4 94 | 2457818.4515469791 175.450 +14.216 +0.2 95 | 2457818.4515472106 175.682 +14.094 +9.5 96 | 2457818.4515474420 175.650 +14.052 +9.0 97 | 2457818.4515476734 175.877 +13.913 +8.9 98 | 2457818.4515479049 175.862 +13.870 +8.4 99 | 2457818.4515481368 176.147 +13.790 +9.3 100 | 2457818.4515483682 176.202 +13.776 +8.2 101 | 2457818.4515485996 176.428 +13.648 +8.9 102 | 2457818.4515488311 176.558 +13.645 +8.8 103 | 2457818.4515490625 176.756 +13.512 +9.2 104 | 2457818.4515492939 176.744 +13.441 +9.1 105 | 2457818.4515495254 176.987 +13.385 +7.8 106 | 2457818.4515497568 177.026 +13.283 +9.0 107 | 2457818.4515499882 177.183 +13.208 +8.3 108 | 2457818.4515502201 177.306 +13.127 +8.7 109 | 2457818.4515504516 177.446 +13.107 +7.5 110 | 2457818.4515506830 177.573 +13.009 +8.1 111 | 2457818.4515509144 177.802 +12.948 +9.3 112 | 2457818.4515511459 177.891 +12.915 +7.4 113 | 2457818.4515513773 178.073 +12.823 -0.9 114 | 2457818.4515516087 178.148 +12.755 +0.2 115 | 2457818.4515518402 178.298 +12.666 +9.6 116 | 2457818.4515520716 178.417 +12.632 -1.2 117 | 2457818.4515523030 178.537 +12.554 -1.9 118 | 2457818.4515525349 178.665 +12.484 +9.5 119 | 2457818.4515527664 178.798 +12.442 -0.0 120 | 2457818.4515529978 178.897 +12.348 -1.0 121 | 2457818.4515532292 179.018 +12.289 +8.7 122 | 2457818.4515534607 179.135 +12.292 +8.3 123 | 2457818.4515536921 179.237 +12.172 -1.1 124 | 2457818.4515539235 179.353 +12.132 +8.8 125 | 2457818.4515541550 179.488 +12.073 -0.1 126 | 2457818.4515543864 179.597 +11.998 +8.9 127 | 2457818.4515546178 179.712 +11.914 +8.0 128 | 2457818.4515548497 179.840 +11.945 +7.8 129 | 2457818.4515550812 179.895 +11.829 +8.7 130 | 2457818.4515553126 180.042 +11.762 +8.9 131 | 2457818.4515555440 180.128 +11.689 +7.6 132 | 2457818.4515557755 180.277 +11.676 +8.1 133 | 2457818.4515560069 180.276 +11.632 +7.3 134 | 2457818.4515562383 180.485 +11.574 +8.1 135 | 2457818.4515564698 180.484 +11.530 +8.1 136 | 2457818.4515567012 180.606 +11.413 +6.9 137 | 2457818.4515569326 180.718 +11.411 +8.4 138 | 2457818.4515571645 180.885 +11.337 +7.9 139 | 2457818.4515573960 180.968 +11.336 +6.9 140 | 2457818.4515576274 181.053 +11.273 +7.1 141 | 2457818.4515578588 181.154 +11.210 +8.0 142 | 2457818.4515580903 181.282 +11.173 +7.6 143 | 2457818.4515583217 181.367 +11.066 +6.8 144 | 2457818.4515585531 181.446 +11.091 +8.2 145 | 2457818.4515587846 181.514 +11.055 +6.3 146 | 2457818.4515590160 181.626 +11.054 +6.5 147 | 2457818.4515592479 181.696 +10.991 +7.8 148 | 2457818.4515594793 181.857 +10.893 +6.7 149 | 2457818.4515597108 181.933 +10.821 +6.9 150 | 2457818.4515599422 182.034 +10.802 +8.4 151 | 2457818.4515606365 182.248 +10.711 +7.4 152 | 2457818.4515608680 182.398 +10.594 +6.3 153 | 2457818.4515610994 182.480 +10.558 +6.5 154 | 2457818.4515613308 182.555 +10.521 +7.2 155 | 2457818.4515615627 182.622 +10.476 +5.6 156 | 2457818.4515617942 182.744 +10.412 +7.4 157 | 2457818.4515620256 182.849 +10.420 +7.7 158 | 2457818.4515622570 182.894 +10.419 +6.8 159 | 2457818.4515624885 183.017 +10.319 +7.4 160 | 2457818.4515627199 183.083 +10.273 +6.8 161 | 2457818.4515629513 183.172 +10.271 +7.3 162 | 2457818.4515631828 183.282 +10.179 +7.3 163 | 2457818.4515634142 183.357 +10.142 +7.6 164 | 2457818.4515636456 183.504 +10.130 +7.1 165 | -------------------------------------------------------------------------------- /wmpl/Formats/Pickle.py: -------------------------------------------------------------------------------- 1 | """ Reruns the trajectory solution from a trajectory pickle file. """ 2 | 3 | from __future__ import print_function, division, absolute_import 4 | 5 | import os 6 | import sys 7 | 8 | import numpy as np 9 | 10 | from wmpl.Formats.EvUWO import writeEvFile 11 | from wmpl.Formats.GenericFunctions import addSolverOptions 12 | from wmpl.Utils.Pickling import loadPickle 13 | from wmpl.Utils.TrajConversions import jd2Date 14 | from wmpl.Trajectory.Trajectory import Trajectory 15 | from wmpl.Trajectory.GuralTrajectory import GuralTrajectory 16 | 17 | 18 | def dumpAsEvFiles(dir_path, file_name): 19 | """ Dump the given pickle file as UWO-style ev_* file. """ 20 | 21 | # Load the pickles trajectory 22 | traj = loadPickle(dir_path, file_name) 23 | 24 | 25 | # Dump the results as a UWO-style ev file 26 | 27 | year, month, day, hour, minute, second, _ = jd2Date(traj.jdt_ref) 28 | 29 | for i, obs in enumerate(traj.observations): 30 | 31 | # Construct file name 32 | date_str = "{:4d}{:02d}{:02d}_{:02d}{:02d}{:02d}A_{:s}".format(year, month, day, hour, minute, second, \ 33 | obs.station_id) 34 | 35 | ev_file_name = 'ev_' + date_str + '.txt' 36 | 37 | # Convert azimuth and altitude to theta/tphi 38 | theta_data = np.pi/2.0 - obs.elev_data 39 | phi_data = (np.pi/2.0 - obs.azim_data)%(2*np.pi) 40 | 41 | # Write the ev_* file 42 | writeEvFile(dir_path, ev_file_name, traj.jdt_ref, str(i), obs.lat, obs.lon, obs.ele, 43 | obs.time_data, theta_data, phi_data) 44 | 45 | 46 | 47 | 48 | def solveTrajectoryPickle(dir_path, file_name, only_plot=False, solver='original', **kwargs): 49 | """ Rerun the trajectory solver on the given trajectory pickle file. """ 50 | 51 | 52 | # Load the pickles trajectory 53 | traj_p = loadPickle(dir_path, file_name) 54 | 55 | # Run the PyLIG trajectory solver 56 | if solver == 'original': 57 | 58 | # Given the max time offset from the pickle file and input, use the larger one of the two 59 | max_toffset = traj_p.max_toffset 60 | if "max_toffset" in kwargs: 61 | 62 | if (kwargs["max_toffset"] is not None) and (traj_p.max_toffset is not None): 63 | 64 | max_toffset = max(traj_p.max_toffset, kwargs["max_toffset"]) 65 | 66 | # Remove the max time offset from the list of keyword arguments 67 | kwargs.pop("max_toffset", None) 68 | 69 | 70 | # Preserve the trajectory ID 71 | if hasattr(traj_p, "traj_id"): 72 | traj_id = traj_p.traj_id 73 | else: 74 | traj_id = None 75 | 76 | 77 | # Reinitialize the trajectory solver 78 | meastype = 2 79 | traj = Trajectory(traj_p.jdt_ref, output_dir=dir_path, max_toffset=max_toffset, \ 80 | meastype=meastype, traj_id=traj_id, **kwargs) 81 | 82 | 83 | # Fill the observations 84 | for obs in traj_p.observations: 85 | traj.infillWithObs(obs, meastype=meastype) 86 | 87 | 88 | elif solver == 'gural': 89 | 90 | # Init the Gural solver 91 | traj = GuralTrajectory(len(traj_p.observations), traj_p.jdt_ref, velmodel=3, \ 92 | max_toffset=traj_p.max_toffset, meastype=2, output_dir=dir_path, verbose=True) 93 | 94 | 95 | # Fill the observations 96 | for obs in traj_p.observations: 97 | 98 | traj.infillTrajectory(obs.azim_data, obs.elev_data, obs.time_data, obs.lat, obs.lon, obs.ele) 99 | 100 | 101 | else: 102 | print('Unrecognized solver:', solver) 103 | 104 | 105 | 106 | if only_plot: 107 | 108 | # Set saving results 109 | traj_p.save_results = True 110 | 111 | # Override plotting options with given options 112 | traj_p.plot_all_spatial_residuals = kwargs["plot_all_spatial_residuals"] 113 | traj_p.plot_file_type = kwargs["plot_file_type"] 114 | 115 | # Show the plots 116 | traj_p.savePlots(dir_path, traj_p.file_name, show_plots=kwargs["show_plots"]) 117 | 118 | 119 | # Recompute the trajectory 120 | else: 121 | 122 | # Run the trajectory solver 123 | traj = traj.run() 124 | 125 | 126 | return traj 127 | 128 | 129 | 130 | 131 | if __name__ == "__main__": 132 | 133 | 134 | import argparse 135 | 136 | 137 | ### COMMAND LINE ARGUMENTS 138 | 139 | # Init the command line arguments parser 140 | arg_parser = argparse.ArgumentParser(description=""" Re-run the Monte Carlo trajectory solver on a trajectory pickle file.""", 141 | formatter_class=argparse.RawTextHelpFormatter) 142 | 143 | arg_parser.add_argument('input_file', type=str, help='Path to the .pickle file.') 144 | 145 | # Add other solver options 146 | arg_parser = addSolverOptions(arg_parser) 147 | 148 | arg_parser.add_argument('-a', '--onlyplot', \ 149 | help='Do not recompute the trajectory, just show and save the plots.', action="store_true") 150 | 151 | # Parse the command line arguments 152 | cml_args = arg_parser.parse_args() 153 | 154 | ############################ 155 | 156 | 157 | ### Parse command line arguments ### 158 | 159 | max_toffset = None 160 | if cml_args.maxtoffset: 161 | max_toffset = cml_args.maxtoffset[0] 162 | 163 | velpart = None 164 | if cml_args.velpart: 165 | velpart = cml_args.velpart 166 | 167 | vinitht = None 168 | if cml_args.vinitht: 169 | vinitht = cml_args.vinitht[0] 170 | 171 | ### ### 172 | 173 | 174 | # Split the input directory and the file 175 | if os.path.isfile(cml_args.input_file): 176 | 177 | dir_path, file_name = os.path.split(cml_args.input_file) 178 | 179 | else: 180 | print('Input file: {:s}'.format(cml_args.input_file)) 181 | print('The given input file does not exits!') 182 | sys.exit() 183 | 184 | # Run the solver 185 | solveTrajectoryPickle(dir_path, file_name, only_plot=cml_args.onlyplot, solver=cml_args.solver, \ 186 | max_toffset=max_toffset, monte_carlo=(not cml_args.disablemc), mc_runs=cml_args.mcruns, \ 187 | geometric_uncert=cml_args.uncertgeom, gravity_correction=(not cml_args.disablegravity), \ 188 | plot_all_spatial_residuals=cml_args.plotallspatial, plot_file_type=cml_args.imgformat, \ 189 | show_plots=(not cml_args.hideplots), v_init_part=velpart, v_init_ht=vinitht, \ 190 | show_jacchia=cml_args.jacchia, \ 191 | estimate_timing_vel=(False if cml_args.notimefit is None else cml_args.notimefit), \ 192 | fixed_times=cml_args.fixedtimes, mc_noise_std=cml_args.mcstd) -------------------------------------------------------------------------------- /wmpl/Formats/RMSJSON.py: -------------------------------------------------------------------------------- 1 | """ Runs the trajectory solver on RMS JSON files. """ 2 | 3 | 4 | import os 5 | import sys 6 | import glob 7 | import json 8 | import datetime 9 | import argparse 10 | 11 | import numpy as np 12 | 13 | from wmpl.Formats.GenericFunctions import addSolverOptions, solveTrajectoryGeneric, MeteorObservation, \ 14 | prepareObservations 15 | from wmpl.Utils.TrajConversions import jd2Date 16 | 17 | 18 | def saveJSON(dir_path, meteor_list): 19 | """ Save observations in the RMS JSON format. 20 | 21 | Arguments: 22 | dir_path: [str] Path to where the JSON files will be saved. 23 | meteor_list: [list of MeteorObservation objects] 24 | 25 | """ 26 | 27 | 28 | for meteor in meteor_list: 29 | 30 | # Construct the file name 31 | dt = jd2Date(meteor.jdt_ref, dt_obj=True) 32 | 33 | json_name = "{:s}_{:s}_picks.json".format(dt.strftime("%Y%m%d_%H%M%S.%f"), meteor.station_id) 34 | 35 | # Init JSON dict 36 | json_dict = {} 37 | 38 | json_dict["fps"] = meteor.fps 39 | json_dict["jdt_ref"] = meteor.jdt_ref 40 | json_dict["meastype"] = 1 # ra/dec 41 | 42 | json_dict["centroids_labels"] = ["Time (s)", 43 | "X (px)", 44 | "Y (px)", 45 | "RA (deg)", 46 | "Dec (deg)", 47 | "Summed intensity", 48 | "Magnitude" 49 | ] 50 | 51 | # Construct station info 52 | station = {} 53 | station["lat"] = np.degrees(meteor.latitude) 54 | station["lon"] = np.degrees(meteor.longitude) 55 | station["elev"] = meteor.height 56 | station["station_id"] = meteor.station_id 57 | json_dict["station"] = station 58 | 59 | 60 | # Construct the JSON data 61 | centroids = np.c_[meteor.time_data, meteor.x_data, meteor.y_data, np.degrees(meteor.ra_data), \ 62 | np.degrees(meteor.dec_data), np.ones_like(meteor.time_data), meteor.mag_data] 63 | centroids = centroids.tolist() 64 | 65 | # Sort centroids by relative time 66 | centroids = sorted(centroids, key=lambda x: x[0]) 67 | 68 | json_dict["centroids"] = centroids 69 | 70 | 71 | # Save the JSON file 72 | with open(os.path.join(dir_path, json_name), 'w') as f: 73 | json.dump(json_dict, f, indent=4, sort_keys=True) 74 | 75 | 76 | 77 | 78 | def initMeteorObjects(json_list): 79 | 80 | # Init meteor objects 81 | meteor_list = [] 82 | for j in json_list: 83 | 84 | # Init the meteor object 85 | meteor = MeteorObservation(j['jdt_ref'], j['station']['station_id'], \ 86 | np.radians(j['station']['lat']), np.radians(j['station']['lon']), j['station']['elev'], j['fps']) 87 | 88 | # Add data to meteor object 89 | for entry in j['centroids']: 90 | t_rel, x_centroid, y_centroid, ra, dec, intensity_sum, mag = entry 91 | 92 | meteor.addPoint(t_rel*j['fps'], x_centroid, y_centroid, 0.0, 0.0, ra, dec, mag) 93 | 94 | meteor.finish() 95 | 96 | meteor_list.append(meteor) 97 | 98 | 99 | # Normalize all observations to the same JD and precess from J2000 to the epoch of date 100 | return prepareObservations(meteor_list) 101 | 102 | 103 | 104 | 105 | if __name__ == "__main__": 106 | 107 | 108 | ### COMMAND LINE ARGUMENTS 109 | 110 | # Init the command line arguments parser 111 | arg_parser = argparse.ArgumentParser(description="Run the trajectory solver on RMS JSON files.") 112 | 113 | arg_parser.add_argument('json_files', nargs="+", metavar='JSON_PATH', type=str, \ 114 | help='Path to 2 of more JSON files.') 115 | 116 | # Add other solver options 117 | arg_parser = addSolverOptions(arg_parser, skip_velpart=True) 118 | 119 | arg_parser.add_argument('-p', '--velpart', metavar='VELOCITY_PART', \ 120 | help="Fixed part from the beginning of the meteor on which the initial velocity estimation using the sliding fit will start. Default is 0.4 (40 percent), but for noisier data this might be bumped up to 0.5.", \ 121 | type=float, default=0.4) 122 | 123 | arg_parser.add_argument('-w', '--walk', \ 124 | help="Recursively find all manual reduction JSON files in the given folder and use them for trajectory estimation.", \ 125 | action="store_true") 126 | 127 | # Parse the command line arguments 128 | cml_args = arg_parser.parse_args() 129 | 130 | ######################### 131 | 132 | ### Parse command line arguments ### 133 | 134 | json_paths = [] 135 | print('Using JSON files:') 136 | 137 | 138 | # If the recursive walk option is given, find all JSON files recursively in the given folder 139 | if cml_args.walk: 140 | 141 | # Take the dir path as the given path 142 | dir_path = cml_args.json_files[0] 143 | 144 | # Find all manual reduction JSON files in the given folder 145 | json_names = [] 146 | for entry in sorted(os.walk(dir_path), key=lambda x: x[0]): 147 | 148 | dir_name, _, file_names = entry 149 | 150 | # Add all JSON files with picks to the processing list 151 | for fn in file_names: 152 | if fn.lower().endswith("_picks.json"): 153 | 154 | # Add JSON file, but skip duplicates 155 | if fn not in json_names: 156 | json_paths.append(os.path.join(dir_name, fn)) 157 | json_names.append(fn) 158 | 159 | 160 | else: 161 | for json_p in cml_args.json_files: 162 | for json_full_p in glob.glob(json_p): 163 | json_full_path = os.path.abspath(json_full_p) 164 | 165 | # Check that the path exists 166 | if os.path.exists(json_full_path): 167 | json_paths.append(json_full_path) 168 | print(json_full_path) 169 | else: 170 | print('File not found:', json_full_path) 171 | 172 | 173 | # Extract dir path 174 | dir_path = os.path.dirname(json_paths[0]) 175 | 176 | 177 | # Check that there are more than 2 JSON files given 178 | if len(json_paths) < 2: 179 | print("At least 2 JSON files are needed for trajectory estimation!") 180 | sys.exit() 181 | 182 | 183 | max_toffset = None 184 | if cml_args.maxtoffset: 185 | max_toffset = cml_args.maxtoffset[0] 186 | 187 | velpart = None 188 | if cml_args.velpart: 189 | velpart = cml_args.velpart 190 | 191 | vinitht = None 192 | if cml_args.vinitht: 193 | vinitht = cml_args.vinitht[0] 194 | 195 | ### ### 196 | 197 | 198 | 199 | # Load all json files 200 | json_list = [] 201 | for json_file in json_paths: 202 | with open(json_file) as f: 203 | data = json.load(f) 204 | json_list.append(data) 205 | 206 | 207 | ### If there are stations with the same names, append "_1, _2,..." at the end of their names ### 208 | station_names = [j['station']['station_id'] for j in json_list] 209 | unique, counts = np.unique(station_names, return_counts=True) 210 | 211 | for station_id, count in zip(unique, counts): 212 | 213 | id_add_counter = 1 214 | 215 | # If there are more than 1 stations with the same name, add suffixes 216 | if count > 1: 217 | 218 | # Find the stations with the duplicate ID 219 | for j in json_list: 220 | if j['station']['station_id'] == station_id: 221 | j['station']['station_id'] += "_{:d}".format(id_add_counter) 222 | id_add_counter += 1 223 | 224 | ### ### 225 | 226 | 227 | 228 | 229 | # Normalize the observations to the same reference Julian date and precess them from J2000 to the 230 | # epoch of date 231 | jdt_ref, meteor_list = initMeteorObjects(json_list) 232 | 233 | 234 | # Solve the trajectory 235 | traj = solveTrajectoryGeneric(jdt_ref, meteor_list, dir_path, solver=cml_args.solver, \ 236 | max_toffset=max_toffset, monte_carlo=(not cml_args.disablemc), mc_runs=cml_args.mcruns, \ 237 | geometric_uncert=cml_args.uncertgeom, gravity_correction=(not cml_args.disablegravity), 238 | gravity_factor=cml_args.gfact, 239 | plot_all_spatial_residuals=cml_args.plotallspatial, plot_file_type=cml_args.imgformat, \ 240 | show_plots=(not cml_args.hideplots), v_init_part=velpart, v_init_ht=vinitht, \ 241 | show_jacchia=cml_args.jacchia, \ 242 | estimate_timing_vel=(False if cml_args.notimefit is None else cml_args.notimefit), \ 243 | fixed_times=cml_args.fixedtimes, mc_noise_std=cml_args.mcstd, enable_OSM_plot=cml_args.emableOSM) 244 | -------------------------------------------------------------------------------- /wmpl/Formats/Vid.py: -------------------------------------------------------------------------------- 1 | """ Loading and handling *.vid files. """ 2 | 3 | from __future__ import print_function, division, absolute_import 4 | 5 | import os 6 | import numpy as np 7 | 8 | 9 | class VidStruct(object): 10 | """ Structure for storing vid file info. """ 11 | 12 | def __init__(self): 13 | 14 | # List of video frames - this is used only in the parent Vid structure, while the child structures 15 | # have this empty, but they contain image data in the img_data variable 16 | self.frames = None 17 | 18 | self.magic = 0 19 | 20 | # Bytes for a single image 21 | self.seqlen = 0 22 | 23 | # Header length in bytes 24 | self.headlen = 0 25 | 26 | self.flags = 0 27 | self.seq = 0 28 | 29 | # UNIX time 30 | self.ts = 0 31 | self.tu = 0 32 | 33 | # Station number 34 | self.station_id = 0 35 | 36 | # Image dimensions in pixels 37 | self.wid = 0 38 | self.ht = 0 39 | 40 | # Image depth in bits 41 | self.depth = 0 42 | 43 | # Mirror pointing for centre of frame 44 | self.hx = 0 45 | self.hy = 0 46 | 47 | # Stream number 48 | self.str_num = 0 49 | self.reserved0 = 0 50 | 51 | # Exposure time in milliseconds 52 | self.exposure = 0 53 | 54 | self.reserved2 = 0 55 | 56 | self.text = 0 57 | 58 | # Image data 59 | self.img_data = None 60 | 61 | 62 | 63 | 64 | def readFrame(st, fid): 65 | """ Read in the information from the next frame, save them to the given structure and return the image 66 | data. 67 | """ 68 | 69 | # Get the current position in the file 70 | file_pos = fid.tell() 71 | 72 | # Check if the end of file (EOF) is reached 73 | if not fid.read(1): 74 | return None 75 | 76 | fid.seek(file_pos) 77 | 78 | 79 | #### Read the header ### 80 | ########################################################################################################## 81 | 82 | st.magic = int(np.fromfile(fid, dtype=np.uint32, count=1)) 83 | 84 | # Size of one frame in bytes 85 | st.seqlen = int(np.fromfile(fid, dtype=np.uint32, count=1)) 86 | 87 | # Header length in bytes 88 | st.headlen = int(np.fromfile(fid, dtype=np.uint32, count=1)) 89 | 90 | st.flags = int(np.fromfile(fid, dtype=np.uint32, count=1)) 91 | st.seq = int(np.fromfile(fid, dtype=np.uint32, count=1)) 92 | 93 | # Beginning UNIX time 94 | st.ts = int(np.fromfile(fid, dtype=np.int32, count=1)) 95 | st.tu = int(np.fromfile(fid, dtype=np.int32, count=1)) 96 | 97 | # Station number 98 | st.station_id = int(np.fromfile(fid, dtype=np.int16, count=1)) 99 | 100 | # Image dimensions 101 | st.wid = int(np.fromfile(fid, dtype=np.int16, count=1)) 102 | st.ht = int(np.fromfile(fid, dtype=np.int16, count=1)) 103 | 104 | # Image depth 105 | st.depth = int(np.fromfile(fid, dtype=np.int16, count=1)) 106 | 107 | st.hx = int(np.fromfile(fid, dtype=np.uint16, count=1)) 108 | st.hy = int(np.fromfile(fid, dtype=np.uint16, count=1)) 109 | 110 | st.str_num = int(np.fromfile(fid, dtype=np.uint16, count=1)) 111 | st.reserved0 = int(np.fromfile(fid, dtype=np.uint16, count=1)) 112 | st.exposure = int(np.fromfile(fid, dtype=np.uint32, count=1)) 113 | st.reserved2 = int(np.fromfile(fid, dtype=np.uint32, count=1)) 114 | 115 | st.text = np.fromfile(fid, dtype=np.uint8, count=64).tostring().decode("ascii").replace('\0', '') 116 | 117 | ########################################################################################################## 118 | 119 | # Rewind the file to the beginning of the frame 120 | fid.seek(file_pos) 121 | 122 | # Read one whole frame 123 | fr = np.fromfile(fid, dtype=np.uint16, count=st.seqlen//2) 124 | 125 | # Set the values of the first row to 0 126 | fr[:st.ht] = 0 127 | 128 | # Reshape the frame to the proper image size 129 | img = fr.reshape(st.ht, st.wid) 130 | 131 | 132 | return img 133 | 134 | 135 | 136 | 137 | def readVid(dir_path, file_name): 138 | """ Read in a *.vid file. 139 | 140 | Arguments: 141 | dir_path: [str] path to the directory where the *.vid file is located 142 | file_name: [str] name of the *.vid file 143 | 144 | Return: 145 | [VidStruct object] 146 | """ 147 | 148 | # Open the file for binary reading 149 | fid = open(os.path.join(dir_path, file_name), 'rb') 150 | 151 | # Init the vid struct 152 | vid = VidStruct() 153 | 154 | # Read the info from the first frame 155 | readFrame(vid, fid) 156 | 157 | # Reset the file pointer to the beginning 158 | fid.seek(0) 159 | 160 | vid.frames = [] 161 | 162 | # Read in the frames 163 | while True: 164 | 165 | # Init a new frame structure 166 | frame = VidStruct() 167 | 168 | # Read one frame 169 | #fr = np.fromfile(fid, dtype=np.uint16, count=vid.seqlen//2) 170 | frame.img_data = readFrame(frame, fid) 171 | 172 | # Check if we have reached the end of file 173 | if frame.img_data is None: 174 | break 175 | 176 | # Reshape the frame and add it to the frame list 177 | vid.frames.append(frame) 178 | 179 | fid.close() 180 | 181 | 182 | return vid 183 | 184 | 185 | 186 | if __name__ == "__main__": 187 | 188 | import matplotlib.pyplot as plt 189 | 190 | # Vid file path 191 | dir_path = "../../MirfitPrepare/20160929_050928_mir" 192 | file_name = "ev_20160929_050928A_01T.vid" 193 | 194 | # Read in the *.vid file 195 | vid = readVid(dir_path, file_name) 196 | 197 | frame_num = 125 198 | 199 | # Show one frame of the vid file 200 | plt.imshow(vid.frames[frame_num].img_data, cmap='gray', vmin=0, vmax=255) 201 | plt.show() 202 | 203 | 204 | -------------------------------------------------------------------------------- /wmpl/Formats/WmplTrajectorySummary.py: -------------------------------------------------------------------------------- 1 | """ Functions for loading the wmpl trajectory summary files. """ 2 | 3 | import os 4 | 5 | import pandas as pd 6 | 7 | 8 | def _set_data_types(dataframe: pd.DataFrame) -> None: 9 | """ 10 | Sets the data types and index column in a DataFrame containing meteor trajectory 11 | data. The input dataframe must be in verbose column name format e.g. 12 | "Beginning (UTC Time)". 13 | 14 | Arguments: 15 | dataframe: [Pandas df] The meteor trajectory dataframe to set the data types for. 16 | 17 | 18 | Return: None. 19 | 20 | """ 21 | 22 | DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" 23 | 24 | dataframe["Beginning (UTC Time)"] = pd.to_datetime( 25 | dataframe["Beginning (UTC Time)"], format=DATETIME_FORMAT 26 | ) 27 | dataframe["IAU (code)"] = dataframe[ 28 | "IAU (code)"].astype("string") 29 | dataframe["IAU (No)"] = ( 30 | dataframe["IAU (No)"].fillna(-1).astype("int64") 31 | ) 32 | dataframe["Beg in (FOV)"] = dataframe[ 33 | "Beg in (FOV)"].map( 34 | {"True": True, "False": False} 35 | ) 36 | dataframe["Beg in (FOV)"] = dataframe[ 37 | "Beg in (FOV)"].astype("bool") 38 | dataframe["End in (FOV)"] = dataframe[ 39 | "End in (FOV)"].map( 40 | {"True": True, "False": False} 41 | ) 42 | dataframe["End in (FOV)"] = dataframe[ 43 | "End in (FOV)"].astype("bool") 44 | dataframe["Participating (stations)"] = dataframe[ 45 | "Participating (stations)" 46 | ].astype("string") 47 | dataframe["Participating (stations)"] = dataframe[ 48 | "Participating (stations)" 49 | ].apply(lambda x: x[1:-1].split(",")) 50 | 51 | dataframe.set_index("Unique trajectory (identifier)", inplace=True) 52 | 53 | 54 | 55 | def loadTrajectorySummary(dir_path, file_name): 56 | """ Loads the meteor trajectory summary file into a pandas DataFrame. 57 | 58 | Arguments: 59 | dir_path: [str] Path to the directory containing the trajectory summary file. 60 | file_name: [str] Name of the trajectory summary file. 61 | 62 | Returns: 63 | meteor_trajectory_df: [pandas.DataFrame] DataFrame containing the meteor trajectory data. 64 | """ 65 | 66 | file_path = os.path.join(dir_path, file_name) 67 | 68 | meteor_trajectory_df = pd.read_csv( 69 | file_path, 70 | engine="python", 71 | sep=r"\s*;\s*", 72 | skiprows=[0, 5, 6], 73 | header=[0, 1], 74 | na_values=["nan", "...", "None"], 75 | ) 76 | 77 | def extract_header(text: str) -> str: 78 | return " ".join(text.replace("#", "").split()) 79 | 80 | meteor_trajectory_df.columns = meteor_trajectory_df.columns.map( 81 | lambda h: extract_header(h[0]) + ( 82 | f" ({extract_header(h[1])})" if "Unnamed" not in h[1] else "") 83 | ) 84 | 85 | _set_data_types(meteor_trajectory_df) 86 | 87 | return meteor_trajectory_df 88 | 89 | 90 | def loadTrajectorySummaryFast(dir_path_traj_summary, traj_summary_file_name, quick_file_name): 91 | """ Loads the meteor trajectory data from the CSV file into a pandas DataFrame. 92 | 93 | Arguments: 94 | dir_path_traj_summary: [str] Path to the directory containing the trajectory summary file. 95 | traj_summary_file_name: [str] Name of the trajectory summary file. 96 | quick_file_name: [str] Name of the pickle file for quick loading of the trajectory data. 97 | 98 | Returns: 99 | data: [pandas.DataFrame] DataFrame containing the meteor trajectory data. 100 | """ 101 | 102 | # If the quick file is available, load the data from it 103 | quick_path = os.path.join(dir_path_traj_summary, quick_file_name) 104 | if os.path.exists(quick_path): 105 | print("Loading the quick load file...") 106 | data = pd.read_pickle(quick_path) 107 | 108 | 109 | # Otherwise, load the data from the CSV file 110 | else: 111 | 112 | print("Loading the trajectory CSV file...") 113 | data = loadTrajectorySummary(dir_path_traj_summary, traj_summary_file_name) 114 | print(data) 115 | 116 | # Save the data in the quick load format 117 | print("Saving the quick load file...") 118 | data.to_pickle(quick_path) 119 | 120 | 121 | return data -------------------------------------------------------------------------------- /wmpl/Formats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/Formats/__init__.py -------------------------------------------------------------------------------- /wmpl/MetSim/AutoRefineFit_options.txt: -------------------------------------------------------------------------------- 1 | # This file is read by the MetSim auto fit script. It is a modified JSON format which allows comments 2 | 3 | # This file should be placed in the same directory as the data files 4 | 5 | ## Fit parameters that can be refined 6 | # Meteoroid properties: 7 | # - m_init - Initial meteoroid mass (kg) 8 | # - v_init - Initial meteoroid veocity (m/s) 9 | # - rho - Meteoroid bulk density (kg/m^3) 10 | # - sigma - Main fragment ablation coefficient (s^2/km^2) 11 | # Erosion properties: 12 | # - erosion_height_start - Height at which the erosion starts (meters) 13 | # - erosion_coeff - Erosion coefficient (s^2/m^2) 14 | # - erosion_mass_index - Grain mass distribution index 15 | # - erosion_mass_min - Minimum grain mass (kg) 16 | # - erosion_mass_max - Maximum grain mass (kg) 17 | # Erosion change properties: 18 | # - erosion_height_change - Height at which the erosion coefficient changes (meters) 19 | # - erosion_coeff_change - Erosion coefficient after the change (s^2/m^2) 20 | # - erosion_rho_change - Density after erosion change 21 | # - erosion_sigma_change - Ablation coeff after erosion change (s^2/m^2) 22 | 23 | # The bounds can either be absolute values or a fraction of the initial value. This is defined by either 24 | # 'abs' or 'rel' in the tuple. For example, ['abs', 0.0, None] means the parameter cannot be less than 25 | # 0.0 and there is no upper bound. If we do ['rel', 0.5, 2.0], it means the parameter cannot be less than 0.5 26 | # and cannot be greater than 2 times the initial value. 27 | 28 | { 29 | # Define the magnitude variance (used to weight the cost function) 30 | "mag_sigma": 0.2, 31 | 32 | # Define the length variance in meters (used to weight the cost function) 33 | "len_sigma": 2.0, 34 | 35 | # Define the fit parameters and bounds 36 | "fit_sets": 37 | [ 38 | # Fits can be chained and will occur one after the other 39 | # The use of individual fits is turned on and off by setting the "enabled" parameter to true or false 40 | 41 | # Simple fit - quick refinement of the basics 42 | { 43 | "enabled": true, 44 | "m_init": ["rel", 0.50, 2.00], 45 | "v_init": ["rel", 0.98, 1.02], 46 | }, 47 | # More complex fit - overall fit 48 | { 49 | "enabled": false, 50 | "m_init": ["rel", 0.80, 2.20], 51 | "v_init": ["rel", 0.90, 1.10], 52 | "rho": ["rel", 0.80, 1.20], 53 | "erosion_height_start": ["rel", 0.95, 1.05], 54 | "erosion_coeff": ["rel", 0.50, 2.00], 55 | "erosion_mass_index": ["abs", 1.50, 3.00], 56 | }, 57 | # Custom refinement of erosion parameters - improves wake 58 | { 59 | "enabled": false, 60 | "erosion_coeff": ["rel", 0.75, 1.25], 61 | "erosion_mass_index": ["abs", 1.5 , 3.0 ], 62 | "erosion_mass_min": ["rel", 0.1 , 10.0 ], 63 | "erosion_mass_max": ["rel", 0.1 , 10.0 ], 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /wmpl/MetSim/GUITools.py: -------------------------------------------------------------------------------- 1 | 2 | """ Matplotlib widget for PyQt5. """ 3 | 4 | from PyQt5.QtWidgets import* 5 | from PyQt5 import QtWidgets 6 | 7 | from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT 8 | from matplotlib.figure import Figure 9 | 10 | 11 | 12 | 13 | # Top figure sizes 14 | TOP_FIGSIZES = (3, 5) 15 | 16 | 17 | class MagnitudeMplWidget(QWidget): 18 | 19 | def __init__(self, parent=None): 20 | 21 | QWidget.__init__(self, parent) 22 | 23 | # Create the figure 24 | self.canvas = FigureCanvas(Figure(facecolor='#efebe7', figsize=(7, 5))) 25 | self.canvas.axes = self.canvas.figure.add_subplot(111) 26 | 27 | # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second. 28 | toolbar = NavigationToolbar2QT(self.canvas, self) 29 | 30 | # Create a layout and add the navigation and plot 31 | vertical_layout = QVBoxLayout() 32 | vertical_layout.addWidget(toolbar) 33 | vertical_layout.addWidget(self.canvas) 34 | 35 | self.setLayout(vertical_layout) 36 | 37 | 38 | 39 | class LagMplWidget(QWidget): 40 | 41 | def __init__(self, parent=None): 42 | 43 | QWidget.__init__(self, parent) 44 | 45 | # Create the figure 46 | self.canvas = FigureCanvas(Figure(facecolor='#efebe7', figsize=(7, 5))) 47 | self.canvas.axes = self.canvas.figure.add_subplot(111) 48 | 49 | # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second. 50 | toolbar = NavigationToolbar2QT(self.canvas, self) 51 | 52 | # Create a layout and add the navigation and plot 53 | vertical_layout = QVBoxLayout() 54 | vertical_layout.addWidget(toolbar) 55 | vertical_layout.addWidget(self.canvas) 56 | 57 | self.setLayout(vertical_layout) 58 | 59 | 60 | class VelocityMplWidget(QWidget): 61 | 62 | def __init__(self, parent=None): 63 | 64 | QWidget.__init__(self, parent) 65 | 66 | # Create the figure 67 | self.canvas = FigureCanvas(Figure(facecolor='#efebe7', figsize=(7, 5))) 68 | self.canvas.axes = self.canvas.figure.add_subplot(111) 69 | 70 | # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second. 71 | toolbar = NavigationToolbar2QT(self.canvas, self) 72 | 73 | # Create a layout and add the navigation and plot 74 | vertical_layout = QVBoxLayout() 75 | vertical_layout.addWidget(toolbar) 76 | vertical_layout.addWidget(self.canvas) 77 | 78 | self.setLayout(vertical_layout) 79 | 80 | 81 | 82 | class WakeMplWidget(QWidget): 83 | 84 | def __init__(self, parent=None): 85 | 86 | QWidget.__init__(self, parent) 87 | 88 | # Create the figure 89 | self.canvas = FigureCanvas(Figure(facecolor='#efebe7', figsize=(8, 5))) 90 | self.canvas.axes = self.canvas.figure.add_subplot(111) 91 | 92 | vertical_layout = QVBoxLayout() 93 | vertical_layout.addWidget(self.canvas) 94 | 95 | 96 | self.setLayout(vertical_layout) 97 | 98 | 99 | 100 | class MatplotlibPopupWindow(QMainWindow): 101 | def __init__(self): 102 | super(MatplotlibPopupWindow, self).__init__() 103 | 104 | self.main_widget = QtWidgets.QWidget() 105 | self.setCentralWidget(self.main_widget) 106 | 107 | 108 | 109 | # Init the matplotlib canvas 110 | self.canvas = FigureCanvas(Figure(facecolor='#efebe7')) 111 | self.canvas.axes = self.canvas.figure.add_subplot(111) 112 | 113 | 114 | 115 | # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second. 116 | toolbar = NavigationToolbar2QT(self.canvas, self) 117 | 118 | # Compose elements into layout 119 | layout = QVBoxLayout(self.main_widget) 120 | layout.addWidget(toolbar) 121 | layout.addWidget(self.canvas) -------------------------------------------------------------------------------- /wmpl/MetSim/ML/PostprocessSims.py: -------------------------------------------------------------------------------- 1 | """ Preprocess the simulations before feeding them into the neural network. """ 2 | 3 | from __future__ import print_function, division, absolute_import, unicode_literals 4 | 5 | 6 | import os 7 | import random 8 | 9 | import numpy as np 10 | 11 | # Have to import all to make sure the classes are registered, otherwise pickle loading won't work 12 | from wmpl.MetSim.ML.GenerateSimulations import * 13 | 14 | 15 | from wmpl.Utils.Pickling import loadPickle 16 | from wmpl.Utils.PyDomainParallelizer import domainParallelizer 17 | 18 | 19 | def validateSimulation(dir_path, file_name, param_class_name, min_frames_visible): 20 | 21 | # Load the pickle file 22 | sim = loadPickle(dir_path, file_name) 23 | 24 | # Extract simulation data 25 | res = extractSimData(sim, min_frames_visible=min_frames_visible, check_only=True, \ 26 | param_class_name=param_class_name) 27 | 28 | # If the simulation didn't satisfy the filters, skip it 29 | if res is None: 30 | return None 31 | 32 | 33 | print("Good:", file_name) 34 | 35 | return os.path.join(dir_path, file_name), res 36 | 37 | 38 | def postprocessSims(data_path, param_class_name=None, min_frames_visible=10): 39 | """ Preprocess simulations generated by the ablation model to prepare them for training. 40 | 41 | From all simulations, make fake observations by taking only data above the limiting magnitude and 42 | add noise to simulated data. 43 | 44 | Arguments: 45 | data_path: [str] Path to directory with simulations. 46 | 47 | Keyword arguments: 48 | param_class_name: [str] Name of the class used for postprocessing parameters. None by default, in 49 | which case the original parameters will be used. 50 | min_frames_visible: [int] Minimum number of frames the meteor was visible. 51 | 52 | """ 53 | 54 | # Go through all simulations and create a list for processing 55 | processing_list = [] 56 | for entry in os.walk(data_path): 57 | 58 | dir_path, _, file_list = entry 59 | 60 | for file_name in file_list: 61 | 62 | file_path = os.path.join(dir_path, file_name) 63 | 64 | # Check if the given file is a pickle file 65 | if os.path.isfile(file_path) and file_name.endswith(".pickle"): 66 | 67 | processing_list.append([dir_path, file_name, param_class_name, min_frames_visible]) 68 | 69 | 70 | # Validate simulation (parallelized) 71 | print("Starting postprocessing in parallel...") 72 | results_list = domainParallelizer(processing_list, validateSimulation) 73 | 74 | # Randomize the list 75 | random.shuffle(results_list) 76 | 77 | # Save the list of post-processed pickle files to disk 78 | saveProcessedList(data_path, results_list, param_class_name, min_frames_visible) 79 | 80 | 81 | 82 | 83 | 84 | if __name__ == "__main__": 85 | 86 | import argparse 87 | 88 | 89 | ### COMMAND LINE ARGUMENTS 90 | 91 | # Init the command line arguments parser 92 | arg_parser = argparse.ArgumentParser(description="Check that the simulations in the given directory satisfy the given conditions and create a file with the list of simulation to use for training.") 93 | 94 | arg_parser.add_argument('dir_path', metavar='DIR_PATH', type=str, \ 95 | help="Path to the directory with simulation pickle files.") 96 | 97 | arg_parser.add_argument('-p', '--params', metavar='PARAM_CLASS', type=str, \ 98 | help="Override the postprocessing parameters by using parameters from the given class. Options: {:s}".format(", ".join(SIM_CLASSES_NAMES))) 99 | 100 | # Parse the command line arguments 101 | cml_args = arg_parser.parse_args() 102 | 103 | ######################### 104 | 105 | # Postprocess simulations 106 | postprocessSims(cml_args.dir_path, param_class_name=cml_args.params) -------------------------------------------------------------------------------- /wmpl/MetSim/ML/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/MetSim/ML/__init__.py -------------------------------------------------------------------------------- /wmpl/MetSim/MetalMass.py: -------------------------------------------------------------------------------- 1 | """ Loading and calculating masses from METAL .met files. """ 2 | 3 | from __future__ import print_function, absolute_import, division 4 | 5 | 6 | import numpy as np 7 | 8 | import matplotlib.pyplot as plt 9 | import matplotlib.dates as mdates 10 | 11 | from wmpl.Formats.Met import loadMet 12 | from wmpl.Utils.TrajConversions import unixTime2Date 13 | from wmpl.Utils.Math import mergeClosePoints 14 | from wmpl.Utils.Physics import calcMass 15 | 16 | 17 | 18 | def loadMetalMags(dir_path, file_name): 19 | """ Loads time and absolute magnitudes (apparent @100km) from the METAL .met file where the photometry has 20 | been done on a meteor. 21 | 22 | Arguments: 23 | dir_path: [str] path to the directory where the METAL .met file is located 24 | file_name: [str] name of the METAL .met file 25 | 26 | Return: 27 | [(time1, mag_abs1), (time2, mag_abs2),...]: [list of tuples of ndarrays] Time in seconds and absolute 28 | magnitudes 29 | 30 | """ 31 | 32 | # Load the METAL-style met file 33 | met = loadMet(dir_path, file_name) 34 | 35 | time_mags = [] 36 | 37 | # Extract the time and absolute magnitudes from all sites 38 | for site in met.sites: 39 | 40 | # Extract time, range, and apparent magnitude 41 | data = np.array([[unixTime2Date(int(pick[23]), int(pick[24]), dt_obj=True), pick[31], pick[17]] for pick in met.picks[site]]) 42 | 43 | # Remove rows with infinite magnitudes 44 | data = data[data[:, 2] != np.inf] 45 | 46 | # Split into time, range and apparent magnitude 47 | time, r, mag_app = data.T 48 | 49 | # If the range are all zeros, skip this step 50 | if not np.all(r): 51 | continue 52 | 53 | # Calculate absolute magnitude (apparent magnitude @100km range) 54 | mag_abs = mag_app + 5.0*np.log10(100.0/r.astype(np.float64)) 55 | 56 | # Append the time and magnitudes for this site 57 | time_mags.append((site, time, mag_abs)) 58 | 59 | 60 | return time_mags 61 | 62 | 63 | 64 | 65 | if __name__ == '__main__': 66 | 67 | 68 | ### INPUT PARAMETERS ### 69 | ########################################################################################################## 70 | 71 | #dir_path = "/home/dvida/Dropbox/UWO/Projects/MirfitPrepare/20161007_052749_met" 72 | dir_path = "/home/dvida/Dropbox/UWO/Projects/MetalPrepare/20161007_052749_met" 73 | 74 | # Path to the directory containing the met file 75 | #dir_path = "../MetalPrepare/20170721_070420_met" 76 | 77 | # Name of the met file 78 | file_name = 'state.met' 79 | 80 | # Frames per second of the sysem 81 | fps = 80.0 82 | 83 | # Average velocity (m/s) 84 | v_avg = 23541.10 85 | #v_avg = 26972.28 86 | #v_avg = 15821.78 87 | 88 | # Approx. bulk density of the meteoroid (used for size calculation) 89 | bulk_density = 1000 # kg/m^3 90 | 91 | 92 | ########################################################################################################## 93 | 94 | # Load magnitudes from the .met file 95 | time_mags = loadMetalMags(dir_path, file_name) 96 | 97 | site_names = ['Tavistock', 'Elginfield'] 98 | for i, (site, time, mag_abs) in enumerate(time_mags): 99 | plt.plot(time, mag_abs, label=site_names[i], zorder=3) 100 | 101 | plt.legend() 102 | plt.grid(color='0.9') 103 | plt.xlabel('Time') 104 | plt.ylabel('Absolute mangitude') 105 | 106 | # Set datetime format 107 | myFmt = mdates.DateFormatter('%H:%M:%S') 108 | plt.gca().xaxis.set_major_formatter(myFmt) 109 | 110 | plt.gca().invert_yaxis() 111 | 112 | plt.savefig('photometry.png', dpi=300) 113 | 114 | plt.show() 115 | 116 | 117 | 118 | # Combine absolute magnitude and time to one array 119 | time_data = [] 120 | mag_abs_data = [] 121 | 122 | for site, time, mag_abs in time_mags: 123 | 124 | # Add the measurements from every site to one list 125 | time_data = np.r_[time_data, time] 126 | mag_abs_data = np.r_[mag_abs_data, mag_abs] 127 | 128 | 129 | # Sort the measurements by time 130 | time_mag_data = np.c_[time_data, mag_abs_data] 131 | time_mag_data = time_mag_data[np.argsort(time_mag_data[:, 0])] 132 | time_data, mag_abs_data = time_mag_data.T 133 | 134 | # Normalize the time to the first time point and convert it to seconds (from datetime objects) 135 | time_data -= np.min(time_data) 136 | time_data = np.fromiter((time_diff.total_seconds() for time_diff in time_data), np.float64) 137 | 138 | 139 | # Plot raw absolute magnitudes 140 | plt.scatter(time_data, mag_abs_data, label='Measurements', s=5) 141 | 142 | 143 | # Average absolute magnitude values which are closer than half a frame 144 | time_data, mag_abs_data = mergeClosePoints(time_data, mag_abs_data, delta=1.0/(2*fps)) 145 | 146 | 147 | time_data = np.array(time_data) 148 | mag_abs_data = np.array(mag_abs_data) 149 | 150 | # Calculate the mass from magnitude 151 | mass = calcMass(time_data, mag_abs_data, v_avg) 152 | 153 | 154 | print('Mass', mass, 'kg') 155 | print('Mass', mass*1000, 'g') 156 | print('log10', np.log10(mass)) 157 | 158 | 159 | # Calculate approx. sphere diameter 160 | vol = mass/bulk_density 161 | d = 2*(3*vol/(4*np.pi))**(1/3.0) 162 | 163 | print('Diameter', d*1000, 'mm at', bulk_density, 'kg/m^3') 164 | 165 | 166 | # Plot averaged data 167 | plt.plot(time_data, mag_abs_data, label='Averaged') 168 | 169 | plt.legend() 170 | plt.grid() 171 | plt.xlabel('Time (s)') 172 | plt.ylabel('Absolute mangitude') 173 | 174 | plt.gca().invert_yaxis() 175 | 176 | plt.show() -------------------------------------------------------------------------------- /wmpl/MetSim/Metsim0001_input.txt: -------------------------------------------------------------------------------- 1 | Meteoroid properties 2 | ncmp = 1 //maximum 10; masses (kg) follow, separate lines 1e-6 3 | 0.000169101311594 4 | //density: (kg/m^3) 7500 1000 5 | 700 6 | //initial porosity 7 | 0.0 8 | //heat of ablation: (J/kg) 5e5 1e6 9 | 6.6e6 10 | //boiling point (K) 3130 1500 11 | 1850 12 | //melting point (K) 1811 1490 13 | 2000 14 | //specific heat (J/kg K) 450 1000 15 | 1000 16 | //condensation coefficient psi 0.5 1.0 17 | 0.5 18 | //molar mass (atomic units) 56 23 19 | 36 20 | //therm conductivity (J/ m s K) 80 1 21 | 3 22 | //luminous efficiency (ratio, NOT percent! Thus, 0.007 is 0.7%) 23 | 0.014 24 | //temperature at which porosity is reduced 25 | 1900 26 | //shape factor 27 | 1.21 28 | //emissivity 29 | 0.9 30 | Initial conditions 31 | //begin height (m) 32 | 180000 33 | //begin trail length (m) 34 | 0 35 | //begin speed (m/s) 36 | 16824.81 37 | //zenith angle (deg) 38 | 75.0 39 | //Initial temperature (K) 40 | 280 41 | Simulation parameters 42 | //absolute magnitude distance (m) 43 | 100000 44 | //time step (s) 45 | 0.001 46 | 47 | //pressure coefficients rho_atmo=10^(dens_co[0]+dens_co[1]*h/1000+dens_co[2]*(h/1000)^2+dens_co[3]*(h/1000)^3+dens_co[4]*(h/1000)^4 48 | //+dens_co[5]*(h/1000)^5)*1000 49 | //these are coefficients of a 5th order polynomial fit (ci*x^i) to the log (base 10) of the density 50 | -9.02726494 51 | 0.108986696 52 | -0.0005189 53 | -2.0646e-5 54 | 1.93881e-7 55 | -4.7231e-10 56 | //heights between which fit is good (max, then min) (in m) 57 | 200000 58 | 60000 59 | //http://nssdc.gsfc.nasa.gov/space/model/models/msis_n.html#height 60 | 61 | //Pressure is taken from the US Standard Atmosphere 1976, in Pascals 62 | //this fit is to log (base 10) of pressure 63 | //EXAMPLE: p1=10^(Press_co[0]+Press_co[1]*h/1000+Press_co[2]*(h/1000)^2+Press_co[3]*(h/1000)^3+Press_co[4]*(h/1000)^4 64 | // +Press_co[5]*(h/1000)^5) 65 | -18.1315 66 | 1.00569 67 | -0.0183576 68 | 0.000146531 69 | -5.47181e-7 70 | 7.82804e-10 71 | -------------------------------------------------------------------------------- /wmpl/MetSim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/MetSim/__init__.py -------------------------------------------------------------------------------- /wmpl/MetSim/native/METSIM0001_sizes.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/MetSim/native/METSIM0001_sizes.txt -------------------------------------------------------------------------------- /wmpl/MetSim/native/METSIM0001_times.txt: -------------------------------------------------------------------------------- 1 | dt=0.001 2 | t_max=6.88 3 | -------------------------------------------------------------------------------- /wmpl/MetSim/native/Met_main2016.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "met_calculate.h" 7 | 8 | #include 9 | 10 | using namespace std; 11 | 12 | //this version reads in given grain numbers and sizes, rather than generating them from a distribution 13 | extern int Met_calculate(char input_dir[],char result_dir[],char metsimID[], 14 | char F[]); 15 | extern int Met_evaluate(char result_dir[],char metsimID[]); 16 | 17 | int main() 18 | { 19 | char input_dir[256], result_dir[256],F[50],IDS[5]; 20 | int TIF,ID1,ID2,Err; 21 | int i,j,k,ii,l;//,n_f; 22 | 23 | strcpy(input_dir,"./");//".\\MetSimGen2016\\"); 24 | strcpy(result_dir,"./");//".\\MetSimGen2016\\"); 25 | 26 | TIF=0; //input files must start with "metsim" 27 | ID1=1; 28 | ID2=1; 29 | TIF=ID2-ID1+1; //number of files 30 | for (ii=0;ii=1000) 34 | l=(ii+ID1)/1000; 35 | else l=0; 36 | if (ID2>=100) 37 | k=((ii+ID1)-l*1000)/100; 38 | else k=0; 39 | i=((ii+ID1)-100*k-1000*l)/10; 40 | j=(ii+ID1) % 10; 41 | //if (ID2>100) 42 | { 43 | IDS[0]=l+48; 44 | IDS[1]=k+48; 45 | IDS[2]=i+48; 46 | IDS[3]=j+48; 47 | IDS[4]='\0'; 48 | } 49 | //else 50 | //{ 51 | // IDS[0]=i+48; 52 | // IDS[1]=j+48; 53 | // IDS[2]='\0'; 54 | //} 55 | strcpy(F,"Metsim"); 56 | strcat(F,IDS); 57 | strcat(F,"_input.txt"); 58 | 59 | cout << "Simulation = " << ii << ", metsimID = " << IDS << endl; 60 | cout << "Input file = " << F << endl; 61 | cout << "Result dir = " << result_dir << endl; 62 | 63 | Err=Met_calculate(input_dir,result_dir,IDS,F); 64 | //if (Err==0) 65 | // Met_evaluate(result_dir,IDS); 66 | } 67 | 68 | return (0); 69 | } -------------------------------------------------------------------------------- /wmpl/MetSim/native/met_calculate.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | const double sigma_b=5.67036713e-8; //Stephan-boltzmann constant (W/(m^2K^4)) 9 | const double k_B = 1.3806485279e-23; //boltzmann constant (J/K) 10 | const double P_sur = 101325; //Standard atmospheric pressure 11 | const double g0 = 9.81 ; //earth acceleration in m/s^2 12 | const double re = 6378000.0; //earth radius in m 13 | //const double e = 2.718281828; //Euler number 14 | const double e = exp(1.0); //Euler number 15 | const double R_gas = 287.2; //specific gas constant in J/kg*K 16 | //const double pi=3.14159265358; 17 | const double pi=M_PI; 18 | const int ncmps_max=20; 19 | const int nszs_max=20; 20 | 21 | class meteor 22 | { 23 | public: 24 | double t; 25 | double s; 26 | double h; 27 | double v; 28 | double p2; 29 | double m; 30 | double vh; 31 | double vv; 32 | double temp; 33 | double m_kill; 34 | double T_lim; 35 | double T_int; 36 | int ncmp; 37 | double mcmp[ncmps_max]; 38 | double rho_meteor[ncmps_max]; //;meteoroid density 39 | double Vtot; 40 | double T_boil[ncmps_max]; //boiling point 41 | double T_fus[ncmps_max]; //melting point 42 | double c_p[ncmps_max]; //specific heat 43 | double q[ncmps_max]; //heat of ablation 44 | double psi[ncmps_max]; //coefficient of condensation 45 | double m_mass[ncmps_max]; //average molar mass of meteor 46 | double therm_cond[ncmps_max]; //thermal conductivity of meteoroid 47 | double lum_eff[ncmps_max]; 48 | double poros; 49 | double T_sphere; 50 | double n_frag; //number of fragments of same mass ablating at same height 51 | double Fl_frag_cond; //1 if the particle should fragment now 52 | double Fl_frag_ever; //1 if the particle will fragment sometime 53 | double Fl_ablate; //1 if the particle is still actively ablating 54 | }; 55 | 56 | class constscl 57 | { 58 | public: 59 | double emiss; //emissivity 60 | double shape_fact; //Shape factor of meteor 61 | double z; //zenith angle (degrees) 62 | double zr; //zenith angle in radians 63 | double dt; //time step for integration 64 | double h_obs; //normalize magnitude to this height 65 | double sigma; //arbitrary angle for hypersonic flight 66 | double kappa; //ratio of specific heats 67 | double alph; 68 | double c_s; 69 | double T_a; //atmospheric temperature 70 | double dens_co[6]; //coefficients for atm. density 71 | double Press_co[6]; //coefficients for atm. pressure 72 | double nrec; //total number of records 73 | double t_max; //largest time reached 74 | double h_min; //smallest height reached (by any particle) 75 | double ion_eff[ncmps_max]; 76 | double P_sur; 77 | double maxht_rhoa; //largest height for which atm fit is valid 78 | double minht_rhoa; //smallest height for atm fit 79 | }; 80 | 81 | void rm_item(double arr[],long ind,long Nrem,long &maxind); 82 | 83 | void rm_iteml(long arr[],long ind,long &maxind); 84 | 85 | double atm_dens(double h,double dens_co[]); 86 | 87 | double scaleht(double h,double dens_co[]); 88 | 89 | double mass_loss(meteor &met,double rho_atmo,constscl &cs,double Lambda,double m_dotcmp[]); 90 | 91 | double temp_ch(meteor &met,double rho_atmo,double scale_ht,constscl &cs,double Lambda, 92 | double m_dot,double m_dotcm[]); 93 | 94 | void Lum_intens(constscl &cs,double &lum,double m_dot,double& q_ed,double vdot,meteor &met,double m_dotcm[]); 95 | 96 | void ablate(meteor &met,constscl& cs,ofstream &outstream,ofstream &accelout); 97 | 98 | double gauss(double a,double s,long idum); 99 | 100 | double fgauss(double p); 101 | 102 | double ran0(long *idum); 103 | 104 | void hpsort(unsigned long n, double ra[], double rb[]); 105 | 106 | int Met_calculate(char input_dir[],char result_dir[],char metsimID[], char F[]); -------------------------------------------------------------------------------- /wmpl/Misc/RomulanOrbits.py: -------------------------------------------------------------------------------- 1 | """ Runs Romulan data through Gural and MC solver. """ 2 | 3 | from __future__ import print_function, division, absolute_import 4 | 5 | 6 | import os 7 | import shutil 8 | 9 | 10 | from wmpl.Formats.Met import loadMet, solveTrajectoryMet 11 | from wmpl.Utils.OSTools import mkdirP 12 | 13 | 14 | if __name__ == "__main__": 15 | 16 | romulan_data_dir = "../Romulan2012Geminids" 17 | 18 | 19 | started = False 20 | 21 | for romulan_state_file in sorted(os.listdir(romulan_data_dir)): 22 | 23 | romulan_state_path = os.path.abspath(os.path.join(romulan_data_dir, romulan_state_file)) 24 | 25 | if ('.met' in romulan_state_file) and os.path.isfile(romulan_state_path): 26 | 27 | # # Continue from the given .met file 28 | # if (not '20121215_015724_A_RR.met' in romulan_state_file) and not started: 29 | # continue 30 | 31 | # # Do just the given .met file 32 | # if not "20121215_032737_A_RR" in romulan_state_file: 33 | # continue 34 | 35 | 36 | started = True 37 | 38 | print("#########################################################################################") 39 | print('Solving:', romulan_state_file) 40 | print("#########################################################################################") 41 | 42 | # Make a directory for the solution 43 | romulan_solution_path = os.path.splitext(romulan_state_path)[0] 44 | mkdirP(romulan_solution_path) 45 | 46 | # Copy the met file to the solution directory 47 | shutil.copy2(romulan_state_path, os.path.join(romulan_solution_path, romulan_state_file)) 48 | 49 | # Load data from the .met file 50 | met = loadMet(romulan_solution_path, romulan_state_file) 51 | 52 | # Handle all solver errors 53 | # while True: 54 | # try: 55 | 56 | # Run the trajectory solver 57 | solveTrajectoryMet(met, solver='gural', show_plots=False, mc_pick_multiplier=2) 58 | #solveTrajectoryMet(met, solver='original', show_plots=True, monte_carlo=False) 59 | 60 | # break 61 | 62 | # except: 63 | 64 | # continue 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /wmpl/Misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/Misc/__init__.py -------------------------------------------------------------------------------- /wmpl/Rebound/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/Rebound/__init__.py -------------------------------------------------------------------------------- /wmpl/TrajSim/AnalyzeErrorEstimation.py: -------------------------------------------------------------------------------- 1 | """ Produces graphs which show how many standard deviations the estimated value is off from the real simulated 2 | value. 3 | """ 4 | 5 | import os 6 | 7 | import matplotlib.pyplot as plt 8 | import matplotlib.lines as mlines 9 | import numpy as np 10 | import scipy.stats 11 | 12 | from wmpl.TrajSim.AnalyzeTrajectories import collectTrajPickles, pairTrajAndSim 13 | from wmpl.Utils.Math import angleBetweenSphericalCoords 14 | 15 | ### This import is needed to be able to load SimMeteor pickle files 16 | from wmpl.TrajSim.ShowerSim import SimMeteor, AblationModelVelocity, LinearDeceleration, ConstantVelocity 17 | ### 18 | 19 | 20 | if __name__ == "__main__": 21 | 22 | # ### SOMN ### 23 | # min_conv_angle = 15.0 24 | # radiant_diff_max = 5.0 25 | 26 | # dir_path_list = ["/mnt/bulk/SimulatedMeteors/SOMN_sim_2station/2012Geminids_1000", \ 27 | # "/mnt/bulk/SimulatedMeteors/SOMN_sim/2011Draconids",\ 28 | # "/mnt/bulk/SimulatedMeteors/SOMN_sim/2012Perseids"] 29 | 30 | # ### 31 | 32 | 33 | # ## CAMS ### 34 | # # Path to the folder with simulated and solved data 35 | # dir_path_list = ["/mnt/bulk/SimulatedMeteors/CAMSsim_2station/2012Geminids_1000", 36 | # "/mnt/bulk/SimulatedMeteors/CAMSsim/2012Perseids", 37 | # "/mnt/bulk/SimulatedMeteors/CAMSsim/2011Draconids"] 38 | # min_conv_angle = 10.0 39 | # radiant_diff_max = 1.0 40 | 41 | # ## 42 | 43 | 44 | ### CAMO ### 45 | # Path to the folder with simulated and solved data 46 | dir_path_list = ["/mnt/bulk/SimulatedMeteors/CAMO/2012Geminids_1000", \ 47 | "/mnt/bulk/SimulatedMeteors/CAMO/2011Draconids", \ 48 | "/mnt/bulk/SimulatedMeteors/CAMO/2012Perseids"] 49 | 50 | min_conv_angle = 1.0 51 | radiant_diff_max = 0.5 52 | 53 | ### ### 54 | 55 | 56 | # Go through all directories 57 | for dir_path in dir_path_list: 58 | 59 | # Load simulated meteors 60 | sim_meteors = collectTrajPickles(dir_path, traj_type='sim_met') 61 | 62 | # Load MC estimated trajectories 63 | traj_list = collectTrajPickles(dir_path, traj_type='mc') 64 | 65 | 66 | # Remove all trajectories with the convergence angle less then min_conv_angle deg 67 | traj_list = [traj for traj in traj_list if np.degrees(traj.best_conv_inter.conv_angle) \ 68 | >= min_conv_angle] 69 | 70 | 71 | # Pair simulations and trajectories 72 | traj_sim_pairs = pairTrajAndSim(traj_list, sim_meteors) 73 | 74 | vinit_stds = [] 75 | radiant_stds = [] 76 | radiant_errors = [] 77 | for (traj, sim) in traj_sim_pairs: 78 | 79 | 80 | # Skip the orbit if it was not estimated properly 81 | if traj.orbit.v_g is None: 82 | continue 83 | 84 | # Difference in the initial velocity (m/s) 85 | vinit_diff = traj.v_init - sim.v_begin 86 | 87 | # Difference in geocentric radiant (degrees) 88 | radiant_diff = np.degrees(angleBetweenSphericalCoords(sim.dec_g, sim.ra_g, traj.orbit.dec_g, \ 89 | traj.orbit.ra_g)) 90 | 91 | 92 | # Reject everything larger than the threshold 93 | if radiant_diff > radiant_diff_max: 94 | continue 95 | 96 | 97 | # # Skip zero velocity uncertainties 98 | # if traj.uncertainties.v_init == 0: 99 | # continue 100 | 101 | # # Compute the number of standard deviations of the real error in the velocity 102 | # vinit_diff_std = vinit_diff/traj.uncertainties.v_init 103 | 104 | # # # Skip all cases where the difference is larger than 0.5 km/s 105 | # # if np.abs(vinit_diff_std) > 500: 106 | # # continue 107 | 108 | 109 | # print(vinit_diff, traj.uncertainties.v_init, vinit_diff_std) 110 | 111 | 112 | 113 | 114 | ### Compute the number of standard deviations of the real error in the radiant ### 115 | 116 | # Compute unified standard deviation for RA and Dec 117 | radiant_std = np.degrees(np.hypot(np.cos(traj.orbit.dec_g)*traj.uncertainties.ra_g, \ 118 | traj.uncertainties.dec_g)) 119 | 120 | if radiant_std == 0: 121 | continue 122 | 123 | radiant_diff_std = radiant_diff/radiant_std 124 | 125 | # Reject standard deviations larger than 10 sigma 126 | if (radiant_diff_std > 10) or (radiant_diff_std < 0): 127 | continue 128 | 129 | 130 | ### ### 131 | 132 | 133 | #vinit_stds.append(vinit_diff_std) 134 | radiant_errors.append(radiant_diff) 135 | radiant_stds.append(radiant_diff_std) 136 | 137 | 138 | 139 | # Fit a truncated normal distribution to the data 140 | dist_min = 0 141 | dist_max = 10 142 | truncnorm_dist_params = scipy.stats.truncnorm.fit(radiant_stds, dist_min, dist_max, loc=0) 143 | print(truncnorm_dist_params) 144 | 145 | # Fit a chi2 distribution to the data 146 | df = 2 147 | chi2_dist_params = scipy.stats.chi2.fit(radiant_stds, df, loc=0) 148 | print(chi2_dist_params) 149 | 150 | # Kolmogorov-Smirnov tests 151 | kstest_truncnorm = scipy.stats.kstest(radiant_stds, scipy.stats.truncnorm.cdf, truncnorm_dist_params) 152 | kstest_chi2 = scipy.stats.kstest(radiant_stds, scipy.stats.chi2.cdf, chi2_dist_params) 153 | 154 | print(kstest_truncnorm) 155 | print(kstest_chi2) 156 | 157 | 158 | x_arr = np.linspace(0, np.max(radiant_stds), 100) 159 | 160 | 161 | # # Plot a histogram of radiant errors in sigmas 162 | # bins, edges, _ = plt.hist(radiant_stds, bins=int(np.floor(np.sqrt(len(radiant_stds)))), cumulative=False, density=True, \ 163 | # color='0.7', label='Simulated vs. estimated values') 164 | 165 | # # Plot fitted truncated normal distribution 166 | # plt.plot(x_arr, scipy.stats.truncnorm.pdf(x_arr, *truncnorm_dist_params)) 167 | 168 | # # Plot fitted chi2 distribution 169 | # plt.plot(x_arr, scipy.stats.chi2.pdf(x_arr, *chi2_dist_params)) 170 | 171 | 172 | # plt.show() 173 | 174 | 175 | 176 | # Plot the cumulative histogram of radiant errors in sigmas 177 | bins, edges, _ = plt.hist(radiant_stds, bins=len(radiant_stds), cumulative=True, density=True, \ 178 | color='0.7', label='Simulated vs. estimated values') 179 | 180 | # Plot the fitted truncnorm distribution 181 | plt.plot(x_arr, scipy.stats.truncnorm.cdf(x_arr, *truncnorm_dist_params), linestyle='dashed', \ 182 | color='k', label='Trucnorm distribution: $\\sigma$ = {:.2f}'.format(truncnorm_dist_params[3])) 183 | 184 | # Plot the fitted chi2 distribution 185 | plt.plot(x_arr, scipy.stats.chi2.cdf(x_arr, *chi2_dist_params), color='k', 186 | label='$\\chi^2$ distribution: $k$ = {:.2f}, scale = {:.2f}'.format(chi2_dist_params[0], chi2_dist_params[2])) 187 | 188 | plt.legend(loc='lower right') 189 | 190 | 191 | plt.xlim(xmax=4) 192 | plt.ylim([0, 1.0]) 193 | plt.xlabel('True radiant error ($\sigma$)') 194 | plt.ylabel('Cumulative normalized count') 195 | 196 | plt.grid(color='0.8') 197 | 198 | plot_path = os.path.join(dir_path, "true_vs_estimated_error.png") 199 | print("Saving plot to:", plot_path) 200 | plt.savefig(plot_path, dpi=300) 201 | 202 | plt.show() -------------------------------------------------------------------------------- /wmpl/TrajSim/TrajConvAngles.py: -------------------------------------------------------------------------------- 1 | """ Plot radiant and velocity error vs. convergence angle for all solvers of the given shower and system. """ 2 | 3 | 4 | import os 5 | 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | from wmpl.TrajSim.AnalyzeTrajectories import collectTrajPickles, pairTrajAndSim, calcTrajSimDiffs 10 | from wmpl.Utils.Math import histogramEdgesEqualDataNumber 11 | 12 | ### This import is needed to be able to load SimMeteor pickle files 13 | from wmpl.TrajSim.ShowerSim import SimMeteor, AblationModelVelocity, LinearDeceleration, ConstantVelocity 14 | ### 15 | 16 | 17 | 18 | 19 | 20 | if __name__ == "__main__": 21 | 22 | 23 | 24 | # ## CAMO ### 25 | # plot_title = "CAMO" 26 | # dir_path = "../SimulatedMeteors/CAMO/2012Geminids_1000" 27 | 28 | # solvers = ['planes', 'los', 'milig', 'mc', 'gural1', 'gural3'] 29 | # solvers_plot_labels = ['IP', 'LoS', 'LoS-FHAV', 'Monte Carlo', 'MPF linear', 'MPF exp'] 30 | 31 | # ## ### 32 | 33 | 34 | 35 | # ### CAMS ### 36 | # plot_title = "Moderate FOV (CAMS-like)" 37 | # dir_path = "../SimulatedMeteors/CAMSsim_2station/2012Geminids_1000" 38 | 39 | # solvers = ['planes', 'los', 'milig', 'mc', 'gural0', 'gural0fha', 'gural1', 'gural3'] 40 | # solvers_plot_labels = ['IP', 'LoS', 'LoS-FHAV', 'Monte Carlo', 'MPF const', 'MPF const-FHAV', \ 41 | # 'MPF linear', 'MPF exp'] 42 | 43 | # ### ### 44 | 45 | 46 | 47 | ### SOMN ### 48 | plot_title = "All-sky" 49 | dir_path = "./mnt/bulk/SimulatedMeteors/SOMN_sim_2station/2012Geminids_1000" 50 | 51 | solvers = ['planes', 'los', 'milig', 'mc', 'gural0', 'gural0fha', 'gural1', 'gural3'] 52 | solvers_plot_labels = ['IP', 'LoS', 'LoS-FHAV', 'Monte Carlo', 'MPF const', 'MPF const-FHAV', \ 53 | 'MPF linear', 'MPF exp'] 54 | 55 | ### ### 56 | 57 | 58 | 59 | # Number of historgram bins 60 | conv_hist_bins = 30 61 | 62 | 63 | 64 | ########################################################################################################## 65 | 66 | 67 | 68 | # Split the path into components 69 | path = os.path.normpath(dir_path) 70 | path = path.split(os.sep) 71 | 72 | # Extract the system and the shower name 73 | system_name = path[-2].replace("_", "").replace('sim', '') 74 | shower_name = path[-1] 75 | shower_name = shower_name[:4] + ' ' + shower_name[4:] 76 | 77 | 78 | # Load simulated meteors 79 | sim_meteors = collectTrajPickles(dir_path, traj_type='sim_met') 80 | 81 | 82 | fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True) 83 | 84 | # Compare trajectories to simulations 85 | for solver, solver_name in zip(solvers, solvers_plot_labels): 86 | 87 | # Load trajectories 88 | traj_list = collectTrajPickles(dir_path, traj_type=solver) 89 | 90 | # Pair simulations and trajectories 91 | traj_sim_pairs = pairTrajAndSim(traj_list, sim_meteors) 92 | 93 | # Compute radiant and velocity errors 94 | radiant_diffs, vg_diffs, conv_angles, _ = calcTrajSimDiffs(traj_sim_pairs, np.inf, np.inf) 95 | 96 | # Get edges of histogram and compute medians of errors in every bin 97 | edges = histogramEdgesEqualDataNumber(conv_angles, conv_hist_bins) 98 | 99 | binned_radiant_diffs = [] 100 | binned_vg_diffs = [] 101 | for i in range(len(edges) - 1): 102 | 103 | min_val = edges[i] 104 | max_val = edges[i + 1] 105 | 106 | # Compute median radiant and vg error of all trajectories in the given range of conv angles 107 | radiant_diff_med = np.median([radiant_diff for radiant_diff, conv_ang in zip(radiant_diffs, conv_angles) if (conv_ang >= min_val) and (conv_ang < max_val)]) 108 | vg_diff_med = np.median([vg_diff for vg_diff, conv_ang in zip(vg_diffs, conv_angles) if (conv_ang >= min_val) and (conv_ang < max_val)]) 109 | 110 | binned_radiant_diffs.append(radiant_diff_med) 111 | binned_vg_diffs.append(vg_diff_med) 112 | 113 | 114 | # Plot MPF results dotted 115 | if solver_name.startswith('MPF'): 116 | linestyle = (0, (1, 1)) 117 | linewidth = 1 118 | 119 | if 'exp' in solver_name: 120 | marker = '*' 121 | 122 | elif 'const-FHAV' in solver_name: 123 | marker = '+' 124 | 125 | elif 'const' in solver_name: 126 | marker = 'x' 127 | 128 | else: 129 | marker = None 130 | 131 | markersize = 6 132 | 133 | 134 | elif solver_name == "Monte Carlo": 135 | linestyle = 'solid' 136 | linewidth = 2 137 | marker = '*' 138 | markersize = 8 139 | 140 | else: 141 | linestyle = 'solid' 142 | linewidth = 1 143 | 144 | if solver_name == "LoS": 145 | marker = 'x' 146 | 147 | elif solver_name == "LoS-FHAV": 148 | marker = '+' 149 | 150 | else: 151 | marker = None 152 | 153 | markersize = 6 154 | 155 | 156 | # Plot convergence angle vs radiant error 157 | ax1.plot(edges[:-1], binned_radiant_diffs, label=solver_name, linestyle=linestyle, \ 158 | linewidth=linewidth, marker=marker, markersize=markersize, zorder=3) 159 | 160 | # Plot convergence angle vs Vg error 161 | ax2.plot(edges[:-1], binned_vg_diffs, label=solver_name, linestyle=linestyle, linewidth=linewidth, \ 162 | marker=marker, markersize=markersize, zorder=3) 163 | 164 | # Add a zero velocity error line 165 | ax2.plot(edges[:-1], np.zeros_like(edges[:-1]), color='k', linestyle='--') 166 | 167 | 168 | ax1.legend() 169 | ax1.grid() 170 | ax1.set_ylabel('Radiant error (deg)') 171 | ax1.set_ylim(bottom=0) 172 | ax1.set_xlim(left=0, right=np.max(edges[:-1])) 173 | 174 | ax2.grid() 175 | ax2.set_ylabel('$V_g$ error (km/s)') 176 | ax2.set_xlabel('Convergence angle (deg)') 177 | 178 | plt.title(plot_title) 179 | 180 | plt.tight_layout() 181 | 182 | plt.subplots_adjust(hspace=0.1) 183 | 184 | # Save the figure 185 | plt.savefig(os.path.join(dir_path, system_name + '_' + shower_name.replace(' ', '_') \ 186 | + '_conv_angle.png'), dpi=300) 187 | 188 | plt.show() -------------------------------------------------------------------------------- /wmpl/TrajSim/TrajSim.py: -------------------------------------------------------------------------------- 1 | """ Meteor trajectory simulator. 2 | 3 | Features: 4 | - Simulating individual trajectories and projecting the observations to the observers. 5 | 6 | """ 7 | 8 | from __future__ import print_function, division, absolute_import 9 | 10 | 11 | import numpy as np 12 | import scipy.optimize 13 | 14 | from wmpl.Trajectory.Orbit import calcOrbit 15 | from wmpl.Utils.TrajConversions import date2JD, raDec2ECI 16 | from wmpl.Utils.Math import angleBetweenSphericalCoords, vectMag 17 | 18 | 19 | 20 | 21 | def geocentricRadiantToApparent(ra_g, dec_g, v_g, state_vector, jd_ref): 22 | """ Numerically converts the given geocentric radiant to the apparent radiant. 23 | 24 | Arguments: 25 | ra_g: [float] Geocentric right ascension (radians). 26 | dec_g: [float] Geocentric declination (radians). 27 | v_g: [float] Geocentric velocity (m/s). 28 | state_vector: [ndarray of 3 elemens] (x, y, z) ECI coordinates of the initial state vector (meters). 29 | jd_ref: [float] reference Julian date of the event. 30 | 31 | Return: 32 | (ra_a, dec_a, v_init): [list] 33 | - ra_a: [float] Apparent R.A. (radians). 34 | - dec_a: [float] Apparent declination (radians). 35 | - v_init: [float] Initial velocity (m/s). 36 | """ 37 | 38 | def _radiantDiff(radiant_eq, ra_g, dec_g, v_init, state_vector, jd_ref): 39 | 40 | ra_a, dec_a = radiant_eq 41 | 42 | # Convert the given RA and Dec to ECI coordinates 43 | radiant_eci = np.array(raDec2ECI(ra_a, dec_a)) 44 | 45 | # Estimate the orbit with the given apparent radiant 46 | orbit = calcOrbit(radiant_eci, v_init, v_init, state_vector, jd_ref, stations_fixed=False, \ 47 | reference_init=True) 48 | 49 | if orbit.ra_g is None: 50 | return None 51 | 52 | # Compare the difference between the calculated and the reference geocentric radiant 53 | return angleBetweenSphericalCoords(orbit.dec_g, orbit.ra_g, dec_g, ra_g) 54 | 55 | 56 | # Assume that the velocity at infinity corresponds to the initial velocity 57 | v_init = np.sqrt(v_g**2 + (2*6.67408*5.9722)*1e13/vectMag(state_vector)) 58 | 59 | # Numerically find the apparent radiant 60 | res = scipy.optimize.minimize(_radiantDiff, x0=[ra_g, dec_g], args=(ra_g, dec_g, v_init, state_vector, jd_ref), \ 61 | bounds=[(0, 2*np.pi), (-np.pi, np.pi)], tol=1e-13, method='SLSQP') 62 | 63 | ra_a, dec_a = res.x 64 | 65 | # Calculate all orbital parameters with the best estimation of apparent RA and Dec 66 | orb = calcOrbit(np.array(raDec2ECI(ra_a, dec_a)), v_init, v_init, state_vector, jd_ref, stations_fixed=False, \ 67 | reference_init=True) 68 | 69 | return ra_a, dec_a, v_init, orb 70 | 71 | 72 | 73 | if __name__ == "__main__": 74 | 75 | 76 | # Least squares solution 77 | # ---------------------- 78 | # State vector (ECI): 79 | # X = 4663824.46 +/- 1982.77 m 80 | # Y = 417867.46 +/- 3446.91 m 81 | # Z = 4458026.80 +/- 12423.32 m 82 | # Vx = -5237.36 +/- 62.35 m/s 83 | # Vy = 8828.73 +/- 323.60 m/s 84 | # Vz = 33076.10 +/- 630.18 m/s 85 | 86 | # Timing offsets: 87 | # 1: -0.004239 s 88 | # 2: 0.000000 s 89 | 90 | # reference point on the trajectory: 91 | # Time: 2012-12-13 00:18:05.413143 UTC 92 | # Lon = -81.494888 +/- 0.0441 deg 93 | # Lat = 43.782497 +/- 0.0905 deg 94 | 95 | # Radiant (apparent): 96 | # R.A. = 120.67717 +/- 0.3462 deg 97 | # Dec = +72.75807 +/- 0.3992 deg 98 | # Vavg = 33.29536 +/- 0.2666 km/s 99 | # Vinit = 34.63242 +/- 0.5897 km/s 100 | # Radiant (geocentric): 101 | # R.A. = 124.39147 +/- 0.3695 deg 102 | # Dec = +71.73020 +/- 0.4479 deg 103 | # Vg = 32.80401 +/- 0.6236 km/s 104 | # Vinf = 34.63242 +/- 0.5897 km/s 105 | # Zg = 57.16852 +/- 0.3859 deg 106 | 107 | 108 | ra_g = np.radians(124.39147) 109 | dec_g = np.radians(+71.73020) 110 | 111 | # Geocentric velocity in m/s 112 | v_g = 32.80401*1000 113 | 114 | # ECI coordinates of the inital state vector 115 | state_vector = np.array([4663824.46, 417867.46, 4458026.80]) 116 | 117 | # reference Julian date 118 | jd_ref = date2JD(2012, 12, 13, 0, 18, 5) 119 | 120 | 121 | 122 | ra_a, dec_a, v_init, orb = geocentricRadiantToApparent(ra_g, dec_g, v_g, state_vector, jd_ref) 123 | 124 | print('Apparent:') 125 | print(' R.A.:', np.degrees(ra_a)) 126 | print(' Dec: ', np.degrees(dec_a)) 127 | 128 | # Print the whole orbit 129 | print(orb) 130 | 131 | 132 | 133 | # Traj solver: 134 | # [-0.15122695 0.25492659 0.9550617 ] 34632.4213376 33295.3578823 [ 4663824.46446627 417867.46380932 4458026.80410226] 2456274.51256 135 | # [-0.1512269 0.25492656 0.95506171] 34632.4203999 34632.4203999 [ 4663824.46 417867.46 4458026.8 ] 2456274.51256 136 | # Inverse 137 | 138 | 139 | # ### TEST ORBIT CALCULATION 140 | # orb1 = calcOrbit(np.array([-0.15122695, 0.25492659, 0.9550617 ]), 34632.4213376, 33295.3578823, np.array([ 4663824.46446627, 417867.46380932, 4458026.80410226]), 2456274.51256) 141 | # print("ORB 1", np.degrees(orb1.ra_g), np.degrees(orb1.dec_g)) 142 | 143 | # orb2 = calcOrbit(np.array([-0.12606042, 0.25102545, 0.95973694]), 34632.4203999, 34632.4203999, np.array([ 4663824.46, 417867.46, 4458026.8 ]), 2456274.51256) 144 | # print("ORB 2", np.degrees(orb2.ra_g), np.degrees(orb2.dec_g)) -------------------------------------------------------------------------------- /wmpl/TrajSim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/TrajSim/__init__.py -------------------------------------------------------------------------------- /wmpl/Trajectory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/Trajectory/__init__.py -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/README: -------------------------------------------------------------------------------- 1 | To compile this library: 2 | 1) Navigate to the "common" directory and run "make". 3 | 2) Navigate to the "trajectory" directory and run "make". 4 | 3) Done! -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/AttributeStructure.h: -------------------------------------------------------------------------------- 1 | 2 | //####################################################################################### 3 | // Cluster and Tracker Attribute Structure Definition 4 | //####################################################################################### 5 | 6 | struct attributeinfo 7 | { 8 | double time; // Time in user defined units (sec or frame# or...) 9 | double rowcentroid; // Row centroid in pixels 10 | double colcentroid; // Col centroid in pixels 11 | long nexceed; // Number of exceedance pixels in region 12 | long nsaturated; // Number of saturated pixels in region 13 | long npixels; // Number of total pixels in region 14 | double sumsignal; // Summed signal over image chip region 15 | double sumnoise; // Summed noise over image chip region 16 | double SsqNdB; // Signal squared over noise detection metric in dB 17 | double entropy; // Region entropy 18 | double PCD_angle; // Phase coded disk angle estimate in deg 19 | double PCD_width; // Phase coded disk line width estimate in pixels 20 | double Hueckel_angle; // Hueckel kernel angle estimate in deg 21 | }; 22 | -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/DynamicArrayAllocation.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | //####################################################################################################################### 4 | // Multi-dimensional dynamic array generation for all data types (short,int,long,float,double,...) using a single 5 | // contiguous chunk of memory which makes releasing memory a simple operation. 6 | // 7 | // 1D Memory allocation: user_array = (type*) allocate_1d_array( dim1, sizeof(type) ); 8 | // 2D Memory allocation: user_array = (type**) allocate_2d_array( dim1, dim2, sizeof(type) ); 9 | // 3D Memory allocation: user_array = (type***) allocate_3d_array( dim1, dim2, dim3, sizeof(type) ); 10 | // 11 | // Assignment and usage: user_array[i] = ...; for 1D arrays 12 | // Assignment and usage: user_array[i][j] = ...; for 2D arrays 13 | // Assignment and usage: user_array[i][j][k] = ...; for 3D arrays 14 | // 15 | // Such that indexing is as follows: [dim1][dim2][dim3] 16 | // 17 | // Freeing memory: delete [] user_array; One simple call for all arrays types and sizes 18 | // 19 | //####################################################################################################################### 20 | 21 | /* 22 | void*** allocate_3d_array( const int dim1, const int dim2, const int dim3, const size_t nsize ) 23 | { 24 | int i, j; 25 | 26 | void*** array3d = (void***) malloc( (dim1 * sizeof(void**)) + (dim1*dim2 * sizeof(void*)) + (dim1*dim2*dim3 * nsize) ); 27 | 28 | if( array3d == NULL ) { 29 | fprintf( stdout, "ERROR ===> Memory not allocated for 3D array in function allocate_3d_array\n" ); 30 | fprintf( stderr, "ERROR ===> Memory not allocated for 3D array in function allocate_3d_array\n" ); 31 | Delay_msec(10000); 32 | exit(1); 33 | } 34 | 35 | for(i = 0; i < dim1; ++i) { 36 | array3d[i] = (void**)( array3d + dim1 * sizeof(void**) + i * dim2 * sizeof(void*) ); 37 | for(j = 0; j < dim2; ++j) { PROBLEM WITH actual sizes of void* and void** 38 | array3d[i][j] = (void*)( array3d + dim1 * sizeof(void**) + dim1 * dim2 * sizeof(void*) + i * dim2 * dim3 * nsize + j * dim3 * nsize ); 39 | } 40 | } 41 | 42 | return array3d; 43 | } 44 | 45 | */ 46 | 47 | //==================================================================== 48 | 49 | void** allocate_2d_array( const int dim1, const int dim2, const size_t nsize ) 50 | { 51 | int i; 52 | 53 | void** array2d = (void**) malloc( (dim1 * sizeof(void*)) + (dim1 * dim2 * nsize) ); 54 | 55 | if( array2d == NULL ) { 56 | fprintf( stdout, "ERROR ===> Memory not allocated for 2D array in function allocate_2d_array\n" ); 57 | fprintf( stderr, "ERROR ===> Memory not allocated for 2D array in function allocate_2d_array\n" ); 58 | Delay_msec(10000); 59 | exit(1); 60 | } 61 | 62 | 63 | for(i = 0; i < dim1; ++i) { 64 | ////array2d[i] = (void*)( (array2d + dim1) * sizeof(void*) + i * dim2 * nsize ); 65 | array2d[i] = (void*)( (array2d + dim1) + i * dim2 * nsize / sizeof(void*) ); 66 | } 67 | 68 | return array2d; 69 | } 70 | 71 | //==================================================================== 72 | 73 | void* allocate_1d_array( const int dim1, const size_t nsize ) 74 | { 75 | void* array1d = (void*) malloc( dim1 * nsize ); 76 | 77 | if( array1d == NULL ) { 78 | fprintf( stdout, "ERROR ===> Memory not allocated for 1D array in function allocate_1d_array\n" ); 79 | fprintf( stderr, "ERROR ===> Memory not allocated for 1D array in function allocate_1d_array\n" ); 80 | Delay_msec(10000); 81 | exit(1); 82 | } 83 | 84 | return array1d; 85 | } 86 | 87 | //==================================================================== 88 | 89 | -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/EZbitmapWriter.h: -------------------------------------------------------------------------------- 1 | 2 | //====================================================================================================================================== 3 | // The WriteBMPfile_RGB function creates a very basic Win3 bit mapped file given an image passed as red, green and blue unsigned 4 | // char arrays. If the red, green and blue pointers are identicle, then the file is a gray scale 8-bit image, otherwise it will be 5 | // a 24-bit RGB image. This function flips the image upside-down internally before writing, thus adhering to standard BMP format. 6 | // There is no compression. 7 | //====================================================================================================================================== 8 | 9 | void WriteBMPfile_RGB( char *filename, unsigned char *red_ptr, unsigned char *grn_ptr, unsigned char *blu_ptr, long nrows, long ncols ) 10 | { 11 | long padSize, dataSize, paletteSize, fileSize, krow, kcol, krc, colorsperpixel; 12 | 13 | unsigned char bmpfileheader[14] = { 'B','M', // BMP file identifier 14 | 0,0,0,0, // Size in bytes 15 | 0,0, // App data 16 | 0,0, // App data 17 | 54,0,0,0 }; // Data offset from beginning of file 18 | 19 | unsigned char bmpinfoheader[40] = { 40,0,0,0, // info hd size 20 | 0,0,0,0, // width 21 | 0,0,0,0, // heigth 22 | 1,0, // number color planes 23 | 24,0, // bits per pixel (default RGB) 24 | 0,0,0,0, // compression is none 25 | 0,0,0,0, // padded image size in bytes 26 | 0x13,0x0B,0,0, // horz resoluition in pixel / m 27 | 0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi) 28 | 0,0,0,0, // #colors in pallete 29 | 0,0,0,0 }; // #important colors 30 | 31 | unsigned char bmppad[3] = { 0, 0, 0 }; 32 | unsigned char BGRA[4] = { 0, 0, 0, 0 }; 33 | 34 | FILE *BMPFile; 35 | 36 | 37 | //======== Check if this to be a gray scale or RGB image 38 | 39 | if( red_ptr == grn_ptr && red_ptr == blu_ptr ) colorsperpixel = 1; //... gray only 40 | else colorsperpixel = 3; //... red/green/blue 41 | 42 | 43 | 44 | //======== Overwrite the file size, width and height dimensions in the headers 45 | // making sure the column count is padded to a multiple of four 46 | 47 | padSize = ( 4 - ( ( ncols * colorsperpixel ) % 4 ) ) % 4; 48 | 49 | dataSize = colorsperpixel * nrows * ncols + nrows * padSize; 50 | 51 | if( colorsperpixel == 1 ) paletteSize = 1024; // 256 gray levels in RGBA format 52 | else paletteSize = 0; 53 | 54 | fileSize = 54 + dataSize + paletteSize; 55 | 56 | bmpfileheader[ 2] = (unsigned char)( fileSize ); 57 | bmpfileheader[ 3] = (unsigned char)( fileSize >> 8 ); 58 | bmpfileheader[ 4] = (unsigned char)( fileSize >> 16 ); 59 | bmpfileheader[ 5] = (unsigned char)( fileSize >> 24 ); 60 | 61 | bmpinfoheader[ 4] = (unsigned char)( ncols ); 62 | bmpinfoheader[ 5] = (unsigned char)( ncols >> 8 ); 63 | bmpinfoheader[ 6] = (unsigned char)( ncols >> 16 ); 64 | bmpinfoheader[ 7] = (unsigned char)( ncols >> 24 ); 65 | 66 | bmpinfoheader[ 8] = (unsigned char)( nrows ); 67 | bmpinfoheader[ 9] = (unsigned char)( nrows >> 8 ); 68 | bmpinfoheader[10] = (unsigned char)( nrows >> 16 ); 69 | bmpinfoheader[11] = (unsigned char)( nrows >> 24 ); 70 | 71 | bmpinfoheader[14] = (unsigned char)( 8 * colorsperpixel ); //... bits per pixel 72 | 73 | bmpinfoheader[20] = (unsigned char)( dataSize ); 74 | bmpinfoheader[21] = (unsigned char)( dataSize >> 8 ); 75 | bmpinfoheader[22] = (unsigned char)( dataSize >> 16 ); 76 | bmpinfoheader[23] = (unsigned char)( dataSize >> 24 ); 77 | 78 | 79 | //======== Open and write the header 80 | 81 | if( ( BMPFile = fopen( filename, "wb" ) ) == NULL ) { 82 | fprintf( stdout, "ERROR ===> BMP file %s could not be opened\n\n", filename ); 83 | fprintf( stderr, "ERROR ===> BMP file %s could not be opened\n\n", filename ); 84 | exit(1); 85 | } 86 | 87 | fwrite( bmpfileheader, sizeof(unsigned char), 14, BMPFile ); 88 | fwrite( bmpinfoheader, sizeof(unsigned char), 40, BMPFile ); 89 | 90 | 91 | //======== If grayscale, insert the color palette 92 | 93 | if( colorsperpixel == 1 ) { 94 | 95 | for ( krow=0; krow<256; krow++ ) { 96 | 97 | BGRA[0] = (unsigned char)krow; 98 | BGRA[1] = (unsigned char)krow; 99 | BGRA[2] = (unsigned char)krow; 100 | BGRA[3] = (unsigned char)0; 101 | 102 | fwrite( BGRA, sizeof(unsigned char), 4, BMPFile ); 103 | 104 | } 105 | 106 | } 107 | 108 | 109 | //======== Write the imagery data portion, flipping the image up/down 110 | 111 | for ( krow=nrows-1; krow>=0; krow-- ) { 112 | 113 | krc = krow * ncols; 114 | 115 | for ( kcol=0; kcol 0 ) fwrite( bmppad, sizeof(unsigned char), padSize, BMPFile ); 128 | 129 | } 130 | 131 | 132 | //========= Close the BMP file 133 | 134 | fclose( BMPFile ); 135 | 136 | } 137 | 138 | //========================================================================= 139 | 140 | -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/JulianCalendarConversions.h: -------------------------------------------------------------------------------- 1 | 2 | //========================================================================= 3 | 4 | double JulianDateAndTime( long year, long month, long day, 5 | long hour, long minute, long second, long milliseconds ) 6 | { 7 | long aterm, bterm, jdate; 8 | double jdt; 9 | 10 | 11 | //.................. compute julian date terms 12 | 13 | if( (month == 1L) || (month == 2L) ) { 14 | year = year - 1L; 15 | month = month + 12L; 16 | } 17 | aterm = (long)( year / 100L ); 18 | bterm = 2L - aterm + (long)( aterm / 4L ); 19 | 20 | //.................. julian day at 12hr UT 21 | 22 | jdate = (long)( 365.25 * (double)(year + 4716L) ) 23 | + (long)( 30.6001 * (double)(month + 1L) ) 24 | + day + bterm - 1524L; 25 | 26 | //................... add on the actual UT 27 | 28 | jdt = (double)jdate - 0.5 29 | + (double)hour / (24.0) 30 | + (double)minute / (24.0 * 60.0) 31 | + (double)second / (24.0 * 60.0 * 60.0) 32 | + (double)milliseconds / (24.0 * 60.0 * 60.0 * 1000.0); 33 | 34 | return( jdt ); 35 | } 36 | 37 | 38 | //==================================================================== 39 | 40 | 41 | void CalendarDateAndTime( double jdt, 42 | long *year, long *month, long *day, 43 | long *hour, long *minute, long *second, 44 | long *milliseconds ) 45 | { 46 | long jyear, jmonth, jday, jhour, jminute, jsecond, jmilliseconds; 47 | long aterm, bterm, cterm, dterm, eterm, alpha, intpart; 48 | double fracpart; 49 | 50 | //.................. JD integer and fractional part 51 | 52 | intpart = (long)(jdt + 0.5); 53 | fracpart = (jdt + 0.5) - (double)intpart; 54 | 55 | alpha = (long)( ( (double)intpart - 1867216.25 ) / 36524.25 ); 56 | aterm = intpart + 1 + alpha - (long)( alpha / 4L ); 57 | bterm = aterm + 1524L; 58 | cterm = (long)( ( (double)bterm - 122.1 ) / 365.25 ); 59 | dterm = (long)( 365.25 * (double)cterm ); 60 | eterm = (long)( ( (double)bterm - (double)dterm ) / 30.6001 ); 61 | 62 | //................... date calculation 63 | 64 | jday = bterm - dterm - (long)( 30.6001 * (double)eterm ); 65 | 66 | if( eterm < 14L ) jmonth = (long)eterm - 1; 67 | else jmonth = (long)eterm - 13; 68 | 69 | if( jmonth > 2L ) jyear = (long)cterm - 4716; 70 | else jyear = (long)cterm - 4715; 71 | 72 | 73 | //................... time calculation 74 | 75 | fracpart += 0.0001 / ( 24.0 * 60.0 * 60.0 ); // Add 0.1 msec for rounding 76 | fracpart *= 24.0; 77 | jhour = (long)fracpart; 78 | fracpart -= (double)jhour; 79 | fracpart *= 60.0; 80 | jminute = (long)fracpart; 81 | fracpart -= (double)jminute; 82 | fracpart *= 60.0; 83 | jsecond = (long)fracpart; 84 | fracpart -= (double)jsecond; 85 | fracpart *= 1000.0; 86 | jmilliseconds = (long)fracpart; 87 | 88 | 89 | //................... put into output pointers 90 | 91 | *year = jyear; 92 | *month = jmonth; 93 | *day = jday; 94 | *hour = jhour; 95 | *minute = jminute; 96 | *second = jsecond; 97 | *milliseconds = jmilliseconds; 98 | 99 | 100 | } 101 | 102 | //========================================================================= 103 | -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/Makefile: -------------------------------------------------------------------------------- 1 | # DetectionApp Makefile 2 | 3 | CC=gcc 4 | 5 | CFLAGS= -O2 -pipe -fPIC -W -Wextra -ggdb 6 | CFLAGS+= -I$(HOME)/meteor/include 7 | 8 | LDFLAGS= -Wl,-rpath,$(HOME)/meteor/lib 9 | LDFLAGS+= -L$(HOME)/meteor/lib 10 | 11 | OBJS=ParticleSwarmFunctions.o 12 | 13 | default: $(OBJS) 14 | 15 | %.o: %.c %.h 16 | $(CC) $(CFLAGS) -o $@ -c $< 17 | 18 | clean: 19 | rm -f $(OBJS) 20 | -------------------------------------------------------------------------------- /wmpl/Trajectory/lib/common/System_FileFunctions.h: -------------------------------------------------------------------------------- 1 | //######################################################################### 2 | //######################################################################### 3 | // 4 | // System File Functions for Windows or Linux 5 | // 6 | //######################################################################### 7 | //######################################################################### 8 | 9 | #pragma warning(disable: 4996) // disable warning on strcpy, fopen, ... 10 | 11 | 12 | #ifdef _WIN32 /************* WINDOWS ******************************/ 13 | 14 | #include // string fncts, malloc/free, system 15 | 16 | #else /********************** LINUX *******************************/ 17 | 18 | #include 19 | #include 20 | 21 | extern unsigned int sleep( unsigned int __seconds ); 22 | 23 | #endif /***********************************************************/ 24 | 25 | 26 | 27 | //################################################################################ 28 | //################################################################################ 29 | // 30 | // Get the directory listing of all the *.fits or *.vid files in the user 31 | // specified folder and pipe it to a text file "listing_pathname". 32 | // 33 | //################################################################################ 34 | //################################################################################ 35 | 36 | int GenerateImageryFileListing( char *folder_pathname, char *listing_pathname ) 37 | { 38 | 39 | #ifdef _WIN32 /************* WINDOWS *******************************************/ 40 | 41 | char windows_system_command[512]; 42 | 43 | strcpy( windows_system_command, "dir /b \"" ); 44 | 45 | strcat( windows_system_command, folder_pathname ); 46 | 47 | strcat( windows_system_command, "*.fits\" > \"" ); //... listing of FITS files 48 | 49 | strcat( windows_system_command, listing_pathname ); 50 | 51 | strcat( windows_system_command, "\"" ); 52 | 53 | system( windows_system_command ); 54 | 55 | return(0); 56 | 57 | 58 | #else /********************** LINUX *******************************************/ 59 | 60 | struct dirent *entry; 61 | DIR *directoryfolder; 62 | FILE *filelisting; 63 | 64 | 65 | if( ( filelisting = fopen( listing_pathname, "w" ) ) == NULL ) { 66 | fprintf( stdout, "ERROR ===> FITS file directory listing %s cannot be open for write\n\n", listing_pathname ); 67 | fprintf( stderr, "ERROR ===> FITS file directory listing %s cannot be open for write\n\n", listing_pathname ); 68 | return(1); 69 | } 70 | 71 | if( ( directoryfolder = opendir( folder_pathname ) ) == NULL ) { 72 | fprintf( stdout, "ERROR ===> FITS file folder %s cannot be opened\n\n", folder_pathname ); 73 | fprintf( stderr, "ERROR ===> FITS file folder %s cannot be opened\n\n", folder_pathname ); 74 | return(1); 75 | } 76 | 77 | 78 | while( entry = readdir( directoryfolder ) ) { //... listing of VID files 79 | 80 | if( strstr( entry->d_name, ".vid" ) != NULL ) fprintf( filelisting, "%s\n", entry->d_name ); 81 | 82 | } 83 | 84 | fclose( filelisting ); 85 | 86 | closedir( directoryfolder ); 87 | 88 | return(0); 89 | 90 | 91 | #endif /************* End of WINDOWS or LINUX *********************************/ 92 | 93 | } 94 | 95 | 96 | //################################################################################ 97 | //################################################################################ 98 | // 99 | // Sleep functions for Windows and Linux 100 | // 101 | //################################################################################ 102 | //################################################################################ 103 | 104 | void Delay_msec( int milliseconds ) 105 | { 106 | 107 | #ifdef _WIN32 /************* WINDOWS ******************************/ 108 | 109 | Sleep( milliseconds ); 110 | 111 | #else /********************** LINUX *******************************/ 112 | 113 | sleep( (unsigned int)( milliseconds / 1000 ) ); 114 | 115 | #endif /***********************************************************/ 116 | 117 | } 118 | 119 | //################################################################################ 120 | //################################################################################ 121 | // 122 | // Split a full pathname in to a folder and filename 123 | // 124 | //################################################################################ 125 | //################################################################################ 126 | 127 | void SplitPathname( char* fullpathname, char* folder_pathname, char* filename ) 128 | { 129 | long k, namelength, lastslash, slash; 130 | 131 | 132 | namelength = strlen( fullpathname ); 133 | 134 | #ifdef _WIN32 135 | slash = 92; 136 | #else //UNIX wants forward slashes 137 | slash = 47; 138 | #endif 139 | 140 | lastslash = strrchr( fullpathname, (int)slash ) - fullpathname; 141 | 142 | strcpy( folder_pathname, fullpathname ); //... include last slash 143 | 144 | folder_pathname[lastslash+1] = '\0'; 145 | folder_pathname[lastslash+2] = '\0'; 146 | 147 | for( k=lastslash+1; k max_dist: 103 | max_dist = c 104 | 105 | # saving the max lat and lon distance 106 | #if delta_lon > max_delta_lon: 107 | # max_delta_lon = delta_lon 108 | #if delta_lat > max_delta_lat: 109 | # max_delta_lat = delta_lat 110 | 111 | # fix the longitude extent 112 | #max_delta_lon = max_delta_lon * 2 113 | #max_delta_lat = max_delta_lat * 2 114 | max_delta_lon = max_dist * 2 115 | max_delta_lat = max_dist 116 | 117 | # Init the map 118 | request = cimgt.OSM() 119 | self.ax = plt.axes(projection=request.crs) 120 | 121 | # calculate map extent 122 | lon_min = np.degrees(lon_mean - max_delta_lon) 123 | lon_max = np.degrees(lon_mean + max_delta_lon) 124 | lat_min = np.degrees(lat_mean - max_delta_lat) 125 | lat_max = np.degrees(lat_mean + max_delta_lat) 126 | 127 | #lon_min = np.floor(lon_min%360) 128 | #at_min = np.floor(lat_min) 129 | #lon_max = np.ceil(lon_max%360) 130 | #lat_max = np.ceil(lat_max) 131 | 132 | # Handle the 0/360 boundary 133 | if lon_min > lon_max: 134 | lon_min = 0 135 | lon_max = 360 + lon_min 136 | 137 | else: 138 | if lon_min < 0: 139 | lon_min = 360 + lon_min 140 | 141 | if lon_max > 360: 142 | lon_max = lon_max - 360 143 | 144 | if lat_min < -90: 145 | lat_min = -90 146 | 147 | if lat_max > 90: 148 | lat_max = 90 149 | 150 | extent = [lon_min, lon_max, lat_min, lat_max] 151 | 152 | # setting map extent 153 | self.ax.set_extent(extent) 154 | 155 | # adding map with zoom ratio 156 | self.ax.add_image(request, 6) 157 | 158 | 159 | def scatter(self, lat_list, lon_list, **kwargs): 160 | """ Perform a scatter plot on the initialized map. 161 | 162 | Arguments: 163 | lon_list: [list] A list of longitudes (+E in radians) to plot. 164 | lat_list: [list] A list of latitudes (+N in radians) to plot. 165 | 166 | """ 167 | 168 | # Convert coordinates to map coordinates 169 | (x,y) = (np.degrees(lon_list), np.degrees(lat_list)) 170 | plt.scatter(x, y, **kwargs, transform=ccrs.PlateCarree()) 171 | 172 | return 0 173 | 174 | 175 | 176 | def plot(self, lat_list, lon_list, **kwargs): 177 | """ Plot a curve of given coordinates on the initialized map. 178 | 179 | Arguments: 180 | lon_list: [list] A list of longitudes (+E in radians) to plot. 181 | lat_list: [list] A list of latitudes (+N in radians) to plot. 182 | 183 | """ 184 | # Convert coordinates to map coordinates 185 | (x, y) = (np.degrees(lon_list), np.degrees(lat_list)) 186 | plt.plot(x,y, transform=ccrs.PlateCarree()) 187 | 188 | return 0 189 | 190 | 191 | 192 | 193 | if __name__ == "__main__": 194 | 195 | 196 | # Generate some geo coords 197 | lat_list = np.linspace(np.radians(48.783253), np.radians(53.658340), 10) 198 | #lon_list = np.linspace(np.radians(-4.949858), np.radians(1.100422), 10) 199 | lon_list = np.linspace(np.radians(354.949858), np.radians(361.100422), 10) 200 | #lat_list = np.append(lat_list, np.radians(50.030404)) 201 | #lat_list = np.append(lon_list, np.radians(51.425581)) 202 | 203 | 204 | # Test the ground plot function 205 | m = OSMMap(lat_list, lon_list, color_scheme='light') 206 | 207 | plt.scatter(lat_list, lon_list) 208 | 209 | plt.plot(lat_list, lon_list) 210 | 211 | plt.show() -------------------------------------------------------------------------------- /wmpl/Utils/Plotting.py: -------------------------------------------------------------------------------- 1 | """ Functions for plotting purposes. """ 2 | 3 | from __future__ import absolute_import, print_function, division 4 | 5 | import os 6 | 7 | import numpy as np 8 | from matplotlib.patches import FancyArrowPatch 9 | from mpl_toolkits.mplot3d import proj3d 10 | 11 | from wmpl.Config import config 12 | from wmpl.Utils.OSTools import mkdirP 13 | 14 | 15 | 16 | 17 | def saveImage(file_path, img, vmin=None, vmax=None, cmap=None, format=None, origin=None): 18 | """ Save numpy array to disk as image. """ 19 | 20 | from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas 21 | from matplotlib.figure import Figure 22 | 23 | fig = Figure(figsize=img.shape[::-1], dpi=1, frameon=False) 24 | FigureCanvas(fig) 25 | fig.figimage(img, cmap=cmap, vmin=vmin, vmax=vmax, origin=origin) 26 | fig.savefig(file_path, dpi=1, format=format) 27 | fig.clf() 28 | del fig 29 | 30 | 31 | 32 | 33 | def savePlot(plt_handle, file_path, output_dir='.', kwargs=None): 34 | """ Saves the plot to the given file path, with the DPI defined in configuration. 35 | 36 | Arguments: 37 | plt_handle: [object] handle to the plot to the saved (usually 'plt' in the main program) 38 | file_path: [string] file name and path to which the plot will be saved 39 | 40 | Keyword arguments: 41 | kwargs: [dictionary] Extra keyword arguments to be passed to savefig. None by default. 42 | 43 | """ 44 | 45 | 46 | if kwargs is None: 47 | kwargs = {} 48 | 49 | # Make the output directory, if it does not exist 50 | mkdirP(output_dir) 51 | 52 | # Save the plot (remove all surrounding white space) 53 | plt_handle.savefig(os.path.join(output_dir, file_path), dpi=config.plots_DPI, bbox_inches='tight', 54 | **kwargs) 55 | 56 | 57 | 58 | 59 | class Arrow3D(FancyArrowPatch): 60 | """ Arrow in 3D for plotting in matplotlib. 61 | 62 | Arguments: 63 | xs: [list of floats] (origin, destination) pair for the X axis 64 | ys: [list of floats] (origin, destination) pair for the Y axis 65 | zs: [list of floats] (origin, destination) pair for the Z axis 66 | 67 | Source: 68 | https://stackoverflow.com/questions/22867620/putting-arrowheads-on-vectors-in-matplotlibs-3d-plot 69 | 70 | """ 71 | def __init__(self, xs, ys, zs, *args, **kwargs): 72 | 73 | FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs) 74 | 75 | self._verts3d = xs, ys, zs 76 | 77 | 78 | def draw(self, renderer): 79 | self.do_3d_projection(renderer) 80 | 81 | def do_3d_projection(self, renderer=None): 82 | 83 | xs3d, ys3d, zs3d = self._verts3d 84 | 85 | xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M) 86 | 87 | self.set_positions((xs[0], ys[0]),(xs[1], ys[1])) 88 | 89 | if renderer is not None: 90 | FancyArrowPatch.draw(self, renderer) 91 | 92 | return np.min(zs) if zs.size else np.nan 93 | 94 | 95 | 96 | 97 | def set3DEqualAxes(ax): 98 | """ Make axes of 3D plot have equal scale so that spheres appear as spheres, cubes as cubes, etc. 99 | This is one possible solution to Matplotlib's ax.set_aspect('equal') and ax.axis('equal') not working 100 | for 3D. 101 | 102 | Source: https://stackoverflow.com/a/31364297 103 | 104 | Arguments: 105 | ax: [matplotlib axis] Axis handle of a 3D plot. 106 | """ 107 | 108 | x_limits = ax.get_xlim3d() 109 | y_limits = ax.get_ylim3d() 110 | z_limits = ax.get_zlim3d() 111 | 112 | x_range = abs(x_limits[1] - x_limits[0]) 113 | x_middle = np.mean(x_limits) 114 | y_range = abs(y_limits[1] - y_limits[0]) 115 | y_middle = np.mean(y_limits) 116 | z_range = abs(z_limits[1] - z_limits[0]) 117 | z_middle = np.mean(z_limits) 118 | 119 | # The plot bounding box is a sphere in the sense of the infinity norm, hence I call half the max range 120 | # the plot radius 121 | plot_radius = 0.5*max([x_range, y_range, z_range]) 122 | 123 | ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) 124 | ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) 125 | ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) 126 | 127 | 128 | 129 | 130 | if __name__ == '__main__': 131 | 132 | import matplotlib.pyplot as plt 133 | from mpl_toolkits.mplot3d import Axes3D 134 | 135 | 136 | fig = plt.figure() 137 | ax = fig.add_subplot(111, projection='3d') 138 | 139 | # Test the arrow function 140 | a = Arrow3D([0.2, 3], [1, -2], [0, 1], mutation_scale=20, lw=3, arrowstyle="-|>", color="r") 141 | 142 | ax.add_artist(a) 143 | 144 | plt.show() -------------------------------------------------------------------------------- /wmpl/Utils/PyDomainParallelizer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import multiprocessing 4 | 5 | from contextlib import closing 6 | 7 | 8 | 9 | def parallelComputeGenerator(generator, workerFunc, resultsCheckFunc, req_num, n_proc=None, 10 | results_check_kwagrs=None, max_runs=None): 11 | """ Given a generator which generates inputs for the workerFunc function, generate and process results 12 | until req_num number of results satisfies the resultsCheckFunc function. 13 | 14 | Arguments: 15 | generator: [generator] Generator function which creates inputs for the workerFunc. It should 16 | return a list of arguments that will be fed into the workerFunc. 17 | workerFunc: [function] Worker function. 18 | resultsCheckFunc: [function] A function which takes a lists of results and returns only those which 19 | satisfy some criteria. 20 | req_num: [int] Number of good results required. A good results is the one that passes the 21 | resultsCheckFunc check. 22 | 23 | Keyword arguments: 24 | n_proc: [int] Number of processes to use. None by default, in which case all available processors 25 | will be used. 26 | results_check_kwargs: [dict] Keyword arguments for resultsCheckFunc. None by default. 27 | max_runs: [int] Maximum number of runs. None by default, which will limit the runs to 10x req_num. 28 | 29 | Return: 30 | [list] A list of results. 31 | """ 32 | 33 | 34 | # If the number of processes was not given, use all available CPUs 35 | if (n_proc is None) or (n_proc == -1): 36 | n_proc = multiprocessing.cpu_count() 37 | 38 | 39 | if results_check_kwagrs is None: 40 | results_check_kwagrs = {} 41 | 42 | # Limit the maxlimum number or runs 43 | if max_runs is None: 44 | max_runs = 10*req_num 45 | 46 | 47 | # Init the pool 48 | with multiprocessing.Pool(processes=n_proc) as pool: 49 | 50 | results = [] 51 | 52 | total_runs = 0 53 | 54 | # Generate an initial input list 55 | input_list = [next(generator) for i in range(req_num)] 56 | 57 | # Run the initial list 58 | results = pool.map(workerFunc, input_list) 59 | 60 | total_runs += len(input_list) 61 | 62 | # Only take good results 63 | results = resultsCheckFunc(results, **results_check_kwagrs) 64 | 65 | 66 | # If there are None, do not continue, as there is obviously a problem 67 | if len(results) == 0: 68 | print("No successful results after the initial run!") 69 | return results 70 | 71 | 72 | # Run the processing until a required number of good values is returned 73 | while len(results) < req_num: 74 | 75 | # Generate an input for processing 76 | input_list = [next(generator) for i in range(n_proc)] 77 | 78 | # Map the inputs 79 | results_temp = pool.map(workerFunc, input_list) 80 | 81 | total_runs += len(input_list) 82 | 83 | # Only take good results 84 | results += resultsCheckFunc(results_temp, **results_check_kwagrs) 85 | 86 | # Check if the number of runs exceeded the maximum 87 | if total_runs >= max_runs: 88 | print("Total runs exceeded! Stopping...") 89 | break 90 | 91 | # Make sure that there are no more results than needed 92 | if len(results) > req_num: 93 | results = results[:req_num] 94 | 95 | return results 96 | 97 | 98 | 99 | def unpackDecorator(func): 100 | def dec(args): 101 | return func(*args) 102 | 103 | return dec 104 | 105 | 106 | def domainParallelizer(domain, function, cores=None, kwarg_dict=None): 107 | """ Runs N (cores) functions as separate processes with parameters given in the domain list. 108 | 109 | Arguments: 110 | domain: [list] a list of separate data (arguments) to feed individual function calls 111 | function: [function object] function that will be called with one entry from the domain 112 | 113 | Keyword arguments: 114 | cores: [int] Number of CPU cores, or number of parallel processes to run simultaneously. None by 115 | default, in which case all available cores will be used. 116 | kwarg_dict: [dictionary] a dictionary of keyword arguments to be passed to the function, None by default 117 | 118 | Return: 119 | results: [list] a list of function results 120 | 121 | """ 122 | 123 | 124 | def _logResult(result): 125 | """ Save the result from the async multiprocessing to a results list. """ 126 | 127 | results.append(result) 128 | 129 | 130 | if kwarg_dict is None: 131 | kwarg_dict = {} 132 | 133 | 134 | # If the number of cores was not given, use all available cores 135 | if cores is None: 136 | cores = multiprocessing.cpu_count() 137 | 138 | 139 | results = [] 140 | 141 | 142 | # Special case when running on only one core, run without multiprocessing 143 | if cores == 1: 144 | for args in domain: 145 | results.append(function(*args, **kwarg_dict)) 146 | 147 | 148 | # Run real multiprocessing if more than one core 149 | elif cores > 1: 150 | 151 | # Maximum number of jobs in waiting 152 | max_jobs = 10*cores 153 | 154 | # Generate a pool of workers 155 | with closing(multiprocessing.Pool(cores)) as pool: 156 | 157 | job_list = [] 158 | 159 | # Give workers things to do 160 | count = 0 161 | for args in domain: 162 | 163 | # Give job to worker 164 | last_job = pool.apply_async(function, args, kwarg_dict, callback=_logResult) 165 | 166 | job_list.append(last_job) 167 | 168 | # Limit the amount of jobs in waiting 169 | count += 1 170 | if count%cores == 0: 171 | if len(pool._cache) > max_jobs: 172 | last_job.wait() 173 | 174 | # Close the pool 175 | pool.close() 176 | 177 | # Wait for all jobs to finish 178 | pool.join() 179 | 180 | # Extract results from the pool 181 | results = [job.get() for job in job_list] 182 | 183 | 184 | else: 185 | print('The number of CPU cores defined is not in an expected range (1 or more.)') 186 | print('Use cpu_cores = 1 as a fallback value.') 187 | 188 | 189 | return results 190 | 191 | 192 | 193 | ############################## 194 | ## USAGE EXAMPLE 195 | 196 | 197 | import time 198 | import sys 199 | 200 | def mpWorker(inputs, wait_time): 201 | """ Example worker function. This function will print out the name of the worker and wait 'wait_time 202 | seconds. 203 | 204 | """ 205 | 206 | print(" Processs %s\tWaiting %s seconds" % (inputs, wait_time)) 207 | 208 | time.sleep(int(wait_time)) 209 | 210 | print(" Process %s\tDONE" % inputs) 211 | 212 | # Must use if you want print to be visible on the screen! 213 | sys.stdout.flush() 214 | 215 | return int(wait_time) 216 | 217 | 218 | 219 | if __name__ == '__main__': 220 | 221 | # List of function arguments for every run 222 | data = [ 223 | ['a', '2'], ['b', '4'], ['c', '6'], ['d', '8'], 224 | ['e', '1'], ['f', '3'], ['g', '5'], ['h', '7'] 225 | ] 226 | 227 | # Get the number of cpu cores available 228 | cpu_cores = multiprocessing.cpu_count() 229 | 230 | # Run the parallelized function 231 | results = domainParallelizer(data, mpWorker, cores=(cpu_cores - 1)) 232 | 233 | print('Results:', results) 234 | 235 | -------------------------------------------------------------------------------- /wmpl/Utils/ReplotMCUnc.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | from wmpl.Utils.Pickling import loadPickle 8 | from wmpl.Utils.Plotting import savePlot 9 | 10 | 11 | if __name__ == "__main__": 12 | 13 | 14 | import argparse 15 | 16 | ### COMMAND LINE ARGUMENTS 17 | 18 | # Init the command line arguments parser 19 | arg_parser = argparse.ArgumentParser(description=""" Replot the trajectory uncertainties.""", 20 | formatter_class=argparse.RawTextHelpFormatter) 21 | 22 | arg_parser.add_argument('mc_uncertainties_path', type=str, help='Path to the MC uncertainties file.') 23 | 24 | arg_parser.add_argument('-n', '--nbins', metavar="NUM_BINS", nargs=1, \ 25 | help='Number of bins for the histogram.', type=int) 26 | 27 | # Parse the command line arguments 28 | cml_args = arg_parser.parse_args() 29 | 30 | ############################ 31 | 32 | 33 | dir_path_mc, mc_unc_file = os.path.split(cml_args.mc_uncertainties_path) 34 | 35 | 36 | ### Load trajectory pickles 37 | 38 | # Load uncertainties 39 | traj_unc = loadPickle(dir_path_mc, mc_unc_file) 40 | 41 | 42 | # Extract file name core 43 | traj_file_name_core = mc_unc_file.replace('_mc_uncertainties.pickle', '') 44 | 45 | # Load geometrical trajectory 46 | dir_path_parent = os.path.abspath(os.path.join(dir_path_mc, os.pardir)) 47 | traj = loadPickle(dir_path_parent, traj_file_name_core + '_trajectory.pickle') 48 | 49 | # Load MC trajectory 50 | traj_best = loadPickle(dir_path_mc, traj_file_name_core + '_mc_trajectory.pickle') 51 | 52 | 53 | ### 54 | 55 | 56 | 57 | 58 | mc_results = traj_unc.mc_traj_list 59 | 60 | 61 | a_list = np.array([traj_temp.orbit.a for traj_temp in mc_results]) 62 | incl_list = np.array([traj_temp.orbit.i for traj_temp in mc_results]) 63 | e_list = np.array([traj_temp.orbit.e for traj_temp in mc_results]) 64 | peri_list = np.array([traj_temp.orbit.peri for traj_temp in mc_results]) 65 | q_list = np.array([traj_temp.orbit.q for traj_temp in mc_results]) 66 | 67 | fig = plt.figure() 68 | 69 | ax1 = fig.add_subplot(2, 2, 1) 70 | ax2 = fig.add_subplot(2, 2, 2, sharey=ax1) 71 | ax3 = fig.add_subplot(2, 2, 3) 72 | ax4 = fig.add_subplot(2, 2, 4, sharey=ax3) 73 | 74 | # Compute the number of bins 75 | if cml_args.nbins is not None: 76 | nbins = cml_args.nbins[0] 77 | 78 | else: 79 | nbins = np.ceil(np.sqrt(len(a_list))) 80 | if nbins < 10: 81 | nbins = 10 82 | 83 | # Semimajor axis vs. inclination 84 | ax1.hist2d(a_list, np.degrees(incl_list), bins=nbins) 85 | ax1.set_xlabel('a (AU)') 86 | ax1.set_ylabel('Inclination (deg)') 87 | plt.setp(ax1.get_xticklabels(), rotation=30, horizontalalignment='right') 88 | #ax1.get_xaxis().get_major_formatter().set_useOffset(False) 89 | ax1.ticklabel_format(useOffset=False) 90 | 91 | # Plot the first solution and the MC solution 92 | if traj is not None: 93 | if traj.orbit.a is not None: 94 | ax1.scatter(traj.orbit.a, np.degrees(traj.orbit.i), c='r', linewidth=1, edgecolors='w') 95 | 96 | if traj_best is not None: 97 | if traj_best.orbit.a is not None: 98 | ax1.scatter(traj_best.orbit.a, np.degrees(traj_best.orbit.i), c='g', linewidth=1, edgecolors='w') 99 | 100 | 101 | 102 | # Plot argument of perihelion vs. inclination 103 | ax2.hist2d(np.degrees(peri_list), np.degrees(incl_list), bins=nbins) 104 | ax2.set_xlabel('peri (deg)') 105 | plt.setp(ax2.get_xticklabels(), rotation=30, horizontalalignment='right') 106 | #ax2.get_xaxis().get_major_formatter().set_useOffset(False) 107 | ax2.ticklabel_format(useOffset=False) 108 | 109 | # Plot the first solution and the MC solution 110 | if traj is not None: 111 | if traj.orbit.peri is not None: 112 | ax2.scatter(np.degrees(traj.orbit.peri), np.degrees(traj.orbit.i), c='r', linewidth=1, \ 113 | edgecolors='w') 114 | 115 | if traj_best is not None: 116 | if traj_best.orbit.peri is not None: 117 | ax2.scatter(np.degrees(traj_best.orbit.peri), np.degrees(traj_best.orbit.i), c='g', linewidth=1, \ 118 | edgecolors='w') 119 | 120 | ax2.tick_params( 121 | axis='y', # changes apply to the y-axis 122 | which='both', # both major and minor ticks are affected 123 | left='off', # ticks along the left edge are off 124 | labelleft='off') # labels along the left edge are off 125 | 126 | 127 | # Plot eccentricity vs. perihelion distance 128 | ax3.hist2d(e_list, q_list, bins=nbins) 129 | ax3.set_xlabel('Eccentricity') 130 | ax3.set_ylabel('q (AU)') 131 | plt.setp(ax3.get_xticklabels(), rotation=30, horizontalalignment='right') 132 | #ax3.get_xaxis().get_major_formatter().set_useOffset(False) 133 | ax3.ticklabel_format(useOffset=False) 134 | 135 | # Plot the first solution and the MC solution 136 | if traj is not None: 137 | if traj.orbit.e is not None: 138 | ax3.scatter(traj.orbit.e, traj.orbit.q, c='r', linewidth=1, edgecolors='w') 139 | 140 | if traj_best is not None: 141 | if traj_best.orbit.e is not None: 142 | ax3.scatter(traj_best.orbit.e, traj_best.orbit.q, c='g', linewidth=1, edgecolors='w') 143 | 144 | # Plot argument of perihelion vs. perihelion distance 145 | ax4.hist2d(np.degrees(peri_list), q_list, bins=nbins) 146 | ax4.set_xlabel('peri (deg)') 147 | plt.setp(ax4.get_xticklabels(), rotation=30, horizontalalignment='right') 148 | #ax4.get_xaxis().get_major_formatter().set_useOffset(False) 149 | ax4.ticklabel_format(useOffset=False) 150 | 151 | # Plot the first solution and the MC solution 152 | if traj is not None: 153 | if traj.orbit.peri is not None: 154 | ax4.scatter(np.degrees(traj.orbit.peri), traj.orbit.q, c='r', linewidth=1, edgecolors='w') 155 | 156 | if traj_best is not None: 157 | if traj_best.orbit.peri is not None: 158 | ax4.scatter(np.degrees(traj_best.orbit.peri), traj_best.orbit.q, c='g', linewidth=1, \ 159 | edgecolors='w') 160 | 161 | 162 | ax4.tick_params( 163 | axis='y', # changes apply to the y-axis 164 | which='both', # both major and minor ticks are affected 165 | left='off', # ticks along the left edge are off 166 | labelleft='off') # labels along the left edge are off 167 | 168 | 169 | plt.tight_layout() 170 | plt.subplots_adjust(wspace=0) 171 | 172 | savePlot(plt, traj_file_name_core + '_monte_carlo_orbit_elems.png', output_dir=dir_path_mc) 173 | 174 | plt.show() -------------------------------------------------------------------------------- /wmpl/Utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/Utils/__init__.py -------------------------------------------------------------------------------- /wmpl/Utils/remoteDataHandling.py: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | # Copyright (c) 2024 Mark McIntyre 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | import os 24 | import paramiko 25 | import logging 26 | import glob 27 | import shutil 28 | 29 | from wmpl.Utils.OSTools import mkdirP 30 | from wmpl.Utils.Pickling import loadPickle 31 | 32 | 33 | log = logging.getLogger("traj_correlator") 34 | 35 | 36 | def collectRemoteTrajectories(remotehost, max_trajs, output_dir): 37 | """ 38 | Collect trajectory pickles from a remote server for local phase2 (monte-carlo) processing 39 | NB: do NOT use os.path.join here, as it will break on Windows 40 | """ 41 | 42 | ftpcli, remote_dir, sshcli = getSFTPConnection(remotehost) 43 | if ftpcli is None: 44 | return 45 | 46 | remote_phase1_dir = os.path.join(remote_dir, 'phase1').replace('\\','/') 47 | 48 | log.info(f'Looking in {remote_phase1_dir} on remote host for up to {max_trajs} trajectories') 49 | 50 | try: 51 | files = ftpcli.listdir(remote_phase1_dir) 52 | files = [f for f in files if '.pickle' in f and 'processing' not in f] 53 | files = files[:max_trajs] 54 | 55 | if len(files) == 0: 56 | log.info('no data available at this time') 57 | ftpcli.close() 58 | sshcli.close() 59 | return 60 | 61 | for trajfile in files: 62 | fullname = os.path.join(remote_phase1_dir, trajfile).replace('\\','/') 63 | localname = os.path.join(output_dir, trajfile) 64 | ftpcli.get(fullname, localname) 65 | ftpcli.rename(fullname, f'{fullname}_processing') 66 | 67 | log.info(f'Obtained {len(files)} trajectories') 68 | 69 | 70 | except Exception as e: 71 | log.warning('Problem with download') 72 | log.info(e) 73 | 74 | ftpcli.close() 75 | sshcli.close() 76 | 77 | return 78 | 79 | 80 | def uploadTrajToRemote(remotehost, trajfile, output_dir): 81 | """ 82 | At the end of MC phase, upload the trajectory pickle and report to a remote host for integration 83 | into the solved dataset 84 | """ 85 | 86 | ftpcli, remote_dir, sshcli = getSFTPConnection(remotehost) 87 | if ftpcli is None: 88 | return 89 | 90 | remote_phase2_dir = os.path.join(remote_dir, 'remoteuploads').replace('\\','/') 91 | try: 92 | ftpcli.mkdir(remote_phase2_dir) 93 | except Exception: 94 | pass 95 | 96 | localname = os.path.join(output_dir, trajfile) 97 | remotename = os.path.join(remote_phase2_dir, trajfile).replace('\\','/') 98 | ftpcli.put(localname, remotename) 99 | 100 | localname = localname.replace('_trajectory.pickle', '_report.txt') 101 | remotename = remotename.replace('_trajectory.pickle', '_report.txt') 102 | if os.path.isfile(localname): 103 | ftpcli.put(localname, remotename) 104 | 105 | ftpcli.close() 106 | sshcli.close() 107 | return 108 | 109 | 110 | def moveRemoteTrajectories(output_dir): 111 | """ 112 | Move remotely processed pickle files to their target location in the trajectories area, 113 | making sure we clean up any previously-calculated trajectory and temporary files 114 | """ 115 | 116 | phase2_dir = os.path.join(output_dir, 'remoteuploads') 117 | 118 | if os.path.isdir(phase2_dir): 119 | log.info('Checking for remotely calculated trajectories...') 120 | pickles = glob.glob1(phase2_dir, '*.pickle') 121 | 122 | for pick in pickles: 123 | traj = loadPickle(phase2_dir, pick) 124 | phase1_name = traj.pre_mc_longname 125 | traj_dir = f'{output_dir}/trajectories/{phase1_name[:4]}/{phase1_name[:6]}/{phase1_name[:8]}/{phase1_name}' 126 | if os.path.isdir(traj_dir): 127 | shutil.rmtree(traj_dir) 128 | processed_traj_file = os.path.join(output_dir, 'phase1', phase1_name + '_trajectory.pickle_processing') 129 | 130 | if os.path.isfile(processed_traj_file): 131 | log.info(f' Moving {phase1_name} to processed folder...') 132 | dst = os.path.join(output_dir, 'phase1', 'processed', phase1_name + '_trajectory.pickle') 133 | shutil.copyfile(processed_traj_file, dst) 134 | os.remove(processed_traj_file) 135 | 136 | phase2_name = traj.longname 137 | traj_dir = f'{output_dir}/trajectories/{phase2_name[:4]}/{phase2_name[:6]}/{phase2_name[:8]}/{phase2_name}' 138 | mkdirP(traj_dir) 139 | log.info(f' Moving {phase2_name} to {traj_dir}...') 140 | src = os.path.join(phase2_dir, pick) 141 | dst = os.path.join(traj_dir, pick[:15]+'_trajectory.pickle') 142 | 143 | shutil.copyfile(src, dst) 144 | os.remove(src) 145 | 146 | report_file = src.replace('_trajectory.pickle','_report.txt') 147 | if os.path.isfile(report_file): 148 | dst = dst.replace('_trajectory.pickle','_report.txt') 149 | shutil.copyfile(report_file, dst) 150 | os.remove(report_file) 151 | 152 | log.info(f'Moved {len(pickles)} trajectories.') 153 | 154 | return 155 | 156 | 157 | def getSFTPConnection(remotehost): 158 | 159 | hostdets = remotehost.split(':') 160 | 161 | if len(hostdets) < 2 or '@' not in hostdets[0]: 162 | log.warning(f'{remotehost} malformed, should be user@host:port:/path/to/dataroot') 163 | return None, None, None 164 | 165 | if len(hostdets) == 3: 166 | port = int(hostdets[1]) 167 | remote_data_dir = hostdets[2] 168 | 169 | else: 170 | port = 22 171 | remote_data_dir = hostdets[1] 172 | 173 | user,host = hostdets[0].split('@') 174 | log.info(f'Connecting to {host}....') 175 | 176 | 177 | ssh_client = paramiko.SSHClient() 178 | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 179 | 180 | 181 | if not os.path.isfile(os.path.expanduser('~/.ssh/trajsolver')): 182 | log.warning('ssh keyfile ~/.ssh/trajsolver missing') 183 | ssh_client.close() 184 | return None, None, None 185 | 186 | pkey = paramiko.RSAKey.from_private_key_file(os.path.expanduser('~/.ssh/trajsolver')) 187 | try: 188 | ssh_client.connect(hostname=host, username=user, port=port, pkey=pkey, look_for_keys=False) 189 | ftp_client = ssh_client.open_sftp() 190 | return ftp_client, remote_data_dir, ssh_client 191 | 192 | except Exception as e: 193 | 194 | log.warning('sftp connection to remote host failed') 195 | log.warning(e) 196 | ssh_client.close() 197 | 198 | return None, None, None 199 | -------------------------------------------------------------------------------- /wmpl/__init__.py: -------------------------------------------------------------------------------- 1 | """ Import all submodules. """ 2 | 3 | 4 | import pkgutil 5 | 6 | # Excluded packages 7 | exclude = ["MetSim.ML", "GUI"] 8 | 9 | __all__ = [] 10 | for loader, module_name, is_pkg in pkgutil.walk_packages(__path__): 11 | 12 | # Skip the config module 13 | if 'Config' in module_name: 14 | continue 15 | 16 | # Skip Supracenter 17 | if 'Supracenter' in module_name: 18 | continue 19 | 20 | 21 | ### Skip exluded packages ### 22 | skip_package = False 23 | for exclude_test in exclude: 24 | if exclude_test in module_name: 25 | skip_package = True 26 | break 27 | 28 | if skip_package: 29 | continue 30 | 31 | ### ### 32 | 33 | 34 | __all__.append(module_name) 35 | module = loader.find_module(module_name).load_module(module_name) 36 | exec('%s = module' % module_name) -------------------------------------------------------------------------------- /wmpl/share/Meteorites_with_Orbits.csv: -------------------------------------------------------------------------------- 1 | # Updated on: 2024/03/21;;;;;;;;;;;;;;;;;; 2 | # Ab.;Name;Type;Country;Date (UTC);Time (UTC);Time Zone;Mass (kg);a (AU);a error (AU);e;e error (AU);i (°);i error (°);Arg. Peri. (°);Arg. Peri. Error (°);Asc. node (°);Asc. node error (°);Ref. 3 | RI;Ribbeck;Aubrite;Germany;2024-01-21;00:32:38;UTC+1;140/0.983;1.3344;;0.374;;7.264;;243.602;;300.092;;SP24 4 | SV;Saint-Pierre-le-Viger;L5-6;France;2023-02-13;02:59:21;UTC+1;??? / 1.2;1.6854;;0.4552;;3.5649;;219.0364;;323.8326;;JPL-SB-DB 5 | KH;Al-Khadhaf;H5-6;Oman;2022-03-08;20:15:00;UTC+4;??? / 0.0221;1.72;;0.45;;4.36;;???;;???;;MB112 6 | GO;Golden;L/LL5;Canada;2021-10-04;05:34;UTC-6;70-200 / 2.39;1.58;;0.366;;23.5;;177.04;;190.9243;;BR23 7 | AT;Antonin;L5;Poland;2021-07-15;03:00:11;UTC+2;50-500 / 0.350;1.1269;;0.2285;;24.22;;257.16;;112.5807;;SH22 8 | WI;Winchcombe;CC;United Kingdom;2021-02-28;22:54:15;UTC+1;66±10;2.585527;0.007708;0.618322;0.001136;0.459586;0.013477;351.798163;0.017501;160.195475;0.001375;KI22 9 | TR;Traspena;L5;Spain;2021-01-18;00:18:56;UTC+1;2620 / 0.527;1.125;;0.386;;4.55;;297.827;;273.93;;AN22 10 | KI;Kindberg;L6;Austria;2020-11-19;03:46:55;UTC+1;??? / 0.223;2;;0.533;;5;;205.5;;237;;KO21 11 | AA;�dalen;Iron;Sweden;2020-11-07;21:27:04;UTC+1;??? / 13.8;1.9;;0.53;;15.22;;223.91;;226.189;;KY23 12 | SF;Santa Filomena;H5-6;Brazil;2020-08-19;13:18:00;UTC-3;??? / 80;2.1;;0.56;;0.15;;319.56;;326.01;;TO23 13 | MC;Madura Cave;L5;Australia;2020-06-19;20:05:07;UTC+8;~30-60 / 1.072;0.889;;0.327;;0.12;;312.02;;88.70376479;;DE22 14 | NM;Novo Mesto;L5;Slovenia;2020-02-28;09:30:32;UTC+1;4500;1.451;0.004;0.60866;0.0006;8.755;0.063;82.649;0.184;338.993041;;VI21 15 | CA;Cavezzo;L5-an;Italy;2020-01-01;18:26:54;UTC+1;3.5±0.8 / 0.055;1.82;0.22;0.46;0.063;4;1.6;179.2;4.8;280.5;4.8;GA20 16 | FL;Flensburg;C1-ungr.;Germany;2019-09-12;12:49:48;UTC+2;10k-20k / 0.0245;2.82;0.03;0.701;0.003;6.82;0.06;307.25;0.16;349.207;0.001;BO21 17 | VI;Vinales;L6;Cuba;2019-02-01;18:17:10;UTC-5;50 / ???;1.217;;0.391;;11.47;;276.97;;132.28;;ZU19 CI21 18 | OZ;Ozerki;L6;Russia;2018-06-21;01:16:20;UTC+3;94k�20k / 6.5;0.84;;0.199;;18.44;;335.29;;89.6561;;KA20 19 | MP;Motopi Pan;Howardite;Botswana;2018-06-02;16:44:01;UTC+2;5.5k / 0.214;1.3764;0.00011;0.431861;0.000061;4.29741;0.00043;256.04869;0.00055;71.869605;0.000012;JE21 20 | HA;Hamburg;H4;USA;2018-01-17;01:08:29;UTC-4;60 - 225 / 1.0;2.73;0.05;0.661;0.006;0.604;0.11;211.65;0.3;296.421;0.03;BR19 21 | DD;Dingle Dell;L/LL5;Australia;2016-10-31;12:03:47;UTC+8;40 / 1.15;2.254;0.034;0.5904;0.0063;4.051;0.012;215.773;0.049;218.252;0.00032;DE18 22 | DI;Dishchii'bikoh;LL7;USA;2016-06-02;10:56:26;UTC-7;3k-15k / 0.0795;1.129;0.008;0.205;0.004;21.24;0.27;108.7;1.5;72.1206;0.0002;JE20 23 | ST;Stubenberg;LL6;Germany;2016-03-06;21:36:51;UTC+1;600 / 1.47;1.525;0.01;0.395;0.004;2.07;0.03;221.02;0.03;346.52;;SP16b 24 | EJ;Ejby;H5/6;Denmark;2016-02-06;21:07:18;UTC+1;250±100 / 8.94;2.81;0.09;0.655;0.011;0.96;0.1;197.75;0.1;317.211;;SP17 25 | OS;Osceola;L6;USA;2016-01-24;15:27:00;UTC-5;>1800 / 1.099;1.486;;0.3406;;13.2;;169;;303.9;;ME20 26 | MU;Murrili;H5;Australia;2015-11-27;10:43:45;UTC+9.5;??? / 1.68;2.62;;0.62;;3.58;;356.3;;64.63;;BL16 27 | CR;Creston;L6;USA;2015-10-24;05:47:44;UTC-7;10-100 / 0.688;1.3;0.019;0.41;0.013;4.228;0.07;79.2;0.13;30.458;0.006;JE19 28 | SA;Sariçiçek;Howardite;Turkey;2015-09-02;20:11:36;UTC+3;ca. 6 - 20k / 15.24;1.454;0.083;0.304;0.039;22.6;1.6;182.8;1.6;159.849;0.004;UN19 29 | PO;Porangaba;L4;Brazil;2015-01-09;17:35:00;UTC-2;0.976 / ?;2.54;1.1;0.64;0.11;8.6;3.2;142.8;6.7;288.921;0.001;FE20 30 | ZD;Žd'ár nad Sázavou;L3;Czech Rep.;2014-12-09;16:16:45;UTC+1;170 / 0.05;2.093;0.006;0.6792;0.001;2.796;0.009;257.721;0.014;257.262;0.01;SP20 31 | AN;Annama;H5;Russia;2014-04-18;22:14:09;UTC+4;472 / 0.17;1.99;0.12;0.69;0.02;14.65;0.46;264.77;0.55;28.611;0.001;TR15 32 | CH;Chelyabinsk;LL5;Russia;2013-02-15;03:22:00;UTC+6;ca. 1.2M / ca. 1k;1.72;0.02;0.571;0.006;4.98;0.12;107.67;0.17;326.459;0.001;BO13b 33 | NO;Novato;L6;USA;2012-10-18;02:44:00;UTC-7;80±35 / 0.31;2.088;0.077;0.526;0.017;5.508;0.04;347.352;0.134;24.99;0.0035;JE14 34 | SM;Sutter's Mill;C CM2;USA;2012-04-22;14:51:10;UTC-7;50k±30k / 0.99;2.59;0.35;0.824;0.02;2.38;1.16;77.8;3.2;32.77;0.06;JE12 35 | KR;Križevci;H6;Croatia;2011-02-04;23:20:41;UTC+1;25-100 / 0.29;1.544;0.01;0.521;0.004;0.64;0.03;254.4;0.1;315.55;0.01;BO15 36 | MG;Mason Gully;H5;Australia;2010-04-13;10:36:10;UTC+8;ca. 40 / 0.02;2.47;0.004;0.6023;0.0007;0.832;0.013;18.95;0.03;203.2112;0.00005;SP12b 37 | KO;Košice;H5;Slovakia;2010-02-28;22:24:46;UTC+1;ca. 3500 / 4.3;2.71;0.24;0.647;0.032;2;0.8;204.2;1.2;340.072;0.004;BO13a 38 | GR;Grimsby;H5;Canada;2009-09-26;01:03:00;UTC-4;33±16 / 0.22;2.04;0.05;0.518;0.011;28.07;0.28;159.865;0.43;182.9561;0.00005;BR11 39 | JE;Jesenice;L6;Slovenia;2009-04-09;00:59:41;UTC+2;170±80 / 3.67;1.75;0.07;0.431;0.022;9.6;0.5;190.5;0.5;19.196;0.0005;SP10 40 | MA;Maribo;CM2;Denmark;2009-01-17;19:09:00;UTC+1;1500 / 0.03;2.48;0.2;0.807;0.011;0.11;0.5;279.2;3.8;297.122;;HA12 JE12 SP13 41 | BC;Buzzard Coulee;H4;Canada;2008-11-21;00:26:40;UTC-7;ca. 15k / 41;1.25;0.02;0.23;0.02;25;0.8;211.3;1.4;238.93739;0.00008;MI10 42 | AS;Almahata Sitta;Ureilite +other;Sudan;2008-10-07;02:45:40;UTC+3;83k±25k / 3.95;1.3082;;0.3121;;2.5422;;234.449;;194.1011;;JE09 43 | BR;Bunburra Rockhole;Eurcite;Australia;2007-07-20;19:13:53;UTC+4.5;ca. 22 / 0.32;0.8529;0.0004;0.2427;0.0005;8.95;0.03;210.04;0.06;297.595;0.0005;BL09 SP12a 44 | VP;Villalbeto de la Peña;L6;Spain;2004-01-04;16:47:00;UTC+1;760±150 / 3.5;2.3;0.2;0.63;0.04;0;0.2;132.3;1.5;283.6712;0.00005;TR06 45 | PF;Park Forest;L5;USA;2003-03-27;05:50:00;UTC-6;11k±3k / 18;2.53;0.19;0.68;0.023;3.2;0.3;237.5;1.6;6.1156;0.0007;BR04 46 | NE;Neuschwan- stein;EL6;Germany;2002-04-06;20:20:18;UTC+2;300±100 / 6.19;2.4;0.02;0.67;0.002;11.41;0.03;241.2;0.06;16.82664;0.00001;SP03 47 | MO;Morávka;H5;Czech Rep.;2000-05-06;11:51:52;UTC+2;1500±500 / 0.63;1.85;0.07;0.47;0.02;32.2;0.5;203.5;0.6;46.258;0.00005;BO03 48 | TL;Tagish Lake;C2-ung.;Canada;2000-01-18;16:43:42;UTC-8;75k±15k / 10;2.1;0.2;0.57;0.05;1.4;0.9;222;2;297.9;0.003;BR00 49 | PE;Peekskill;H6;USA;1992-10-09;23:48:00;UTC-4;<10k / 12.57;1.49;0.03;0.41;0.01;4.9;0.2;308;1;17.03;0.001;BR94 50 | BE;Benešov;LL3.5 H5;Czech Rep.;1991-05-07;23:03:46;UTC+2;4100±100 / 0.01;2.483;0.002;0.6274;0.0004;23.981;0.007;218.37;0.008;47.001;;CR05 SP14 51 | IN;Innisfree;L5;Canada;1977-02-06;02:17:38;UTC-7;42±2 / 4.58;1.87;;0.473;;12.27;;178;;316.8;;CR05 HA78 52 | LC;Lost City;H5;USA;1970-01-04;02:14:00;UTC-6;165±6 / 17;1.66;;0.417;;12;;161;;283;;CR05 MC71 53 | PR;Příbram;H5;Czech Rep.;1959-04-07;19:30:20;UTC+1;<5000 / 5.56;2.401;0.002;0.6711;0.0003;10.482;0.0004;241.75;0.013;17.79147;0.00001;SP03 CE61 54 | -------------------------------------------------------------------------------- /wmpl/share/WW15MGH.DAC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/share/WW15MGH.DAC -------------------------------------------------------------------------------- /wmpl/share/shower_parameters.txt: -------------------------------------------------------------------------------- 1 | Name LaSun_max ZHRmax B 2 | Boo 282.62 133 1.8 3 | gVe 285.0 2.4 0.12 4 | aCu 294.7 3.0 0.11 5 | aCa 310.5 2.3 0.16 6 | aCe 318.7 7.3 0.18 7 | oCe 322.7 2.2 0.15 8 | dLe 334 1.1 0.049 9 | gNo 352.3 5.8 0.19 10 | dPa 370.4 5.3 0.075 11 | Lyt 31.7 12.8 0.22 12 | mVi 39 2.2 0.045 13 | eAq 45.8 36.7 0.08 14 | Sco 55.2 3.2 0.13 15 | oSc 71.9 5.2 0.15 16 | Ari 76 54 0.10 17 | Sag 88.5 2.4 0.037 18 | Cet 95.0 3.6 0.18 19 | Oph 97 2.3 0.037 20 | tAq 97.3 7.1 0.24 21 | uPh 110.5 5.0 0.25 22 | oCy 116.0 2.5 0.13 23 | Cap 121.7 2.2 0.041 24 | dAN 123.4 1.0 0.063 25 | PAu 123.7 2.9 0.26 26 | dAZ 124.9 11.4 0.091 27 | iAZ 131.0 1.5 0.07 28 | Per 139.49 84 0.2 29 | kCy 146.0 2.3 0.069 30 | gDo 155.0 4.8 0.18 31 | Aur 157.5 9 0.19 32 | kAq 176.5 2.7 0.11 33 | Eps 206.0 2.9 0.082 34 | Ori 207.9 25 0.12 35 | LMi 209.0 1.9 0.14 36 | Tau 222.9 7.3 0.026 37 | zPu 231.5 3.2 0.13 38 | Leo 234.4 23 0.39 39 | PuV 251 4.5 0.034 40 | Pho 251.7 2.8 0.3 41 | Mon 260.2 2.0 0.25 42 | Gem 261.4 88 0.39 43 | sHy 264.8 2.5 0 10 44 | Urs 270.3 11.8 0.61 -------------------------------------------------------------------------------- /wmpl/share/tai-utc.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wmpg/WesternMeteorPyLib/3d82b916d20325798c0e673842a307a7b54686af/wmpl/share/tai-utc.dat --------------------------------------------------------------------------------