spacecoords 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of spacecoords might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2025] [Daniel Kastinen]
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.
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: spacecoords
3
+ Version: 0.1.0
4
+ Summary: A collection of coordinate transforms for convenient use
5
+ Author-email: Daniel Kastinen <daniel.kastinen@irf.se>
6
+ Maintainer-email: Daniel Kastinen <daniel.kastinen@irf.se>
7
+ License: MIT License
8
+
9
+ Copyright (c) [2025] [Daniel Kastinen]
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Documentation, https://danielk.developer.irf.se/spacecoords/
30
+ Project-URL: Repository, https://github.com/danielk333/spacecoords
31
+ Classifier: Intended Audience :: Science/Research
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Topic :: Scientific/Engineering :: Physics
34
+ Classifier: Operating System :: OS Independent
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Requires-Python: >=3.10
37
+ Description-Content-Type: text/markdown
38
+ License-File: LICENSE
39
+ Requires-Dist: numpy>=2
40
+ Provides-Extra: all
41
+ Requires-Dist: astropy>=6; extra == "all"
42
+ Requires-Dist: spiceypy>=8.0.0; extra == "all"
43
+ Requires-Dist: jplephem>=2.23; extra == "all"
44
+ Requires-Dist: requests>=2.20; extra == "all"
45
+ Provides-Extra: develop
46
+ Requires-Dist: pytest; extra == "develop"
47
+ Requires-Dist: pytest-cov; extra == "develop"
48
+ Requires-Dist: flake8; extra == "develop"
49
+ Requires-Dist: wheel; extra == "develop"
50
+ Requires-Dist: build; extra == "develop"
51
+ Requires-Dist: twine; extra == "develop"
52
+ Requires-Dist: auditwheel; extra == "develop"
53
+ Requires-Dist: numpydoc; extra == "develop"
54
+ Requires-Dist: black; extra == "develop"
55
+ Requires-Dist: matplotlib; extra == "develop"
56
+ Requires-Dist: ipykernel; extra == "develop"
57
+ Requires-Dist: mkdocs-material; extra == "develop"
58
+ Requires-Dist: mkdocstrings[python]; extra == "develop"
59
+ Requires-Dist: mkdocs-jupyter; extra == "develop"
60
+ Requires-Dist: mkdocs-gen-files; extra == "develop"
61
+ Requires-Dist: mkdocs-literate-nav; extra == "develop"
62
+ Requires-Dist: mkdocs-section-index; extra == "develop"
63
+ Provides-Extra: tests
64
+ Requires-Dist: pytest; extra == "tests"
65
+ Requires-Dist: pytest-cov; extra == "tests"
66
+ Dynamic: license-file
67
+
68
+ # spacecoords
69
+
70
+ A collection of coordinate transforms for convenient use across our applications to avoid
71
+ duplication
@@ -0,0 +1,4 @@
1
+ # spacecoords
2
+
3
+ A collection of coordinate transforms for convenient use across our applications to avoid
4
+ duplication
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spacecoords"
7
+ version = "0.1.0"
8
+ description = "A collection of coordinate transforms for convenient use"
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ authors = [{ name = "Daniel Kastinen", email = "daniel.kastinen@irf.se" }]
11
+ maintainers = [{ name = "Daniel Kastinen", email = "daniel.kastinen@irf.se" }]
12
+ license = { file = "LICENSE" }
13
+ classifiers = [
14
+ "Intended Audience :: Science/Research",
15
+ "Programming Language :: Python :: 3",
16
+ "Topic :: Scientific/Engineering :: Physics",
17
+ "Operating System :: OS Independent",
18
+ "License :: OSI Approved :: MIT License",
19
+ ]
20
+ requires-python = ">=3.10"
21
+ dependencies = ["numpy >= 2"]
22
+
23
+ [project.urls]
24
+ Documentation = "https://danielk.developer.irf.se/spacecoords/"
25
+ Repository = "https://github.com/danielk333/spacecoords"
26
+
27
+
28
+ [project.optional-dependencies]
29
+ all = ["astropy>=6", "spiceypy>=8.0.0", "jplephem>=2.23", "requests>=2.20"]
30
+ develop = [
31
+ "pytest",
32
+ "pytest-cov",
33
+ "flake8",
34
+ "wheel",
35
+ "build",
36
+ "twine",
37
+ "auditwheel",
38
+ "numpydoc",
39
+ "black",
40
+ "matplotlib",
41
+ "ipykernel",
42
+ "mkdocs-material",
43
+ "mkdocstrings[python]",
44
+ "mkdocs-jupyter",
45
+ "mkdocs-gen-files",
46
+ "mkdocs-literate-nav",
47
+ "mkdocs-section-index",
48
+ ]
49
+ tests = ["pytest", "pytest-cov"]
50
+
51
+
52
+ [tool.setuptools]
53
+ include-package-data = true
54
+ license-files = ["LICENSE"]
55
+
56
+ [project.scripts]
57
+ spacecoords = "spacecoords.cli:main"
58
+
59
+ [tool.setuptools.packages.find]
60
+ where = ["src"]
61
+
62
+ [tool.black]
63
+ line-length = 110
64
+
65
+
66
+ [tool.pytest.ini_options]
67
+ addopts = "-v --cov=spacecoords"
68
+ testpaths = ["tests"]
69
+
70
+ [tool.coverage.report]
71
+ exclude_also = [
72
+ # Don't complain if tests don't hit defensive assertion code:
73
+ "raise AssertionError",
74
+ "raise NotImplementedError",
75
+
76
+ # Don't complain if non-runnable code isn't run:
77
+ "if 0:",
78
+ "if __name__ == .__main__.:",
79
+
80
+ # Don't complain about abstract methods, they aren't run:
81
+ "@(abc\\.)?abstractmethod",
82
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """General purpose convenience functions for different coordinate systems and linear algebra
2
+ functions
3
+ """
4
+
5
+ from types import ModuleType
6
+ import importlib.util
7
+ from .version import __version__
8
+
9
+ from . import linalg
10
+ from . import spherical
11
+ from . import constants
12
+
13
+
14
+ def _make_missing_module(name: str, dep: str):
15
+ class _MissingModule(ModuleType):
16
+ def __getattr__(self, key):
17
+ raise ImportError(
18
+ f"The optional dependency `{dep}` for is missing.\n"
19
+ f"Install it with `pip install spacecoords[all]` or `pip install {dep}`."
20
+ )
21
+
22
+ return _MissingModule(name)
23
+
24
+
25
+ # Optional modules
26
+ if importlib.util.find_spec("astropy") is not None:
27
+ from . import celestial
28
+ else:
29
+ astropy = _make_missing_module("celestial", "astropy")
30
+
31
+ if importlib.util.find_spec("jplephem") is not None:
32
+ from . import spk_basic
33
+ else:
34
+ naif_ephemeris = _make_missing_module("spk_basic", "jplephem")
35
+
36
+ if importlib.util.find_spec("spice") is not None:
37
+ from . import spice
38
+ else:
39
+ naif_spice = _make_missing_module("spice", "spiceypy")
40
+
41
+ if importlib.util.find_spec("requests") is not None:
42
+ from . import download
43
+ else:
44
+ download = _make_missing_module("download", "requests")
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python
2
+
3
+ """Coordinate frame transformations and related functions.
4
+ Main usage is the `convert` function that wraps Astropy frame transformations.
5
+ """
6
+ from typing import Type, Any
7
+ from pathlib import Path
8
+ import numpy as np
9
+ from astropy.time import Time
10
+ import astropy.coordinates as coord
11
+ import astropy.units as units
12
+ import astropy.config as config
13
+
14
+ from .types import (
15
+ NDArray_N,
16
+ NDArray_3,
17
+ NDArray_6,
18
+ NDArray_3xN,
19
+ NDArray_6xN,
20
+ T,
21
+ )
22
+
23
+ """List of astropy frames
24
+ """
25
+ ASTROPY_FRAMES = {
26
+ "TEME": "TEME",
27
+ "ITRS": "ITRS",
28
+ "ITRF": "ITRS",
29
+ "ICRS": "ICRS",
30
+ "ICRF": "ICRS",
31
+ "GCRS": "GCRS",
32
+ "GCRF": "GCRS",
33
+ "HCRS": "HCRS",
34
+ "HCRF": "HCRS",
35
+ "HeliocentricMeanEcliptic".upper(): "HeliocentricMeanEcliptic",
36
+ "GeocentricMeanEcliptic".upper(): "GeocentricMeanEcliptic",
37
+ "HeliocentricTrueEcliptic".upper(): "HeliocentricTrueEcliptic",
38
+ "GeocentricTrueEcliptic".upper(): "GeocentricTrueEcliptic",
39
+ "BarycentricMeanEcliptic".upper(): "BarycentricMeanEcliptic",
40
+ "BarycentricTrueEcliptic".upper(): "BarycentricTrueEcliptic",
41
+ "SPICEJ2000": "ICRS",
42
+ }
43
+
44
+ """List of frames that are not time-dependant
45
+ """
46
+ ASTROPY_NOT_OBSTIME = [
47
+ "ICRS",
48
+ "BarycentricMeanEcliptic",
49
+ "BarycentricTrueEcliptic",
50
+ ]
51
+
52
+
53
+ def get_solarsystem_body_state(
54
+ body: str,
55
+ time: Time,
56
+ kernel_dir: Path,
57
+ ephemeris: str = "jpl",
58
+ ) -> NDArray_6xN | NDArray_6:
59
+ """
60
+
61
+ This is to not have to remember how to do this astropy config stuff
62
+ # https://docs.astropy.org/en/stable/api/astropy.coordinates.solar_system_ephemeris.html
63
+ """
64
+ with config.set_temp_cache(path=str(kernel_dir), delete=False):
65
+ pos, vel = coord.get_body_barycentric_posvel(body, time, ephemeris=ephemeris)
66
+
67
+ size = len(time)
68
+ shape: tuple[int, ...] = (6, size) if size > 0 else (6,)
69
+ state = np.empty(shape, dtype=np.float64)
70
+ state[:3, ...] = pos.xyz.to(units.m).value
71
+ state[3:, ...] = vel.d_xyz.to(units.m / units.s).value
72
+ return state
73
+
74
+
75
+ def not_geocentric(frame: str) -> bool:
76
+ """Check if the given frame name is one of the non-geocentric frames."""
77
+ frame = frame.upper()
78
+ return frame in ["ICRS", "ICRF", "HCRS", "HCRF"] or frame.startswith("Heliocentric".upper())
79
+
80
+
81
+ def is_geocentric(frame: str) -> bool:
82
+ """Check if the frame name is a supported geocentric frame"""
83
+ return not not_geocentric(frame)
84
+
85
+
86
+ def convert(
87
+ t: NDArray_N,
88
+ states: NDArray_6xN,
89
+ in_frame: str,
90
+ out_frame: str,
91
+ frame_kwargs: dict[str, Any],
92
+ ) -> NDArray_6xN:
93
+ """Perform predefined coordinate transformations using Astropy.
94
+ Always returns a copy of the array.
95
+
96
+ Parameters
97
+ ----------
98
+ t
99
+ Absolute time corresponding to the input states.
100
+ states
101
+ Size `(6,n)` matrix of states in SI units where rows 1-3
102
+ are position and 4-6 are velocity.
103
+ in_frame
104
+ Name of the frame the input states are currently in.
105
+ out_frame
106
+ Name of the state to transform to.
107
+ frame_kwargs
108
+ Any arguments needed for the specific transform detailed by `astropy`
109
+ in their documentation
110
+
111
+ Returns
112
+ -------
113
+ Size `(6,n)` matrix of states in SI units where rows
114
+ 1-3 are position and 4-6 are velocity.
115
+
116
+ """
117
+
118
+ in_frame = in_frame.upper()
119
+ out_frame = out_frame.upper()
120
+
121
+ if in_frame == out_frame:
122
+ return states.copy()
123
+
124
+ if in_frame in ASTROPY_FRAMES:
125
+ in_frame_ = ASTROPY_FRAMES[in_frame]
126
+ in_frame_cls = getattr(coord, in_frame_)
127
+ else:
128
+ err_str = [
129
+ f"In frame '{in_frame}' not recognized, ",
130
+ "please check spelling or perform manual transformation",
131
+ ]
132
+ raise ValueError("".join(err_str))
133
+
134
+ kw = {}
135
+ kw.update(frame_kwargs)
136
+ if in_frame_ not in ASTROPY_NOT_OBSTIME:
137
+ kw["obstime"] = t
138
+
139
+ astropy_states = _convert_to_astropy(states, in_frame_cls, kw)
140
+
141
+ if out_frame in ASTROPY_FRAMES:
142
+ out_frame_ = ASTROPY_FRAMES[out_frame]
143
+ out_frame_cls = getattr(coord, out_frame_)
144
+ else:
145
+ err_str = [
146
+ f"Out frame '{out_frame}' not recognized, ",
147
+ "please check spelling or perform manual transformation",
148
+ ]
149
+ raise ValueError("".join(err_str))
150
+
151
+ kw = {}
152
+ kw.update(frame_kwargs)
153
+ if out_frame_ not in ASTROPY_NOT_OBSTIME:
154
+ kw["obstime"] = t
155
+
156
+ out_states = astropy_states.transform_to(out_frame_cls(**kw))
157
+
158
+ rets = states.copy()
159
+ rets[:3, ...] = out_states.cartesian.xyz.to(units.m).value
160
+ rets[3:, ...] = out_states.velocity.d_xyz.to(units.m / units.s).value
161
+
162
+ return rets
163
+
164
+
165
+ def _convert_to_astropy(
166
+ states: NDArray_6xN | NDArray_6,
167
+ frame: Type[T],
168
+ frame_kwargs: dict[str, Any],
169
+ ) -> T:
170
+ state_p = coord.CartesianRepresentation(states[:3, ...] * units.m)
171
+ state_v = coord.CartesianDifferential(states[3:, ...] * units.m / units.s)
172
+ astropy_states = frame(state_p.with_differentials(state_v), **frame_kwargs) # type: ignore
173
+ return astropy_states
174
+
175
+
176
+ def geodetic_to_ITRS(
177
+ lat: NDArray_N | float,
178
+ lon: NDArray_N | float,
179
+ alt: NDArray_N | float,
180
+ degrees: bool = True,
181
+ ) -> NDArray_3xN | NDArray_3:
182
+ """Use `astropy.coordinates.WGS84GeodeticRepresentation` to transform from WGS84 to ITRS."""
183
+ ang_unit = units.deg if degrees else units.rad
184
+
185
+ wgs_cord = coord.WGS84GeodeticRepresentation(
186
+ lon=lon * ang_unit,
187
+ lat=lat * ang_unit,
188
+ height=alt * units.m,
189
+ )
190
+ itrs_cord = coord.ITRS(wgs_cord)
191
+
192
+ if isinstance(lat, np.ndarray):
193
+ size = lat.size
194
+ else:
195
+ size = 0
196
+
197
+ shape: tuple[int, ...] = (6, size) if size > 0 else (6,)
198
+ state = np.empty(shape, dtype=np.float64)
199
+ state[:3, ...] = itrs_cord.cartesian.xyz.to(units.m).value
200
+ state[3:, ...] = itrs_cord.velocity.d_xyz.to(units.m / units.s).value
201
+
202
+ return state
203
+
204
+
205
+ def ITRS_to_geodetic(
206
+ state: NDArray_3xN | NDArray_N,
207
+ degrees: bool = True,
208
+ ):
209
+ """Use `astropy.coordinates.WGS84GeodeticRepresentation` to transform from ITRS to WGS84."""
210
+ raise NotImplementedError()
211
+ # ang_unit = units.deg if degrees else units.rad
212
+ # astropy_states = _convert_to_astropy(state, coord.ITRS)
213
+ # wgs_cord = coord.WGS84GeodeticRepresentation(astropy_states)
214
+ #
215
+ # if isinstance(lat, np.ndarray):
216
+ # size = lat.size
217
+ # else:
218
+ # size = 0
219
+ #
220
+ # shape: tuple[int, ...] = (6, size) if size > 0 else (6,)
221
+ # state = np.empty(shape, dtype=np.float64)
222
+ # state[:3, ...] = itrs_cord.cartesian.xyz.to(units.m).value
223
+ # state[3:, ...] = itrs_cord.velocity.d_xyz.to(units.m / units.s).value
224
+ #
225
+ # return state
226
+ pass
@@ -0,0 +1,22 @@
1
+ import argparse
2
+
3
+
4
+ def main():
5
+ import spacecoords as sc
6
+
7
+ parser = argparse.ArgumentParser(description="Download files")
8
+ subparsers = parser.add_subparsers(help="Available download interfaces", dest="command")
9
+
10
+ naif_parser = subparsers.add_parser("naif_kernel", help="Download NAIF Kernels")
11
+ naif_parser.add_argument(
12
+ "kernel_type",
13
+ choices=list(sc.download.KERNEL_PATHS.keys()),
14
+ help="Type of kernel (determines location on server)",
15
+ )
16
+ naif_parser.add_argument("kernel_name", help="Kernel filename")
17
+ naif_parser.add_argument("output_file", help="Path to output file")
18
+
19
+ args = parser.parse_args()
20
+
21
+ if args.command == "naif_kernel":
22
+ sc.download.naif_kernel_main(args)
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python
2
+
3
+ """Constants useful for different coordinate systems"""
4
+
5
+
6
+ G: float = 6.67430e-11
7
+ """Newtonian constant of gravitation (m^3 kg^-1 s^-2), CODATA Recommended Values of the Fundamental
8
+ Physical Constants 2022.
9
+ """
10
+
11
+
12
+ class WGS84:
13
+ """World Geodetic System 1984 constants."""
14
+
15
+ a: float = 6378.137 * 1e3
16
+ """float: semi-major axis parameter in meters of the World Geodetic System 1984 (WGS84)
17
+ """
18
+
19
+ b: float = 6356.7523142 * 1e3
20
+ """float: semi-minor axis parameter in meters of the World Geodetic System 1984 (WGS84)
21
+ """
22
+
23
+ esq: float = 6.69437999014 * 0.001
24
+ """float: `esq` parameter in meters of the World Geodetic System 1984 (WGS84)
25
+ """
26
+
27
+ e1sq: float = 6.73949674228 * 0.001
28
+ """float: `e1sq` parameter in meters of the World Geodetic System 1984 (WGS84)
29
+ """
30
+
31
+ f: float = 1 / 298.257223563
32
+ """float: `f` parameter of the World Geodetic System 1984 (WGS84)
33
+ """
34
+
35
+
36
+ R_earth: float = 6371.0088e3
37
+ """float: Radius of the Earth using the International
38
+ Union of Geodesy and Geophysics (IUGG) definition
39
+ """
40
+
41
+
42
+ class WGS72:
43
+ """World Geodetic System 1972 constants."""
44
+
45
+ MU_earth: float = 398600.8 * 1e9
46
+ """float: Standard gravitational parameter of the Earth using the WGS72 convention.
47
+ """
48
+
49
+ M_earth: float = MU_earth / G
50
+ """float: Mass of the Earth using the WGS72 convention.
51
+ """
@@ -0,0 +1,59 @@
1
+ import sys
2
+ import os
3
+ from pathlib import Path
4
+ import requests
5
+
6
+ NAIF_URL = "https://naif.jpl.nasa.gov/pub/naif/"
7
+
8
+ KERNEL_PATHS = {
9
+ "planetary": "generic_kernels/spk/planets/",
10
+ }
11
+
12
+
13
+ def naif_kernel(
14
+ kernel_path: str,
15
+ output_file: Path,
16
+ progress: bool = True,
17
+ chunk_size: int = 8192,
18
+ ):
19
+ url = NAIF_URL + kernel_path
20
+ with requests.get(url, stream=True) as r:
21
+ r.raise_for_status()
22
+ total_length = r.headers.get("content-length")
23
+ downloaded = 0
24
+ if total_length is None:
25
+ with open(output_file, "wb") as fh:
26
+ for chunk in r.iter_content(chunk_size=chunk_size):
27
+ fh.write(chunk)
28
+ if progress:
29
+ downloaded += len(chunk)
30
+ sys.stdout.write(f"{downloaded / 1024:.1f} KB")
31
+ sys.stdout.flush()
32
+ if progress:
33
+ print()
34
+ else:
35
+ total_length = int(total_length)
36
+ prog_width = min(os.get_terminal_size()[0] - 10, 100)
37
+ with open(output_file, "wb") as fh:
38
+ for chunk in r.iter_content(chunk_size=chunk_size):
39
+ if not chunk:
40
+ continue
41
+ fh.write(chunk)
42
+ if progress:
43
+ downloaded += len(chunk)
44
+ done = int(prog_width * downloaded / total_length)
45
+ sys.stdout.write(
46
+ f"\r[{'=' * done}{' ' * (prog_width - done)}] "
47
+ f"{downloaded / 1024:.1f} KB / {total_length / 1024:.1f} KB"
48
+ )
49
+ sys.stdout.flush()
50
+ if progress:
51
+ print()
52
+
53
+
54
+ def naif_kernel_main(args):
55
+ naif_kernel(
56
+ kernel_path=KERNEL_PATHS[args.kernel_type] + args.kernel_name,
57
+ output_file=args.output_file,
58
+ progress=True,
59
+ )