pegasource 0.2.1__tar.gz → 0.2.2__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.
Files changed (84) hide show
  1. {pegasource-0.2.1 → pegasource-0.2.2}/PKG-INFO +5 -11
  2. {pegasource-0.2.1 → pegasource-0.2.2}/README.md +4 -10
  3. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/__init__.py +1 -1
  4. pegasource-0.2.2/pegasource/path_estimation/__init__.py +45 -0
  5. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/evaluate.py +0 -1
  6. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/generate_synthetic_datasets.py +0 -2
  7. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/run_method_evaluation.py +0 -2
  8. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/PKG-INFO +5 -11
  9. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/SOURCES.txt +0 -1
  10. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/entry_points.txt +0 -1
  11. {pegasource-0.2.1 → pegasource-0.2.2}/pyproject.toml +1 -2
  12. pegasource-0.2.1/pegasource/path_estimation/__init__.py +0 -36
  13. pegasource-0.2.1/pegasource/path_estimation/__main__.py +0 -65
  14. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/data/README.md +0 -0
  15. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/data/israel_roads.pkl.gz +0 -0
  16. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/__init__.py +0 -0
  17. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/_paths.py +0 -0
  18. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/cluster_hardware.py +0 -0
  19. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/cluster_viz/app.js +0 -0
  20. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/cluster_viz/data.json +0 -0
  21. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/cluster_viz/index.html +0 -0
  22. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/cluster_viz/styles.css +0 -0
  23. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/custom_devices.py +0 -0
  24. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/clustered_output.csv +0 -0
  25. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/dirty_hardware_data_20k.csv +0 -0
  26. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/dirty_hardware_data_40k.csv +0 -0
  27. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/dirty_hardware_data_hebrew_40k.csv +0 -0
  28. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/hardware_data_40_percent_dirty.csv +0 -0
  29. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/hardware_data_hebrew_40_percent_dirty.csv +0 -0
  30. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/data/test_output.csv +0 -0
  31. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/dataset_generator.py +0 -0
  32. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/fetch_ifixit_devices.py +0 -0
  33. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/ifixit_devices.json +0 -0
  34. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/prepare_viz_data.py +0 -0
  35. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/dataset_clustering/server.py +0 -0
  36. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/__init__.py +0 -0
  37. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/_rv_graph_builder.py +0 -0
  38. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/_rv_preprocessing.py +0 -0
  39. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/_rv_visualization.py +0 -0
  40. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/distance.py +0 -0
  41. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/israel_roads.py +0 -0
  42. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/projection.py +0 -0
  43. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/geo/vectorizer.py +0 -0
  44. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/cache/london_walk_bbox.graphml +0 -0
  45. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/filters/__init__.py +0 -0
  46. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/filters/ekf.py +0 -0
  47. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/filters/kf.py +0 -0
  48. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/filters/particle.py +0 -0
  49. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/filters/ukf.py +0 -0
  50. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/geo_reference.py +0 -0
  51. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/gnn/__init__.py +0 -0
  52. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/gnn/estimate.py +0 -0
  53. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/graph_stitch.py +0 -0
  54. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/graph_utils.py +0 -0
  55. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/hmm_map_match.py +0 -0
  56. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/io.py +0 -0
  57. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/london_street_path.py +0 -0
  58. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/metrics.py +0 -0
  59. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/nn/__init__.py +0 -0
  60. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/nn/dataset.py +0 -0
  61. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/nn/lstm_model.py +0 -0
  62. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/nn/transformer_model.py +0 -0
  63. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/plotting_utils.py +0 -0
  64. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/sample_observations.csv +0 -0
  65. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/sample_true_path.csv +0 -0
  66. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/types.py +0 -0
  67. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/path_estimation/viz.py +0 -0
  68. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/pcap/__init__.py +0 -0
  69. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/pcap/patterns.py +0 -0
  70. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/pcap/reader.py +0 -0
  71. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/pcap/report.py +0 -0
  72. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/pcap/stats.py +0 -0
  73. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/timeseries/__init__.py +0 -0
  74. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/timeseries/auto.py +0 -0
  75. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/timeseries/models.py +0 -0
  76. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource/timeseries/utils.py +0 -0
  77. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/dependency_links.txt +0 -0
  78. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/requires.txt +0 -0
  79. {pegasource-0.2.1 → pegasource-0.2.2}/pegasource.egg-info/top_level.txt +0 -0
  80. {pegasource-0.2.1 → pegasource-0.2.2}/setup.cfg +0 -0
  81. {pegasource-0.2.1 → pegasource-0.2.2}/tests/test_geo.py +0 -0
  82. {pegasource-0.2.1 → pegasource-0.2.2}/tests/test_path_estimation.py +0 -0
  83. {pegasource-0.2.1 → pegasource-0.2.2}/tests/test_pcap.py +0 -0
  84. {pegasource-0.2.1 → pegasource-0.2.2}/tests/test_timeseries.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pegasource
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Offline-capable toolkit: PCAP, geo, time-series, clustering, and path estimation
5
5
  Author: Josef Berman
6
6
  License: MIT
@@ -240,13 +240,10 @@ python -m pegasource.dataset_clustering.prepare_viz_data
240
240
 
241
241
  ### Path estimation (trajectories)
242
242
 
243
- Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for full method set):
243
+ Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for the full estimator stack):
244
244
 
245
245
  ```bash
246
246
  pip install -e ".[path_estimation]"
247
- pegasource-path-estimation --observations run_1_observations.csv --true-path run_1_true_path.csv --output-dir out/
248
- # or
249
- python -m pegasource.path_estimation --observations ... --true-path ... --output-dir out/
250
247
  ```
251
248
 
252
249
  ```python
@@ -283,12 +280,9 @@ results = estimate_paths_only(
283
280
  # results["kf"] is an EstimationResult with times_s, east_m, north_m (or {"error": "..."})
284
281
  ```
285
282
 
286
- Synthetic data generation and batch method comparison:
287
-
288
- ```bash
289
- python -m pegasource.path_estimation.generate_synthetic_datasets --help
290
- python -m pegasource.path_estimation.run_method_evaluation --help # writes ./method_eval/
291
- ```
283
+ Synthetic data (``generate_dataset`` in ``pegasource.path_estimation.generate_synthetic_datasets``)
284
+ and batch method comparison (``main`` in ``pegasource.path_estimation.run_method_evaluation``) are
285
+ invoked from your own Python code, not as installed console commands.
292
286
 
293
287
  ---
294
288
 
@@ -189,13 +189,10 @@ python -m pegasource.dataset_clustering.prepare_viz_data
189
189
 
190
190
  ### Path estimation (trajectories)
191
191
 
192
- Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for full method set):
192
+ Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for the full estimator stack):
193
193
 
194
194
  ```bash
195
195
  pip install -e ".[path_estimation]"
196
- pegasource-path-estimation --observations run_1_observations.csv --true-path run_1_true_path.csv --output-dir out/
197
- # or
198
- python -m pegasource.path_estimation --observations ... --true-path ... --output-dir out/
199
196
  ```
200
197
 
201
198
  ```python
@@ -232,12 +229,9 @@ results = estimate_paths_only(
232
229
  # results["kf"] is an EstimationResult with times_s, east_m, north_m (or {"error": "..."})
233
230
  ```
234
231
 
235
- Synthetic data generation and batch method comparison:
236
-
237
- ```bash
238
- python -m pegasource.path_estimation.generate_synthetic_datasets --help
239
- python -m pegasource.path_estimation.run_method_evaluation --help # writes ./method_eval/
240
- ```
232
+ Synthetic data (``generate_dataset`` in ``pegasource.path_estimation.generate_synthetic_datasets``)
233
+ and batch method comparison (``main`` in ``pegasource.path_estimation.run_method_evaluation``) are
234
+ invoked from your own Python code, not as installed console commands.
241
235
 
242
236
  ---
243
237
 
@@ -10,5 +10,5 @@ pegasource.dataset_clustering Hardware inventory embedding + clustering (opti
10
10
  pegasource.path_estimation GPS/cellular path reconstruction (filters, graph, NN; optional torch stack)
11
11
  """
12
12
 
13
- __version__ = "0.2.1"
13
+ __version__ = "0.2.2"
14
14
  __all__ = ["pcap", "geo", "timeseries", "dataset_clustering", "path_estimation"]
@@ -0,0 +1,45 @@
1
+ """Trajectory reconstruction from asynchronous observations (local ENU meters).
2
+
3
+ Re-exports :func:`run_evaluation`, :func:`evaluate_path_estimation`, and
4
+ :func:`estimate_paths_only` from :mod:`pegasource.path_estimation.evaluate`, same pattern
5
+ as :mod:`pegasource.pcap` / :mod:`pegasource.geo`. Requires the optional **path_estimation**
6
+ dependencies (filterpy, torch, …); install with ``pip install -e ".[path_estimation]"``.
7
+
8
+ Because Python loads this file when importing any submodule (e.g.
9
+ ``import pegasource.path_estimation.metrics``), loading the package also pulls in
10
+ ``evaluate`` and those dependencies. Previously, lazy :func:`__getattr__` deferred that
11
+ until you accessed ``run_evaluation``; explicit imports match the rest of pegasource at
12
+ the cost of a heavier first import.
13
+
14
+ Quick start::
15
+
16
+ from pegasource.path_estimation import run_evaluation
17
+ from pathlib import Path
18
+
19
+ run_evaluation(
20
+ Path("obs.csv"),
21
+ Path("true.csv"),
22
+ Path("out"),
23
+ methods=["kf", "dijkstra"],
24
+ )
25
+
26
+ Examples
27
+ --------
28
+ >>> from pegasource.path_estimation.metrics import compute_all_metrics
29
+ >>> import numpy as np
30
+ >>> t = np.zeros((10, 2))
31
+ >>> out = compute_all_metrics(t, t + 0.1)
32
+ >>> assert "rmse_m" in out
33
+ """
34
+
35
+ from .evaluate import (
36
+ estimate_paths_only,
37
+ evaluate_path_estimation,
38
+ run_evaluation,
39
+ )
40
+
41
+ __all__ = [
42
+ "estimate_paths_only",
43
+ "evaluate_path_estimation",
44
+ "run_evaluation",
45
+ ]
@@ -404,7 +404,6 @@ def run_evaluation(
404
404
  See Also
405
405
  --------
406
406
  evaluate_path_estimation : supply your own ``road_graph``.
407
- pegasource.path_estimation.__main__.main : CLI entry (``pegasource-path-estimation``).
408
407
  """
409
408
  obs_df = load_observations_csv(observations_csv)
410
409
  true_df = load_true_path_csv(true_path_csv)
@@ -1005,5 +1005,3 @@ def main() -> None:
1005
1005
  print(f"Output folder: {args.output_dir.resolve()}")
1006
1006
 
1007
1007
 
1008
- if __name__ == "__main__":
1009
- main()
@@ -252,5 +252,3 @@ def main() -> None:
252
252
  print(f"Per-run outputs: {eval_dir}/run_<i>/ (metrics.json, figures/)")
253
253
 
254
254
 
255
- if __name__ == "__main__":
256
- main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pegasource
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Offline-capable toolkit: PCAP, geo, time-series, clustering, and path estimation
5
5
  Author: Josef Berman
6
6
  License: MIT
@@ -240,13 +240,10 @@ python -m pegasource.dataset_clustering.prepare_viz_data
240
240
 
241
241
  ### Path estimation (trajectories)
242
242
 
243
- Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for full method set):
243
+ Evaluate reconstruction methods on observation CSVs vs a 1 Hz ground-truth path (requires **`[path_estimation]`** for the full estimator stack):
244
244
 
245
245
  ```bash
246
246
  pip install -e ".[path_estimation]"
247
- pegasource-path-estimation --observations run_1_observations.csv --true-path run_1_true_path.csv --output-dir out/
248
- # or
249
- python -m pegasource.path_estimation --observations ... --true-path ... --output-dir out/
250
247
  ```
251
248
 
252
249
  ```python
@@ -283,12 +280,9 @@ results = estimate_paths_only(
283
280
  # results["kf"] is an EstimationResult with times_s, east_m, north_m (or {"error": "..."})
284
281
  ```
285
282
 
286
- Synthetic data generation and batch method comparison:
287
-
288
- ```bash
289
- python -m pegasource.path_estimation.generate_synthetic_datasets --help
290
- python -m pegasource.path_estimation.run_method_evaluation --help # writes ./method_eval/
291
- ```
283
+ Synthetic data (``generate_dataset`` in ``pegasource.path_estimation.generate_synthetic_datasets``)
284
+ and batch method comparison (``main`` in ``pegasource.path_estimation.run_method_evaluation``) are
285
+ invoked from your own Python code, not as installed console commands.
292
286
 
293
287
  ---
294
288
 
@@ -38,7 +38,6 @@ pegasource/geo/israel_roads.py
38
38
  pegasource/geo/projection.py
39
39
  pegasource/geo/vectorizer.py
40
40
  pegasource/path_estimation/__init__.py
41
- pegasource/path_estimation/__main__.py
42
41
  pegasource/path_estimation/evaluate.py
43
42
  pegasource/path_estimation/generate_synthetic_datasets.py
44
43
  pegasource/path_estimation/geo_reference.py
@@ -1,4 +1,3 @@
1
1
  [console_scripts]
2
2
  pegasource-cluster-viz = pegasource.dataset_clustering.server:run
3
3
  pegasource-download-roads = pegasource.geo.israel_roads:_cli_download
4
- pegasource-path-estimation = pegasource.path_estimation.__main__:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pegasource"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Offline-capable toolkit: PCAP, geo, time-series, clustering, and path estimation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -69,7 +69,6 @@ dev = [
69
69
  [project.scripts]
70
70
  pegasource-download-roads = "pegasource.geo.israel_roads:_cli_download"
71
71
  pegasource-cluster-viz = "pegasource.dataset_clustering.server:run"
72
- pegasource-path-estimation = "pegasource.path_estimation.__main__:main"
73
72
 
74
73
  [tool.setuptools.packages.find]
75
74
  where = ["."]
@@ -1,36 +0,0 @@
1
- """Trajectory reconstruction from asynchronous observations (local ENU meters).
2
-
3
- **Lazy exports** (importing these loads :mod:`pegasource.path_estimation.evaluate` and
4
- pulls in **filterpy**, **torch**, etc. — use ``pip install -e ".[path_estimation]"``):
5
-
6
- - `estimate_paths_only` — no ground-truth CSV; stub timeline only
7
- - `evaluate_path_estimation` — CSV paths + caller-supplied road graph
8
- - `run_evaluation` — same as ``evaluate_path_estimation`` but loads the default OSM walk graph
9
-
10
- **Always-light submodules** (safe without the extra):
11
-
12
- - :mod:`pegasource.path_estimation.metrics` — RMSE, Fréchet, DTW, …
13
- - :mod:`pegasource.path_estimation.io` — CSV loaders (used by evaluation pipelines)
14
-
15
- Examples
16
- --------
17
- >>> from pegasource.path_estimation.metrics import compute_all_metrics
18
- >>> import numpy as np
19
- >>> t = np.zeros((10, 2))
20
- >>> out = compute_all_metrics(t, t + 0.1)
21
- >>> assert "rmse_m" in out
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- from typing import Any
27
-
28
- __all__ = ["estimate_paths_only", "evaluate_path_estimation", "run_evaluation"]
29
-
30
-
31
- def __getattr__(name: str) -> Any:
32
- if name in __all__:
33
- from . import evaluate
34
-
35
- return getattr(evaluate, name)
36
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,65 +0,0 @@
1
- """CLI: ``python -m pegasource.path_estimation ...``."""
2
-
3
- from __future__ import annotations
4
-
5
- import argparse
6
- from pathlib import Path
7
-
8
- from .evaluate import print_summary, run_evaluation
9
-
10
-
11
- def main() -> None:
12
- p = argparse.ArgumentParser(
13
- description="Evaluate path reconstruction methods against a true_path CSV.",
14
- )
15
- p.add_argument(
16
- "--observations",
17
- type=Path,
18
- required=True,
19
- help="Path to *_observations.csv",
20
- )
21
- p.add_argument(
22
- "--true-path",
23
- type=Path,
24
- required=True,
25
- help="Path to *_true_path.csv (1 Hz ground truth).",
26
- )
27
- p.add_argument(
28
- "--output-dir",
29
- type=Path,
30
- default=Path("path_estimation_runs/run1"),
31
- help="Directory for metrics.json and figures/.",
32
- )
33
- p.add_argument(
34
- "--methods",
35
- type=str,
36
- default="dijkstra,astar,hmm,kf,ekf,ukf,particle,lstm,transformer,gnn",
37
- help="Comma-separated method names.",
38
- )
39
- p.add_argument("--no-plots", action="store_true", help="Skip figure generation.")
40
- p.add_argument(
41
- "--map-plots",
42
- action="store_true",
43
- help="Also write Web Mercator basemap PNGs (slower; may fetch tiles).",
44
- )
45
- p.add_argument("--device", type=str, default=None, help="Torch device, e.g. cuda or cpu.")
46
- p.add_argument("--seed", type=int, default=0)
47
- args = p.parse_args()
48
-
49
- methods = [m.strip() for m in args.methods.split(",") if m.strip()]
50
- args.output_dir.mkdir(parents=True, exist_ok=True)
51
- summary = run_evaluation(
52
- args.observations,
53
- args.true_path,
54
- args.output_dir,
55
- methods=methods,
56
- plot=not args.no_plots,
57
- plot_map=bool(args.map_plots) and not args.no_plots,
58
- device=args.device,
59
- seed=args.seed,
60
- )
61
- print_summary(summary)
62
-
63
-
64
- if __name__ == "__main__":
65
- main()
File without changes
File without changes