x4d-devkit 0.46.0rc2__tar.gz → 0.47.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.
- {x4d_devkit-0.46.0rc2/x4d_devkit.egg-info → x4d_devkit-0.47.0}/PKG-INFO +1 -1
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/pyproject.toml +1 -1
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_clean_basemap.py +172 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_sdk_api.py +1 -1
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/clean_basemap.py +104 -14
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/reconstruction.py +9 -1
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0/x4d_devkit.egg-info}/PKG-INFO +1 -1
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/LICENSE +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/README.md +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/setup.cfg +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_box.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_dataset_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_label_schemas.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_loader.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_matching.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_models.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_token.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_transform.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_validation.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_work_session.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/cli.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/dataset_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/dataset_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/materialize.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/validation/validator.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit.egg-info/SOURCES.txt +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit.egg-info/requires.txt +0 -0
- {x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/x4d_devkit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "x4d-devkit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.47.0"
|
|
8
8
|
description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -3,6 +3,7 @@ import copy
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import pytest
|
|
5
5
|
import zstandard
|
|
6
|
+
import x4d_devkit.clean_basemap as clean_basemap
|
|
6
7
|
|
|
7
8
|
from tests.semantic_scene_contract_fixtures import current_semantic_scene_component
|
|
8
9
|
from x4d_devkit.clean_basemap import (
|
|
@@ -53,6 +54,7 @@ def fixture():
|
|
|
53
54
|
raw_point_ordinals=[0, 1, 2],
|
|
54
55
|
timestamps_ns=[100, 100, 100],
|
|
55
56
|
decisions=decisions[:3],
|
|
57
|
+
intensities=[2.5, 19.0, 240.0],
|
|
56
58
|
)
|
|
57
59
|
profile = {
|
|
58
60
|
"algorithm": {"id": "clean_static_basemap", "version": "0.6.0"},
|
|
@@ -101,6 +103,7 @@ def fixture():
|
|
|
101
103
|
"bounds": bounds,
|
|
102
104
|
"geometry": descriptor,
|
|
103
105
|
"mapping": mapping.descriptor("mapping"),
|
|
106
|
+
"intensity_range": [2.5, 240.0],
|
|
104
107
|
}
|
|
105
108
|
],
|
|
106
109
|
"diagnostics": {
|
|
@@ -138,6 +141,9 @@ def validate(manifest, assets):
|
|
|
138
141
|
manifest,
|
|
139
142
|
read_asset=assets.__getitem__,
|
|
140
143
|
observation_to_world=lambda sweep, ordinals, times: original[ordinals],
|
|
144
|
+
observation_intensity=lambda sweep, ordinals, times: np.asarray(
|
|
145
|
+
[2.5, 19.0, 240.0], dtype=np.float32
|
|
146
|
+
)[ordinals],
|
|
141
147
|
)
|
|
142
148
|
|
|
143
149
|
|
|
@@ -154,6 +160,7 @@ def remap(manifest, assets, **overrides):
|
|
|
154
160
|
"raw_point_ordinals": mapping["raw_point_ordinal"],
|
|
155
161
|
"timestamps_ns": mapping["timestamp_ns"],
|
|
156
162
|
"decisions": mapping["decision"],
|
|
163
|
+
"intensities": mapping["intensity"],
|
|
157
164
|
}
|
|
158
165
|
encoded = encode_clean_basemap_mapping(
|
|
159
166
|
geometry_raw_sha256=chunk["geometry"]["raw_sha256"], **(arguments | overrides)
|
|
@@ -189,6 +196,167 @@ def test_complete_scene_and_exact_original_observation_roundtrip():
|
|
|
189
196
|
parsed.data["profile"]["voxel_size_m"] = 1
|
|
190
197
|
|
|
191
198
|
|
|
199
|
+
def test_intensity_is_original_float32_and_does_not_change_geometry():
|
|
200
|
+
manifest, assets, _ = fixture()
|
|
201
|
+
before = copy.deepcopy(manifest["scene_source"])
|
|
202
|
+
assert MAPPING_DTYPE.itemsize == 24
|
|
203
|
+
remap(manifest, assets, intensities=[2.5, 20.0, 240.0])
|
|
204
|
+
assert manifest["scene_source"] == before
|
|
205
|
+
with pytest.raises(ValueError, match="intensity differs from its original"):
|
|
206
|
+
validate(manifest, assets)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.mark.parametrize(
|
|
210
|
+
"values",
|
|
211
|
+
[
|
|
212
|
+
[1, 2],
|
|
213
|
+
[True, False, True],
|
|
214
|
+
[1, float("nan"), 3],
|
|
215
|
+
[1, float("inf"), 3],
|
|
216
|
+
[1, 1e100, 3],
|
|
217
|
+
],
|
|
218
|
+
)
|
|
219
|
+
def test_invalid_intensity_cannot_be_encoded(values):
|
|
220
|
+
manifest, assets, _ = fixture()
|
|
221
|
+
with pytest.raises(ValueError, match="intensit"):
|
|
222
|
+
remap(manifest, assets, intensities=values)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_intensity_range_cannot_hide_wrong_values():
|
|
226
|
+
manifest, assets, _ = fixture()
|
|
227
|
+
manifest["chunks"][0]["intensity_range"] = [0.0, 255.0]
|
|
228
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
229
|
+
with pytest.raises(ValueError, match="intensity range"):
|
|
230
|
+
validate(manifest, assets)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_old_manifest_and_mapping_are_rejected():
|
|
234
|
+
manifest, assets, _ = fixture()
|
|
235
|
+
manifest["contract_version"] = "x4d-clean-static-basemap-v4"
|
|
236
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
237
|
+
with pytest.raises(ValueError):
|
|
238
|
+
parse_clean_basemap_manifest(manifest)
|
|
239
|
+
chunk = manifest["chunks"][0]
|
|
240
|
+
descriptor = chunk["mapping"]
|
|
241
|
+
raw = bytearray(zstandard.ZstdDecompressor().decompress(assets[descriptor["path"]]))
|
|
242
|
+
raw[:8] = b"X4DCBM2\0"
|
|
243
|
+
payload = zstandard.ZstdCompressor().compress(raw)
|
|
244
|
+
descriptor = dict(
|
|
245
|
+
descriptor,
|
|
246
|
+
path=f"generated/reconstruction/clean_basemap/mapping/{sha256_bytes(payload)[7:]}.zst",
|
|
247
|
+
sha256=sha256_bytes(payload),
|
|
248
|
+
raw_sha256=sha256_bytes(bytes(raw)),
|
|
249
|
+
size_bytes=len(payload),
|
|
250
|
+
)
|
|
251
|
+
with pytest.raises(ValueError):
|
|
252
|
+
decode_clean_basemap_mapping(
|
|
253
|
+
payload,
|
|
254
|
+
descriptor=descriptor,
|
|
255
|
+
geometry_raw_sha256=chunk["geometry"]["raw_sha256"],
|
|
256
|
+
point_count=chunk["point_count"],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_decision_proof_reuse_is_within_one_validation_call(monkeypatch):
|
|
261
|
+
manifest, assets, _ = fixture()
|
|
262
|
+
decoded = []
|
|
263
|
+
original = clean_basemap.decode_clean_basemap_decisions
|
|
264
|
+
|
|
265
|
+
def count(*args, **kwargs):
|
|
266
|
+
result = original(*args, **kwargs)
|
|
267
|
+
assert not result.flags.writeable
|
|
268
|
+
decoded.append(result.nbytes)
|
|
269
|
+
return result
|
|
270
|
+
|
|
271
|
+
monkeypatch.setattr(clean_basemap, "decode_clean_basemap_decisions", count)
|
|
272
|
+
validate(manifest, assets)
|
|
273
|
+
assert decoded == [5]
|
|
274
|
+
validate(manifest, assets)
|
|
275
|
+
assert decoded == [5, 5]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@pytest.mark.parametrize("changed", [b"tampered", bytearray, memoryview])
|
|
279
|
+
def test_cached_decision_proof_still_checks_every_carrier_read(changed):
|
|
280
|
+
manifest, assets, _ = fixture()
|
|
281
|
+
original = original_points(manifest, assets)
|
|
282
|
+
path = manifest["sweep_sources"][0]["decisions"]["path"]
|
|
283
|
+
reads = 0
|
|
284
|
+
|
|
285
|
+
def read_asset(name):
|
|
286
|
+
nonlocal reads
|
|
287
|
+
body = assets[name]
|
|
288
|
+
if name == path:
|
|
289
|
+
reads += 1
|
|
290
|
+
if reads > 1:
|
|
291
|
+
return changed(body) if callable(changed) else changed
|
|
292
|
+
return body
|
|
293
|
+
|
|
294
|
+
with pytest.raises(ValueError, match="payload size/hash"):
|
|
295
|
+
validate_clean_basemap_resource(
|
|
296
|
+
manifest,
|
|
297
|
+
read_asset=read_asset,
|
|
298
|
+
observation_to_world=lambda sweep, ordinals, times: original[ordinals],
|
|
299
|
+
observation_intensity=lambda sweep, ordinals, times: np.asarray(
|
|
300
|
+
[2.5, 19.0, 240.0], dtype=np.float32
|
|
301
|
+
)[ordinals],
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def add_second_sweep(manifest, assets):
|
|
306
|
+
source = copy.deepcopy(manifest["sweep_sources"][0])
|
|
307
|
+
source.update(sample_data_token="sweep-1", asset_sha256="sha256:" + "c" * 64)
|
|
308
|
+
carrier = encode_clean_basemap_decisions(
|
|
309
|
+
asset_sha256=source["asset_sha256"], decisions=[1, 0, 3, 18, 33]
|
|
310
|
+
)
|
|
311
|
+
source["decisions"] = carrier.descriptor("decisions")
|
|
312
|
+
assets[source["decisions"]["path"]] = carrier.payload
|
|
313
|
+
manifest["sweep_sources"].append(source)
|
|
314
|
+
manifest["diagnostics"]["source_point_count"] *= 2
|
|
315
|
+
manifest["diagnostics"]["decision_counts"] = {
|
|
316
|
+
key: value * 2
|
|
317
|
+
for key, value in manifest["diagnostics"]["decision_counts"].items()
|
|
318
|
+
}
|
|
319
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
320
|
+
return source
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize("changed", ["source", "count", "descriptor"])
|
|
324
|
+
def test_decision_cache_never_reuses_a_different_binding(changed):
|
|
325
|
+
manifest, assets, _ = fixture()
|
|
326
|
+
second = add_second_sweep(manifest, assets)
|
|
327
|
+
second["decisions"] = copy.deepcopy(manifest["sweep_sources"][0]["decisions"])
|
|
328
|
+
if changed != "source":
|
|
329
|
+
second["asset_sha256"] = manifest["sweep_sources"][0]["asset_sha256"]
|
|
330
|
+
if changed == "count":
|
|
331
|
+
second["point_count"] += 1
|
|
332
|
+
manifest["diagnostics"]["source_point_count"] += 1
|
|
333
|
+
manifest["diagnostics"]["decision_counts"]["33"] += 1
|
|
334
|
+
if changed == "descriptor":
|
|
335
|
+
second["decisions"]["size_bytes"] += 1
|
|
336
|
+
manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
|
|
337
|
+
with pytest.raises(ValueError):
|
|
338
|
+
validate(manifest, assets)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@pytest.mark.parametrize("limit,expected_decodes", [(0, 3), (4, 3), (5, 3), (10, 2)])
|
|
342
|
+
def test_decision_proof_cache_is_bounded_and_evicted_entries_are_revalidated(
|
|
343
|
+
monkeypatch, limit, expected_decodes
|
|
344
|
+
):
|
|
345
|
+
manifest, assets, _ = fixture()
|
|
346
|
+
add_second_sweep(manifest, assets)
|
|
347
|
+
original = clean_basemap.decode_clean_basemap_decisions
|
|
348
|
+
calls = []
|
|
349
|
+
|
|
350
|
+
def count(*args, **kwargs):
|
|
351
|
+
calls.append(kwargs["asset_sha256"])
|
|
352
|
+
return original(*args, **kwargs)
|
|
353
|
+
|
|
354
|
+
monkeypatch.setattr(clean_basemap, "_DECISION_CACHE_BYTES", limit)
|
|
355
|
+
monkeypatch.setattr(clean_basemap, "decode_clean_basemap_decisions", count)
|
|
356
|
+
validate(manifest, assets)
|
|
357
|
+
assert len(calls) == expected_decodes
|
|
358
|
+
|
|
359
|
+
|
|
192
360
|
@pytest.mark.parametrize(
|
|
193
361
|
"overrides,message",
|
|
194
362
|
[
|
|
@@ -221,6 +389,9 @@ def test_original_observation_projection_is_required(kind):
|
|
|
221
389
|
manifest,
|
|
222
390
|
read_asset=assets.__getitem__,
|
|
223
391
|
observation_to_world=lambda *args: result,
|
|
392
|
+
observation_intensity=lambda sweep, ordinals, times: np.asarray(
|
|
393
|
+
[2.5, 19.0, 240.0], dtype=np.float32
|
|
394
|
+
)[ordinals],
|
|
224
395
|
)
|
|
225
396
|
|
|
226
397
|
|
|
@@ -289,6 +460,7 @@ def test_every_byte_in_last_record_obeys_exact_decision_domain(family):
|
|
|
289
460
|
raw_point_ordinals=np.arange(count),
|
|
290
461
|
timestamps_ns=np.full(count, 100),
|
|
291
462
|
decisions=np.full(count, KEEP_GROUND),
|
|
463
|
+
intensities=np.arange(count, dtype=np.float32),
|
|
292
464
|
)
|
|
293
465
|
domain, decode = KEEP_CODES, decode_clean_basemap_mapping
|
|
294
466
|
arguments = dict(geometry_raw_sha256=anchor, point_count=count)
|
|
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_package_version_is_current_release():
|
|
14
|
-
assert x4d_devkit.__version__ == "0.
|
|
14
|
+
assert x4d_devkit.__version__ == "0.47.0"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
|
|
@@ -7,6 +7,7 @@ their native authorities; this module never guesses ownership from coordinates.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import copy
|
|
10
|
+
from collections import OrderedDict
|
|
10
11
|
from dataclasses import dataclass
|
|
11
12
|
import math
|
|
12
13
|
import re
|
|
@@ -35,10 +36,11 @@ from x4d_devkit.semantic_scene_contract import (
|
|
|
35
36
|
)
|
|
36
37
|
from x4d_devkit.time_contract import parse_time_ns
|
|
37
38
|
|
|
38
|
-
CLEAN_BASEMAP_CONTRACT_VERSION = "x4d-clean-static-basemap-
|
|
39
|
+
CLEAN_BASEMAP_CONTRACT_VERSION = "x4d-clean-static-basemap-v5"
|
|
39
40
|
CLEAN_BASEMAP_OPERATION = "reconstruction.prepare_clean_static_basemap"
|
|
40
|
-
CLEAN_BASEMAP_MAPPING_FORMAT = "x4d-clean-static-basemap-mapping-
|
|
41
|
+
CLEAN_BASEMAP_MAPPING_FORMAT = "x4d-clean-static-basemap-mapping-v3"
|
|
41
42
|
CLEAN_BASEMAP_DECISION_FORMAT = "x4d-clean-static-basemap-decisions-v3"
|
|
43
|
+
_DECISION_CACHE_BYTES = 128 << 20
|
|
42
44
|
CLEAN_BASEMAP_INPUT_FIELDS = frozenset(
|
|
43
45
|
{"samples_revision", "sweeps_revision", "calibration_revision", "pose_revision"}
|
|
44
46
|
)
|
|
@@ -53,7 +55,7 @@ DECISION_CODES = frozenset({0, 1, 3, 18, 32, 33, 34})
|
|
|
53
55
|
KEEP_CODES = frozenset({0, 1, 3})
|
|
54
56
|
_HASH = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
55
57
|
_HEADER = struct.Struct("<8sI32s")
|
|
56
|
-
_MAPPING_MAGIC = b"
|
|
58
|
+
_MAPPING_MAGIC = b"X4DCBM3\0"
|
|
57
59
|
_DECISION_MAGIC = b"X4DCBD3\0"
|
|
58
60
|
MAPPING_DTYPE = np.dtype(
|
|
59
61
|
[
|
|
@@ -61,7 +63,8 @@ MAPPING_DTYPE = np.dtype(
|
|
|
61
63
|
("raw_point_ordinal", "<u4"),
|
|
62
64
|
("timestamp_ns", "<u8"),
|
|
63
65
|
("decision", "u1"),
|
|
64
|
-
("
|
|
66
|
+
("intensity", "<f4"),
|
|
67
|
+
("reserved", "u1", (3,)),
|
|
65
68
|
]
|
|
66
69
|
)
|
|
67
70
|
_MAX_U32 = (1 << 32) - 1
|
|
@@ -106,6 +109,21 @@ def _bounds(value: Any, path: str) -> None:
|
|
|
106
109
|
raise ValueError(f"{path} is inverted")
|
|
107
110
|
|
|
108
111
|
|
|
112
|
+
def _intensity_range(value: Any, path: str) -> None:
|
|
113
|
+
if (
|
|
114
|
+
not isinstance(value, list)
|
|
115
|
+
or len(value) != 2
|
|
116
|
+
or any(
|
|
117
|
+
isinstance(v, bool)
|
|
118
|
+
or not isinstance(v, (int, float))
|
|
119
|
+
or not math.isfinite(v)
|
|
120
|
+
for v in value
|
|
121
|
+
)
|
|
122
|
+
or value[0] > value[1]
|
|
123
|
+
):
|
|
124
|
+
raise ValueError(f"{path} must contain finite ordered intensity bounds")
|
|
125
|
+
|
|
126
|
+
|
|
109
127
|
def _freeze(value: Any) -> Any:
|
|
110
128
|
if isinstance(value, Mapping):
|
|
111
129
|
return MappingProxyType({k: _freeze(v) for k, v in value.items()})
|
|
@@ -347,7 +365,16 @@ def parse_clean_basemap_manifest(
|
|
|
347
365
|
for i, chunk in enumerate(chunks):
|
|
348
366
|
path = f"chunks[{i}]"
|
|
349
367
|
_object(
|
|
350
|
-
chunk,
|
|
368
|
+
chunk,
|
|
369
|
+
{
|
|
370
|
+
"ordinal",
|
|
371
|
+
"point_count",
|
|
372
|
+
"bounds",
|
|
373
|
+
"intensity_range",
|
|
374
|
+
"geometry",
|
|
375
|
+
"mapping",
|
|
376
|
+
},
|
|
377
|
+
path,
|
|
351
378
|
)
|
|
352
379
|
if chunk["ordinal"] != i or type(chunk["ordinal"]) is not int:
|
|
353
380
|
raise ValueError("chunk ordinals must be contiguous in canonical order")
|
|
@@ -358,6 +385,7 @@ def parse_clean_basemap_manifest(
|
|
|
358
385
|
f"{path}.point_count",
|
|
359
386
|
)
|
|
360
387
|
_bounds(chunk["bounds"], f"{path}.bounds")
|
|
388
|
+
_intensity_range(chunk["intensity_range"], f"{path}.intensity_range")
|
|
361
389
|
bounds.append(chunk["bounds"])
|
|
362
390
|
_descriptor(chunk["geometry"], "geometry", f"{path}.geometry")
|
|
363
391
|
_descriptor(chunk["mapping"], "mapping", f"{path}.mapping")
|
|
@@ -465,6 +493,7 @@ def encode_clean_basemap_mapping(
|
|
|
465
493
|
raw_point_ordinals: Any,
|
|
466
494
|
timestamps_ns: Any,
|
|
467
495
|
decisions: Any,
|
|
496
|
+
intensities: Any,
|
|
468
497
|
) -> CleanBasemapPayload:
|
|
469
498
|
vectors = {
|
|
470
499
|
"sweep_index": _vector(sweep_indices, _MAX_U32, "sweep_indices"),
|
|
@@ -485,6 +514,19 @@ def encode_clean_basemap_mapping(
|
|
|
485
514
|
records = np.zeros(count, dtype=MAPPING_DTYPE)
|
|
486
515
|
for key, vector in vectors.items():
|
|
487
516
|
records[key] = vector
|
|
517
|
+
intensity = np.asarray(intensities)
|
|
518
|
+
if (
|
|
519
|
+
intensity.shape != (count,)
|
|
520
|
+
or intensity.dtype.kind not in "iuf"
|
|
521
|
+
or not np.isfinite(intensity).all()
|
|
522
|
+
):
|
|
523
|
+
raise ValueError(
|
|
524
|
+
"intensities must contain one finite numeric value per geometry point"
|
|
525
|
+
)
|
|
526
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
527
|
+
records["intensity"] = intensity
|
|
528
|
+
if not np.isfinite(records["intensity"]).all():
|
|
529
|
+
raise ValueError("intensities must be representable as finite float32")
|
|
488
530
|
return _encode(_MAPPING_MAGIC, geometry_raw_sha256, records)
|
|
489
531
|
|
|
490
532
|
|
|
@@ -509,6 +551,7 @@ def decode_clean_basemap_mapping(
|
|
|
509
551
|
np.any(records["reserved"] != 0)
|
|
510
552
|
or np.any(~np.isin(records["decision"], tuple(KEEP_CODES)))
|
|
511
553
|
or np.any(records["timestamp_ns"] > _MAX_I64)
|
|
554
|
+
or not np.isfinite(records["intensity"]).all()
|
|
512
555
|
):
|
|
513
556
|
raise ValueError(
|
|
514
557
|
"mapping records are noncanonical or contain removed observations"
|
|
@@ -602,6 +645,9 @@ def validate_clean_basemap_resource(
|
|
|
602
645
|
observation_to_world: Callable[
|
|
603
646
|
[Mapping[str, Any], np.ndarray, np.ndarray], np.ndarray
|
|
604
647
|
],
|
|
648
|
+
observation_intensity: Callable[
|
|
649
|
+
[Mapping[str, Any], np.ndarray, np.ndarray], np.ndarray
|
|
650
|
+
],
|
|
605
651
|
) -> CleanBasemapManifest:
|
|
606
652
|
"""Verify exact original observations and byte-exact complete scene correspondence."""
|
|
607
653
|
parsed = parse_clean_basemap_manifest(manifest)
|
|
@@ -611,13 +657,47 @@ def validate_clean_basemap_resource(
|
|
|
611
657
|
for sweep in data["sweep_sources"]
|
|
612
658
|
]
|
|
613
659
|
actual_counts = {str(code): 0 for code in DECISION_CODES}
|
|
614
|
-
|
|
660
|
+
decisions_cache = OrderedDict()
|
|
661
|
+
decisions_bytes = 0
|
|
662
|
+
|
|
663
|
+
def read_decisions(sweep):
|
|
664
|
+
nonlocal decisions_bytes
|
|
665
|
+
descriptor = sweep["decisions"]
|
|
666
|
+
payload = read_asset(descriptor["path"])
|
|
667
|
+
# A cached proof never authorizes changed or non-byte carrier data.
|
|
668
|
+
if (
|
|
669
|
+
type(payload) is not bytes
|
|
670
|
+
or len(payload) != descriptor["size_bytes"]
|
|
671
|
+
or sha256_bytes(payload) != descriptor["sha256"]
|
|
672
|
+
):
|
|
673
|
+
raise ValueError(
|
|
674
|
+
"clean basemap payload size/hash does not match descriptor"
|
|
675
|
+
)
|
|
676
|
+
key = (
|
|
677
|
+
tuple(sorted(descriptor.items())),
|
|
678
|
+
sweep["asset_sha256"],
|
|
679
|
+
sweep["point_count"],
|
|
680
|
+
)
|
|
681
|
+
values = decisions_cache.pop(key, None)
|
|
682
|
+
if values is not None:
|
|
683
|
+
decisions_cache[key] = values
|
|
684
|
+
return values
|
|
615
685
|
values = decode_clean_basemap_decisions(
|
|
616
|
-
|
|
617
|
-
descriptor=
|
|
686
|
+
payload,
|
|
687
|
+
descriptor=descriptor,
|
|
618
688
|
asset_sha256=sweep["asset_sha256"],
|
|
619
689
|
point_count=sweep["point_count"],
|
|
620
690
|
)
|
|
691
|
+
if values.nbytes <= _DECISION_CACHE_BYTES:
|
|
692
|
+
while decisions_bytes + values.nbytes > _DECISION_CACHE_BYTES:
|
|
693
|
+
_, evicted = decisions_cache.popitem(last=False)
|
|
694
|
+
decisions_bytes -= evicted.nbytes
|
|
695
|
+
decisions_cache[key] = values
|
|
696
|
+
decisions_bytes += values.nbytes
|
|
697
|
+
return values
|
|
698
|
+
|
|
699
|
+
for sweep in data["sweep_sources"]:
|
|
700
|
+
values = read_decisions(sweep)
|
|
621
701
|
counts = np.bincount(values, minlength=256)
|
|
622
702
|
for code in DECISION_CODES:
|
|
623
703
|
actual_counts[str(code)] += int(counts[code])
|
|
@@ -645,6 +725,11 @@ def validate_clean_basemap_resource(
|
|
|
645
725
|
geometry_raw_sha256=descriptor["raw_sha256"],
|
|
646
726
|
point_count=chunk["point_count"],
|
|
647
727
|
)
|
|
728
|
+
if [
|
|
729
|
+
float(mapping["intensity"].min()),
|
|
730
|
+
float(mapping["intensity"].max()),
|
|
731
|
+
] != chunk["intensity_range"]:
|
|
732
|
+
raise ValueError("basemap intensity range differs from its actual points")
|
|
648
733
|
if np.any(mapping["sweep_index"] >= len(data["sweep_sources"])):
|
|
649
734
|
raise ValueError("mapping source index is outside the pinned source tables")
|
|
650
735
|
for index in np.unique(mapping["sweep_index"]):
|
|
@@ -661,12 +746,7 @@ def validate_clean_basemap_resource(
|
|
|
661
746
|
if np.any(times < low) or np.any(times > high):
|
|
662
747
|
raise ValueError("mapping exact point time is outside its source sweep")
|
|
663
748
|
_mark_unique(observation_seen[index], ordinals, "original observation")
|
|
664
|
-
values =
|
|
665
|
-
read_asset(sweep["decisions"]["path"]),
|
|
666
|
-
descriptor=sweep["decisions"],
|
|
667
|
-
asset_sha256=sweep["asset_sha256"],
|
|
668
|
-
point_count=sweep["point_count"],
|
|
669
|
-
)
|
|
749
|
+
values = read_decisions(sweep)
|
|
670
750
|
if not np.array_equal(values[ordinals], mapping["decision"][selected]):
|
|
671
751
|
raise ValueError(
|
|
672
752
|
"mapping decision does not match its original observation"
|
|
@@ -684,6 +764,16 @@ def validate_clean_basemap_resource(
|
|
|
684
764
|
raise ValueError(
|
|
685
765
|
"basemap geometry differs from its original observations"
|
|
686
766
|
)
|
|
767
|
+
intensity = np.asarray(observation_intensity(sweep, ordinals, times))
|
|
768
|
+
if (
|
|
769
|
+
intensity.shape != ordinals.shape
|
|
770
|
+
or intensity.dtype.kind not in "iuf"
|
|
771
|
+
or not np.isfinite(intensity).all()
|
|
772
|
+
or not np.array_equal(intensity, mapping["intensity"][selected])
|
|
773
|
+
):
|
|
774
|
+
raise ValueError(
|
|
775
|
+
"basemap intensity differs from its original observations"
|
|
776
|
+
)
|
|
687
777
|
return parsed
|
|
688
778
|
|
|
689
779
|
|
|
@@ -307,7 +307,7 @@ class ReconstructionAPI:
|
|
|
307
307
|
"clean basemap chunk ordinal is outside the exact resource"
|
|
308
308
|
)
|
|
309
309
|
chunk = chunks[chunk_ordinal]
|
|
310
|
-
|
|
310
|
+
mapping = decode_clean_basemap_mapping(
|
|
311
311
|
self._client.assets.fetch(
|
|
312
312
|
state.asset_urls[chunk["mapping"]["path"]]
|
|
313
313
|
).content,
|
|
@@ -315,6 +315,14 @@ class ReconstructionAPI:
|
|
|
315
315
|
geometry_raw_sha256=chunk["geometry"]["raw_sha256"],
|
|
316
316
|
point_count=chunk["point_count"],
|
|
317
317
|
)
|
|
318
|
+
if [
|
|
319
|
+
float(mapping["intensity"].min()),
|
|
320
|
+
float(mapping["intensity"].max()),
|
|
321
|
+
] != list(chunk["intensity_range"]):
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"clean basemap intensity range differs from its descriptor"
|
|
324
|
+
)
|
|
325
|
+
return mapping
|
|
318
326
|
|
|
319
327
|
def cancel_clean_basemap_task(
|
|
320
328
|
self, *, project_id: int, task_id: str
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{x4d_devkit-0.46.0rc2 → x4d_devkit-0.47.0}/tests/test_current_state_identity_consumers_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|