cars 1.0.0rc3__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
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.
- cars/__init__.py +74 -0
- cars/applications/__init__.py +40 -0
- cars/applications/application.py +117 -0
- cars/applications/application_constants.py +29 -0
- cars/applications/application_template.py +146 -0
- cars/applications/auxiliary_filling/__init__.py +29 -0
- cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +105 -0
- cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
- cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -0
- cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
- cars/applications/dem_generation/__init__.py +30 -0
- cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
- cars/applications/dem_generation/bulldozer_config/base_config.yaml +46 -0
- cars/applications/dem_generation/bulldozer_dem_app.py +641 -0
- cars/applications/dem_generation/bulldozer_memory.py +55 -0
- cars/applications/dem_generation/dem_generation_algo.py +107 -0
- cars/applications/dem_generation/dem_generation_constants.py +32 -0
- cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
- cars/applications/dense_match_filling/__init__.py +30 -0
- cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
- cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
- cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
- cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
- cars/applications/dense_match_filling/zero_padding_app.py +302 -0
- cars/applications/dense_matching/__init__.py +30 -0
- cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
- cars/applications/dense_matching/census_mccnn_sgm_app.py +1461 -0
- cars/applications/dense_matching/cpp/__init__.py +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cpython-313-x86_64-linux-gnu.so +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
- cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
- cars/applications/dense_matching/cpp/meson.build +9 -0
- cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
- cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
- cars/applications/dense_matching/dense_matching_algo.py +401 -0
- cars/applications/dense_matching/dense_matching_constants.py +89 -0
- cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
- cars/applications/dense_matching/disparity_grid_algo.py +597 -0
- cars/applications/dense_matching/loaders/__init__.py +23 -0
- cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
- cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
- cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
- cars/applications/dense_matching/loaders/config_mapping.json +13 -0
- cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
- cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
- cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
- cars/applications/dsm_filling/__init__.py +32 -0
- cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
- cars/applications/dsm_filling/border_interpolation_app.py +278 -0
- cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
- cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
- cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
- cars/applications/dsm_merging/__init__.py +28 -0
- cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
- cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
- cars/applications/grid_correction/__init__.py +30 -0
- cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
- cars/applications/grid_correction/grid_correction_app.py +557 -0
- cars/applications/grid_generation/__init__.py +30 -0
- cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
- cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
- cars/applications/grid_generation/grid_generation_algo.py +388 -0
- cars/applications/grid_generation/grid_generation_constants.py +46 -0
- cars/applications/grid_generation/transform_grid.py +88 -0
- cars/applications/ground_truth_reprojection/__init__.py +30 -0
- cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
- cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
- cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
- cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
- cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
- cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
- cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
- cars/applications/rasterization/__init__.py +30 -0
- cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
- cars/applications/rasterization/rasterization_algo.py +534 -0
- cars/applications/rasterization/rasterization_constants.py +38 -0
- cars/applications/rasterization/rasterization_wrappers.py +639 -0
- cars/applications/rasterization/simple_gaussian_app.py +1152 -0
- cars/applications/resampling/__init__.py +28 -0
- cars/applications/resampling/abstract_resampling_app.py +187 -0
- cars/applications/resampling/bicubic_resampling_app.py +760 -0
- cars/applications/resampling/resampling_algo.py +590 -0
- cars/applications/resampling/resampling_constants.py +36 -0
- cars/applications/resampling/resampling_wrappers.py +309 -0
- cars/applications/sensors_subsampling/__init__.py +32 -0
- cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
- cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
- cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
- cars/applications/sparse_matching/__init__.py +30 -0
- cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
- cars/applications/sparse_matching/sift_app.py +724 -0
- cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
- cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
- cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
- cars/applications/triangulation/__init__.py +32 -0
- cars/applications/triangulation/abstract_triangulation_app.py +227 -0
- cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
- cars/applications/triangulation/pc_transform.py +552 -0
- cars/applications/triangulation/triangulation_algo.py +371 -0
- cars/applications/triangulation/triangulation_constants.py +38 -0
- cars/applications/triangulation/triangulation_wrappers.py +259 -0
- cars/bundleadjustment.py +750 -0
- cars/cars.py +179 -0
- cars/conf/__init__.py +23 -0
- cars/conf/geoid/egm96.grd +0 -0
- cars/conf/geoid/egm96.grd.hdr +15 -0
- cars/conf/input_parameters.py +156 -0
- cars/conf/mask_cst.py +35 -0
- cars/core/__init__.py +23 -0
- cars/core/cars_logging.py +402 -0
- cars/core/constants.py +191 -0
- cars/core/constants_disparity.py +50 -0
- cars/core/datasets.py +140 -0
- cars/core/geometry/__init__.py +27 -0
- cars/core/geometry/abstract_geometry.py +1130 -0
- cars/core/geometry/shareloc_geometry.py +604 -0
- cars/core/inputs.py +568 -0
- cars/core/outputs.py +176 -0
- cars/core/preprocessing.py +722 -0
- cars/core/projection.py +843 -0
- cars/core/roi_tools.py +215 -0
- cars/core/tiling.py +774 -0
- cars/core/utils.py +164 -0
- cars/data_structures/__init__.py +23 -0
- cars/data_structures/cars_dataset.py +1544 -0
- cars/data_structures/cars_dict.py +74 -0
- cars/data_structures/corresponding_tiles_tools.py +186 -0
- cars/data_structures/dataframe_converter.py +185 -0
- cars/data_structures/format_transformation.py +297 -0
- cars/devibrate.py +689 -0
- cars/extractroi.py +264 -0
- cars/orchestrator/__init__.py +23 -0
- cars/orchestrator/achievement_tracker.py +125 -0
- cars/orchestrator/cluster/__init__.py +37 -0
- cars/orchestrator/cluster/abstract_cluster.py +250 -0
- cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
- cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
- cars/orchestrator/cluster/dask_config/README.md +94 -0
- cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
- cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
- cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
- cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
- cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
- cars/orchestrator/cluster/local_dask_cluster.py +116 -0
- cars/orchestrator/cluster/log_wrapper.py +728 -0
- cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
- cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
- cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
- cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
- cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
- cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
- cars/orchestrator/cluster/sequential_cluster.py +139 -0
- cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
- cars/orchestrator/memory_tools.py +47 -0
- cars/orchestrator/orchestrator.py +755 -0
- cars/orchestrator/orchestrator_constants.py +29 -0
- cars/orchestrator/registry/__init__.py +23 -0
- cars/orchestrator/registry/abstract_registry.py +143 -0
- cars/orchestrator/registry/compute_registry.py +106 -0
- cars/orchestrator/registry/id_generator.py +116 -0
- cars/orchestrator/registry/replacer_registry.py +213 -0
- cars/orchestrator/registry/saver_registry.py +363 -0
- cars/orchestrator/registry/unseen_registry.py +118 -0
- cars/orchestrator/tiles_profiler.py +279 -0
- cars/pipelines/__init__.py +26 -0
- cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
- cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
- cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
- cars/pipelines/default/__init__.py +26 -0
- cars/pipelines/default/default_pipeline.py +1095 -0
- cars/pipelines/filling/__init__.py +26 -0
- cars/pipelines/filling/filling.py +981 -0
- cars/pipelines/formatting/__init__.py +26 -0
- cars/pipelines/formatting/formatting.py +190 -0
- cars/pipelines/merging/__init__.py +26 -0
- cars/pipelines/merging/merging.py +439 -0
- cars/pipelines/parameters/__init__.py +0 -0
- cars/pipelines/parameters/advanced_parameters.py +256 -0
- cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
- cars/pipelines/parameters/application_parameters.py +72 -0
- cars/pipelines/parameters/depth_map_inputs.py +0 -0
- cars/pipelines/parameters/dsm_inputs.py +349 -0
- cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
- cars/pipelines/parameters/output_constants.py +52 -0
- cars/pipelines/parameters/output_parameters.py +435 -0
- cars/pipelines/parameters/sensor_inputs.py +859 -0
- cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
- cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
- cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
- cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
- cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
- cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
- cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
- cars/pipelines/pipeline.py +119 -0
- cars/pipelines/pipeline_constants.py +38 -0
- cars/pipelines/pipeline_template.py +135 -0
- cars/pipelines/subsampling/__init__.py +26 -0
- cars/pipelines/subsampling/subsampling.py +358 -0
- cars/pipelines/surface_modeling/__init__.py +26 -0
- cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
- cars/pipelines/tie_points/__init__.py +26 -0
- cars/pipelines/tie_points/tie_points.py +536 -0
- cars/starter.py +167 -0
- cars-1.0.0rc3.dist-info/METADATA +289 -0
- cars-1.0.0rc3.dist-info/RECORD +220 -0
- cars-1.0.0rc3.dist-info/WHEEL +6 -0
- cars-1.0.0rc3.dist-info/entry_points.txt +8 -0
cars/extractroi.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
cars-extractroi: helper to extract region of interest from image product
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
import rasterio as rio
|
|
30
|
+
from affine import Affine
|
|
31
|
+
from shapely.geometry import box
|
|
32
|
+
from shareloc.geomodels.rpc_writers import write_rio_rpc_as_rpb
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_bbx_in_image(bbx, image_dataset):
|
|
36
|
+
"""
|
|
37
|
+
Checks if the bounding box is within the image.
|
|
38
|
+
|
|
39
|
+
Parameters:
|
|
40
|
+
bbx (array): The bounding box of the image.
|
|
41
|
+
image_dataset (rio.DatasetReader): Opened image dataset.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
image_box = box(0, 0, image_dataset.height, image_dataset.width)
|
|
45
|
+
|
|
46
|
+
return image_box.contains(bbx)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_slices_from_bbx(image_dataset, bbx, rpc_options):
|
|
50
|
+
"""get slices from bounding box
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
image_dataset (rio.DatasetReader): Opened image dataset.
|
|
54
|
+
bbx (array): The bounding box of the image.
|
|
55
|
+
rpc_options (dict): Options for GDALCreateRPCTransformer.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
tuple: The slices from the bounding box.
|
|
59
|
+
"""
|
|
60
|
+
transformer = rio.transform.RPCTransformer(
|
|
61
|
+
image_dataset.rpcs, **rpc_options
|
|
62
|
+
)
|
|
63
|
+
coordinates = [
|
|
64
|
+
transformer.rowcol(bbx[0], bbx[1]),
|
|
65
|
+
transformer.rowcol(bbx[2], bbx[1]),
|
|
66
|
+
transformer.rowcol(bbx[2], bbx[3]),
|
|
67
|
+
transformer.rowcol(bbx[0], bbx[3]),
|
|
68
|
+
]
|
|
69
|
+
coordinates = np.array(coordinates)
|
|
70
|
+
(row_start, col_start) = np.amin(coordinates, axis=0)
|
|
71
|
+
(row_stop, col_stop) = np.amax(coordinates, axis=0)
|
|
72
|
+
rows = (row_start, row_stop)
|
|
73
|
+
cols = (col_start, col_stop)
|
|
74
|
+
|
|
75
|
+
return rows, cols
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def process_image_file(
|
|
79
|
+
bbx, input_image_path, output_image_path, rpb_file_path, rpc_options
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Processes an image file by extracting a region based on the given geometry.
|
|
83
|
+
|
|
84
|
+
Parameters:
|
|
85
|
+
region_geometry (dict): GeoJSON-like dictionary defining the region.
|
|
86
|
+
input_image_path (str): Path to the input image file.
|
|
87
|
+
output_image_path (str): Path to save the output image.
|
|
88
|
+
rpb_file_path (str): Path to save the .RPB file.
|
|
89
|
+
rpc_options (dict): Options for GDALCreateRPCTransformer.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
with rio.open(input_image_path) as image_dataset:
|
|
93
|
+
if not image_dataset.rpcs:
|
|
94
|
+
raise ValueError("Image dataset has no RPCs")
|
|
95
|
+
validate_bounding_box(bbx, image_dataset, rpc_options)
|
|
96
|
+
row, col = get_slices_from_bbx(image_dataset, bbx, rpc_options)
|
|
97
|
+
window = rio.windows.Window.from_slices(row, col)
|
|
98
|
+
array = image_dataset.read(window=window)
|
|
99
|
+
profile = image_dataset.profile
|
|
100
|
+
profile["driver"] = "GTiff"
|
|
101
|
+
profile["width"] = window.width
|
|
102
|
+
profile["height"] = window.height
|
|
103
|
+
profile["transform"] = Affine.translation(
|
|
104
|
+
window.col_off, window.row_off
|
|
105
|
+
)
|
|
106
|
+
if "crs" in profile:
|
|
107
|
+
del profile["crs"]
|
|
108
|
+
with rio.open(output_image_path, "w", **profile) as dst:
|
|
109
|
+
# write data
|
|
110
|
+
dst.write(array)
|
|
111
|
+
# copy rpc
|
|
112
|
+
dst.rpcs = image_dataset.rpcs
|
|
113
|
+
|
|
114
|
+
if rpb_file_path is not None:
|
|
115
|
+
create_rpb_file(image_dataset, rpb_file_path)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_human_readable_bbox(image_dataset, rpc_options):
|
|
119
|
+
"""
|
|
120
|
+
Get the human-readable bounding box from an image dataset.
|
|
121
|
+
|
|
122
|
+
Parameters:
|
|
123
|
+
image_dataset (rio.DatasetReader): Opened image dataset.
|
|
124
|
+
rpc_options (dict): Options for GDALCreateRPCTransformer.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
tuple: The human-readable bounding box in the format
|
|
128
|
+
(min_x, max_x, min_y, max_y).
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
transformer = rio.transform.RPCTransformer(
|
|
132
|
+
image_dataset.rpcs, **rpc_options
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
human_readable_bbx = [
|
|
136
|
+
transformer.xy(0, 0),
|
|
137
|
+
transformer.xy(image_dataset.height, image_dataset.width),
|
|
138
|
+
]
|
|
139
|
+
# fix coordinates to precision -7 for (x, y)
|
|
140
|
+
image_coords = [
|
|
141
|
+
(round(coord[0], 7), round(coord[1], 7)) for coord in human_readable_bbx
|
|
142
|
+
]
|
|
143
|
+
[(x_1, y_1), (x_2, y_2)] = image_coords
|
|
144
|
+
|
|
145
|
+
min_x, max_x = min(x_1, x_2), max(x_1, x_2)
|
|
146
|
+
min_y, max_y = min(y_1, y_2), max(y_1, y_2)
|
|
147
|
+
|
|
148
|
+
return min_x, max_x, min_y, max_y
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def validate_bounding_box(bbx, image_dataset, rpc_options):
|
|
152
|
+
"""
|
|
153
|
+
Validate the bounding box coordinates.
|
|
154
|
+
|
|
155
|
+
Parameters:
|
|
156
|
+
bbx (array): The bounding box of the image.
|
|
157
|
+
image_dataset (rio.DatasetReader): Opened image dataset.
|
|
158
|
+
rpc_options (dict): Options for GDALCreateRPCTransformer.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
transformer = rio.transform.RPCTransformer(
|
|
162
|
+
image_dataset.rpcs, **rpc_options
|
|
163
|
+
)
|
|
164
|
+
input_box = box(
|
|
165
|
+
*transformer.rowcol(bbx[0], bbx[1]), *transformer.rowcol(bbx[2], bbx[3])
|
|
166
|
+
)
|
|
167
|
+
if not is_bbx_in_image(input_box, image_dataset):
|
|
168
|
+
min_x, max_x, min_y, max_y = get_human_readable_bbox(
|
|
169
|
+
image_dataset, rpc_options
|
|
170
|
+
)
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"Coordinates must be between "
|
|
173
|
+
f"({min_x}, {min_y}) and ({max_x}, {max_y})"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def create_rpb_file(image_dataset, rpb_filename):
|
|
178
|
+
"""
|
|
179
|
+
Create and save a .RPB file from a rasterio dataset
|
|
180
|
+
|
|
181
|
+
Parameters:
|
|
182
|
+
image_dataset (rio.DatasetReader): Opened image dataset.
|
|
183
|
+
rpb_filename (str): Path to save the .RPB file.
|
|
184
|
+
"""
|
|
185
|
+
if not image_dataset.rpcs:
|
|
186
|
+
raise ValueError("Image dataset has no RPCs")
|
|
187
|
+
rpcs_as_dict = image_dataset.rpcs.to_dict()
|
|
188
|
+
write_rio_rpc_as_rpb(rpcs_as_dict, rpb_filename)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main():
|
|
192
|
+
"""
|
|
193
|
+
Main cars-extractroi entrypoint
|
|
194
|
+
"""
|
|
195
|
+
parser = argparse.ArgumentParser(
|
|
196
|
+
"cars-extractroi", description="Helper to extract roi from bounding box"
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"-il",
|
|
200
|
+
type=str,
|
|
201
|
+
nargs="*",
|
|
202
|
+
help="Image products",
|
|
203
|
+
required=True,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
parser.add_argument(
|
|
207
|
+
"-out",
|
|
208
|
+
type=str,
|
|
209
|
+
help="Extracts directory",
|
|
210
|
+
required=True,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
parser.add_argument(
|
|
214
|
+
"-bbx",
|
|
215
|
+
type=float,
|
|
216
|
+
nargs=4,
|
|
217
|
+
help="Bounding box from two points (x1, y1) and (x2, y2)",
|
|
218
|
+
metavar=("x1", "y1", "x2", "y2"),
|
|
219
|
+
required=True,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
parser.add_argument(
|
|
223
|
+
"--rpc_height",
|
|
224
|
+
type=float,
|
|
225
|
+
help="Constant height offset used for projection",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
"--rpc_dem",
|
|
230
|
+
type=str,
|
|
231
|
+
help="Digital Elevation Model used for projection",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--generate_rpb",
|
|
236
|
+
action="store_true",
|
|
237
|
+
help="Generate RPB file",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
args = parser.parse_args()
|
|
241
|
+
if not os.path.exists(args.out):
|
|
242
|
+
os.makedirs(args.out)
|
|
243
|
+
|
|
244
|
+
rpc_options = {}
|
|
245
|
+
|
|
246
|
+
if args.rpc_height is not None:
|
|
247
|
+
rpc_options["rpc_height"] = args.rpc_height
|
|
248
|
+
if args.rpc_dem is not None:
|
|
249
|
+
rpc_options["rpc_dem"] = args.rpc_dem
|
|
250
|
+
|
|
251
|
+
# check first input in list to determine pipeline
|
|
252
|
+
for idx, image_path in enumerate(args.il):
|
|
253
|
+
output_image_path = os.path.join(args.out, "ext_%03d.tif" % idx)
|
|
254
|
+
rpb_file_path = None
|
|
255
|
+
if args.generate_rpb:
|
|
256
|
+
rpb_file_path = os.path.splitext(output_image_path)[0] + ".RPB"
|
|
257
|
+
|
|
258
|
+
process_image_file(
|
|
259
|
+
args.bbx, image_path, output_image_path, rpb_file_path, rpc_options
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
Cars orchestrator module init file
|
|
23
|
+
"""
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
this module contains the achievement tracker
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
from cars.orchestrator.registry.abstract_registry import (
|
|
30
|
+
AbstractCarsDatasetRegistry,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AchievementTracker:
|
|
35
|
+
"""
|
|
36
|
+
AchievementTracker
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
"""
|
|
41
|
+
Init function of AchievementTracker
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
self.tracked_cars_ds = []
|
|
45
|
+
self.cars_ds_ids = []
|
|
46
|
+
self.achievement = []
|
|
47
|
+
|
|
48
|
+
def track(self, cars_ds, cars_ds_id):
|
|
49
|
+
"""
|
|
50
|
+
Track cars dataset
|
|
51
|
+
|
|
52
|
+
:param cars_ds: cars dataset to track
|
|
53
|
+
:type cars_ds: CarsDataset
|
|
54
|
+
:param cars_ds_id: id of cars dataset
|
|
55
|
+
:type cars_ds_id: int
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if cars_ds not in self.tracked_cars_ds:
|
|
59
|
+
self.tracked_cars_ds.append(cars_ds)
|
|
60
|
+
self.cars_ds_ids.append(cars_ds_id)
|
|
61
|
+
self.achievement.append(np.zeros(cars_ds.shape, dtype=bool))
|
|
62
|
+
|
|
63
|
+
def add_tile(self, tile):
|
|
64
|
+
"""
|
|
65
|
+
Add finished tile
|
|
66
|
+
|
|
67
|
+
:param tile: finished tile
|
|
68
|
+
:type tile: xarray Dataset or Pandas Dataframe
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
self._add_tile(tile)
|
|
73
|
+
except RuntimeError:
|
|
74
|
+
logging.error("Error getting id in Achiement Tracker")
|
|
75
|
+
|
|
76
|
+
def _add_tile(self, tile):
|
|
77
|
+
"""
|
|
78
|
+
Add finished tile
|
|
79
|
+
|
|
80
|
+
:param tile: finished tile
|
|
81
|
+
:type tile: xarray Dataset or Pandas Dataframe
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# Get cars dataset id
|
|
85
|
+
cars_ds_id = AbstractCarsDatasetRegistry.get_future_cars_dataset_id(
|
|
86
|
+
tile
|
|
87
|
+
)
|
|
88
|
+
if cars_ds_id is None:
|
|
89
|
+
raise RuntimeError("No id in data")
|
|
90
|
+
if cars_ds_id not in self.cars_ds_ids:
|
|
91
|
+
raise RuntimeError("Cars ds not registered")
|
|
92
|
+
index = self.cars_ds_ids.index(cars_ds_id)
|
|
93
|
+
|
|
94
|
+
# Get position
|
|
95
|
+
row, col = AbstractCarsDatasetRegistry.get_future_cars_dataset_position(
|
|
96
|
+
tile
|
|
97
|
+
)
|
|
98
|
+
if None in (row, col):
|
|
99
|
+
logging.error("None in row, col in achievement tracker")
|
|
100
|
+
else:
|
|
101
|
+
# update
|
|
102
|
+
self.achievement[index][row, col] = 1
|
|
103
|
+
|
|
104
|
+
def get_remaining_tiles(self):
|
|
105
|
+
"""
|
|
106
|
+
Get remaining tiles to compute
|
|
107
|
+
|
|
108
|
+
:return: remaining tiles
|
|
109
|
+
:rtype: list(delayed)
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
tiles = []
|
|
113
|
+
|
|
114
|
+
for cars_ds, achievement in zip( # noqa: B905
|
|
115
|
+
self.tracked_cars_ds, self.achievement
|
|
116
|
+
):
|
|
117
|
+
for row in range(cars_ds.shape[0]):
|
|
118
|
+
for col in range(cars_ds.shape[1]):
|
|
119
|
+
if (
|
|
120
|
+
not achievement[row, col]
|
|
121
|
+
and cars_ds[row, col] is not None
|
|
122
|
+
):
|
|
123
|
+
tiles.append(cars_ds[row, col])
|
|
124
|
+
|
|
125
|
+
return tiles
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
CARS cluster module init file
|
|
23
|
+
"""
|
|
24
|
+
# flake8: noqa: F401
|
|
25
|
+
|
|
26
|
+
# CARS imports
|
|
27
|
+
from cars.orchestrator.cluster.abstract_cluster import AbstractCluster
|
|
28
|
+
|
|
29
|
+
from . import (
|
|
30
|
+
abstract_dask_cluster,
|
|
31
|
+
dask_cluster_tools,
|
|
32
|
+
local_dask_cluster,
|
|
33
|
+
mp_cluster,
|
|
34
|
+
pbs_dask_cluster,
|
|
35
|
+
sequential_cluster,
|
|
36
|
+
slurm_dask_cluster,
|
|
37
|
+
)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
Contains abstract function for Abstract Cluster
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Standard imports
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
from abc import ABCMeta, abstractmethod
|
|
29
|
+
from typing import Dict
|
|
30
|
+
|
|
31
|
+
from cars.orchestrator.cluster.log_wrapper import cars_profile
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AbstractCluster(metaclass=ABCMeta):
|
|
35
|
+
"""
|
|
36
|
+
AbstractCluster
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Available cluster modes to instanciate AbstractCluster subclasses.
|
|
40
|
+
available_modes: Dict = {}
|
|
41
|
+
|
|
42
|
+
# Define abstract attributes
|
|
43
|
+
|
|
44
|
+
# cluster mode output directory
|
|
45
|
+
out_dir: str
|
|
46
|
+
|
|
47
|
+
def __new__( # pylint: disable=too-many-positional-arguments
|
|
48
|
+
cls,
|
|
49
|
+
conf_cluster,
|
|
50
|
+
out_dir,
|
|
51
|
+
log_dir,
|
|
52
|
+
launch_worker=True,
|
|
53
|
+
data_to_propagate=None,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Return the required cluster
|
|
57
|
+
:raises:
|
|
58
|
+
- KeyError when the required cluster is not registered
|
|
59
|
+
|
|
60
|
+
:param conf_cluster: configuration for cluster
|
|
61
|
+
:param out_dir: output directory for results
|
|
62
|
+
:param launch_worker: launcher of the new worker
|
|
63
|
+
:return: a cltser object
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
cluster_mode = "multiprocessing"
|
|
67
|
+
if conf_cluster is not None:
|
|
68
|
+
if "mode" not in conf_cluster:
|
|
69
|
+
logging.warning("Cluster mode not defined, default is used")
|
|
70
|
+
else:
|
|
71
|
+
cluster_mode = conf_cluster["mode"]
|
|
72
|
+
|
|
73
|
+
if cluster_mode not in cls.available_modes:
|
|
74
|
+
logging.error("No mode named {} registered".format(cluster_mode))
|
|
75
|
+
raise KeyError("No mode named {} registered".format(cluster_mode))
|
|
76
|
+
|
|
77
|
+
logging.info("The AbstractCluster {} will be used".format(cluster_mode))
|
|
78
|
+
|
|
79
|
+
return super(AbstractCluster, cls).__new__(
|
|
80
|
+
cls.available_modes[cluster_mode]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def register_subclass(cls, *short_names: str):
|
|
85
|
+
"""
|
|
86
|
+
Allows to register the subclass with its short name
|
|
87
|
+
:param short_names: the subclasses to be registered
|
|
88
|
+
:type short_names: string
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def decorator(subclass):
|
|
92
|
+
"""
|
|
93
|
+
Registers the subclass in the available methods
|
|
94
|
+
:param subclass: the subclass to be registered
|
|
95
|
+
:type subclass: object
|
|
96
|
+
"""
|
|
97
|
+
for short_name in short_names:
|
|
98
|
+
cls.available_modes[short_name] = subclass
|
|
99
|
+
return subclass
|
|
100
|
+
|
|
101
|
+
return decorator
|
|
102
|
+
|
|
103
|
+
def __init__( # pylint: disable=too-many-positional-arguments
|
|
104
|
+
self,
|
|
105
|
+
conf_cluster,
|
|
106
|
+
out_dir,
|
|
107
|
+
log_dir,
|
|
108
|
+
launch_worker=True,
|
|
109
|
+
data_to_propagate=None,
|
|
110
|
+
): # pylint: disable=W0613
|
|
111
|
+
"""
|
|
112
|
+
Init function of AbstractCluster
|
|
113
|
+
|
|
114
|
+
:param conf_cluster: configuration for cluster
|
|
115
|
+
:param data_to_propagate: data to propagate to new cluster if reset
|
|
116
|
+
:type data_to_propagate: dict
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
self.out_dir = out_dir
|
|
120
|
+
|
|
121
|
+
# data to propagate
|
|
122
|
+
self.data_to_propagate = data_to_propagate
|
|
123
|
+
|
|
124
|
+
self.worker_log_dir = os.path.join(log_dir, "workers_log")
|
|
125
|
+
if not os.path.exists(self.worker_log_dir):
|
|
126
|
+
os.makedirs(self.worker_log_dir)
|
|
127
|
+
|
|
128
|
+
self.log_level = logging.getLogger().getEffectiveLevel()
|
|
129
|
+
handlers = logging.getLogger().handlers
|
|
130
|
+
for hand in handlers:
|
|
131
|
+
if "stdout" == hand.get_name():
|
|
132
|
+
self.log_level = hand.level
|
|
133
|
+
|
|
134
|
+
# Check conf
|
|
135
|
+
self.checked_conf_cluster = self.check_conf(conf_cluster)
|
|
136
|
+
|
|
137
|
+
@abstractmethod
|
|
138
|
+
def get_delayed_type(self):
|
|
139
|
+
"""
|
|
140
|
+
Get delayed type
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def cleanup(self, **kwargs):
|
|
145
|
+
"""
|
|
146
|
+
Cleanup cluster
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def check_conf(self, conf):
|
|
151
|
+
"""
|
|
152
|
+
Check configuration
|
|
153
|
+
|
|
154
|
+
:param conf: configuration to check
|
|
155
|
+
:type conf: dict
|
|
156
|
+
|
|
157
|
+
:return: overloaded configuration
|
|
158
|
+
:rtype: dict
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def get_conf(self):
|
|
163
|
+
"""
|
|
164
|
+
Get overriden configuration
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
:return: overriden configuration
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
return self.checked_conf_cluster
|
|
171
|
+
|
|
172
|
+
def create_task(self, func, nout=1):
|
|
173
|
+
"""
|
|
174
|
+
Create task
|
|
175
|
+
|
|
176
|
+
:param func: function
|
|
177
|
+
:param nout: number of outputs
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def create_task_builder(*argv, **kwargs):
|
|
181
|
+
"""
|
|
182
|
+
Create task builder
|
|
183
|
+
|
|
184
|
+
:param argv: list of input arguments
|
|
185
|
+
:param kwargs: list of named input arguments
|
|
186
|
+
"""
|
|
187
|
+
wrapped_cars_profile = cars_profile_wrapper
|
|
188
|
+
additionnal_kwargs = {
|
|
189
|
+
"func": func,
|
|
190
|
+
"func_name": func.__name__,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return self.create_task_wrapped(wrapped_cars_profile, nout=nout)(
|
|
194
|
+
*argv, **kwargs, **additionnal_kwargs
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return create_task_builder
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
def create_task_wrapped(self, func, nout=1):
|
|
201
|
+
"""
|
|
202
|
+
Create task
|
|
203
|
+
|
|
204
|
+
:param func: function
|
|
205
|
+
:param nout: number of outputs
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
@abstractmethod
|
|
209
|
+
def start_tasks(self, task_list):
|
|
210
|
+
"""
|
|
211
|
+
Start all tasks
|
|
212
|
+
|
|
213
|
+
:param task_list: task list
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def scatter(self, data, broadcast=True):
|
|
218
|
+
"""
|
|
219
|
+
Distribute data through workers
|
|
220
|
+
|
|
221
|
+
:param data: task data
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
@abstractmethod
|
|
225
|
+
def future_iterator(self, future_list, timeout=None):
|
|
226
|
+
"""
|
|
227
|
+
Iterator, iterating on computed futures
|
|
228
|
+
|
|
229
|
+
:param future_list: future_list list
|
|
230
|
+
:param timeout: time to wait for next job
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def cars_profile_wrapper(*argv, **kwargs):
|
|
235
|
+
"""
|
|
236
|
+
Create a wrapper for cars_profile to be used in cluster tasks
|
|
237
|
+
|
|
238
|
+
:param argv: args of func
|
|
239
|
+
:param kwargs: kwargs of func
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
func = kwargs["func"]
|
|
244
|
+
func_name = kwargs["func_name"]
|
|
245
|
+
kwargs.pop("func")
|
|
246
|
+
kwargs.pop("func_name")
|
|
247
|
+
|
|
248
|
+
res = cars_profile(name=func_name)(func)(*argv, **kwargs)
|
|
249
|
+
|
|
250
|
+
return res
|