Skip to content

[AdvCP] Add asset generation and validation utilities - #184

Open
meaning-0f-life wants to merge 10 commits into
CAVISE:mainfrom
meaning-0f-life:main
Open

meaning-0f-life wants to merge 10 commits into
CAVISE:mainfrom
meaning-0f-life:main

Conversation

@meaning-0f-life

Copy link
Copy Markdown

Summary

Related to #140

Adds utility scripts under opencda/core/attack/advcp/utils/ for generating and validating the 3D assets required by AdvCP spoofing and removal attacks. This addresses the reproducibility problem raised in the original AdvCollaborativePerception repository (zqzqz/AdvCollaborativePerception#8) and extends the same capability to removal-related assets.

Changes

New files

  • utils/generate_remove_advshape_assets.py — CLI for generating removal mesh-divide metadata (.pkl) and adversarial-shape perturbation tensors (.npy). Supports three modes: divide, perturb, and both. Perturbations can be zero-initialised or random with configurable scale and seed.

  • utils/validate_advcp_assets.py — CLI for validating .ply, .pkl, and .npy assets against AdvCP pipeline requirements. Checks file existence, mesh structural integrity, coordinate frame and scale, divide-index bounds, and perturbation shape/finiteness. Exits with code 0 on success or 1 with actionable error messages.

  • test/test_advcp_asset_utils.py — 90 unit tests (84 passed, 6 skipped on Python 3.9) covering all utility functions, CLI smoke tests, and edge cases. All tests use temporary directories and do not modify the repository.

Modified files

  • utils/__init__.py — Updated module docstring with descriptions of all submodules.

  • utils/asset_utils.py — Added comprehensive NumPy-style docstrings to MeshData and all 17 functions (parameters, return values, raised exceptions).

  • utils/generate_car_mesh.py — Added module-level and function-level docstrings.

  • utils/generate_mesh_divide.py — Added module-level and function-level docstrings.

  • utils/runtime_assets.py — Added module-level and class-level docstrings with detailed parameter documentation for AdvCPRuntimeAssetHelper and all 6 methods.

Key features

  1. CLI-driven generation — assets can be regenerated for any CARLA vehicle type or external mesh input.
  2. Runtime generation support — AdvCPRuntimeAssetHelper provides on-demand asset generation during simulation, avoiding the need to commit large .ply/.pkl/.npy files to the repository.
  3. Optional caching — generated assets can be stored outside the source tree and reused across runs via the asset_cache_dir config key.
  4. Validation — early failure with descriptive error messages if assets are missing, malformed, or incompatible with the attack pipeline.
  5. Reproducibility — the generation process is transparent and scriptable, solving the "how was car_0200.ply generated?" problem.

Usage examples

# Generate a car mesh from a CARLA blueprint
python -m opencda.core.attack.advcp.utils.generate_car_mesh \
  --vehicle-blueprint vehicle.tesla.model3 \
  --output opencda/core/attack/advcp/3d_models/car_mesh_0200.ply

# Generate spoof mesh-divide metadata
python -m opencda.core.attack.advcp.utils.generate_mesh_divide \
  --mesh opencda/core/attack/advcp/3d_models/car_mesh_0200.ply \
  --mode spoof \
  --output opencda/core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl

# Generate removal assets
python -m opencda.core.attack.advcp.utils.generate_remove_advshape_assets \
  --mode both \
  --divide-output opencda/core/attack/advcp/3d_models/remove/mesh_divide.pkl \
  --perturb-output opencda/core/attack/advcp/3d_models/remove/mesh_perturb.npy

# Validate all generated assets
python -m opencda.core.attack.advcp.utils.validate_advcp_assets \
  --car-mesh opencda/core/attack/advcp/3d_models/car_mesh_0200.ply \
  --spoof-divide opencda/core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl

@@ -0,0 +1,93 @@
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move utilities within opencda/scripts directory

Comment thread scripts/advcp/validate_advcp_assets.py Outdated
# 4. Validate removal perturbation (if provided)
if args.remove_perturb is not None:
try:
_validate_remove_perturb(args.remove_perturb, car_vertex_count)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removal assets are based on the 98-vertex adv-shape template, not on --car-mesh. Passing car_vertex_count to both removal checks makes the documented “validate all assets” workflow reject assets generated by these utilities for example, an 8-vertex generated car mesh together with a 98-vertex removal template. Please resolve a separate removal-template vertex count and use it for both remove-divide and remove-perturb; --advshape-template-vertices should take precedence when provided.

@@ -0,0 +1,93 @@
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you need to run simulation with attacks and pin proofs how it works with new generated assets

np.argwhere(z >= maxs[2] - eps[2]).reshape(-1),
np.argwhere(z <= mins[2] + eps[2]).reshape(-1),
np.argwhere(np.logical_and(x >= mids[0], z >= mids[2])).reshape(-1),
np.argwhere(np.logical_and(x < mids[0], z >= mids[2])).reshape(-1),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generating the default car mesh produces an 8-vertex box. For that mesh, these last two groups contain only two vertices each. TriangleMesh.select_by_index() therefore returns empty meshes for them, and AdvCPCarMeshHelper.ray_intersection() currently passes those empty meshes to RaycastingScene; I reproduced an IndexError: _Map_base::at. Please generate groups that each retain at least one face (or subdivide the default mesh), and validate the resulting mesh pieces rather than only checking that an index array is non-empty.

The AdvCP template mesh.
"""
template = box_mesh(4.9, 2.5, 2.0)
return subdivide_midpoint(template, levels=2)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template is geometrically equivalent to the runtime AdvCP template, but its vertex ordering differs from the Open3D template created in _create_adv_shape_template_mesh(). The generated .pkl indices and .npy perturbations are positional, so they are applied to different runtime vertices. In a local comparison, none of the 10 generated groups matched the runtime groups. Please share the exact same template construction and vertex ordering with the consumer, or export and consume the generated template mesh itself.

@@ -0,0 +1,93 @@
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you need to add new assets in PR

"""
if blueprint is None:
return _DEFAULT_DIMENSIONS_M
return _BLUEPRINT_DIMENSIONS_M.get(blueprint, _DEFAULT_DIMENSIONS_M)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unknown blueprint silently falls back to Tesla Model 3 dimensions, and the blueprint is only used to select hardcoded dimensions for a box mesh. No CARLA vehicle geometry is queried or exported. This means different CARLA blueprint values can produce the same generic box and does not provide the blueprint-based mesh generation requested in #140. Please either derive the geometry or bounding box from CARLA, or rename and document this mode as a generic box fallback and fail or warn for unknown blueprints.

return None

cache_dir_value = config.get("asset_cache_dir")
if cache_dir_value is None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue #140 describes caching as optional, but removal runtime generation is disabled entirely when asset_cache_dir is not configured. Generation and caching should be separate choices: without a cache directory, the helper should still be able to generate to the explicitly configured removal asset paths (or another documented temporary/output location).

cache_dir = cls._resolve_absolute(cache_dir_value)
blueprint = str(config.get("vehicle_blueprint") or "vehicle.default").replace(".", "_")
mesh_name = f"car_mesh_{blueprint}.ply"
divide_name = f"car_mesh_divide_{blueprint}.pkl"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache key only includes the blueprint name, while the generated mesh also depends on car_mesh_dimensions, car_mesh_source_path, car_mesh_preserve_aspect, and the generation algorithm. Changing any of those values will silently reuse stale, incompatible assets. Please include all generation inputs, or a deterministic metadata hash and format version, in the cache identity and validate the metadata before reuse.


with path.open("rb") as handle:
handle.seek(data_offset)
vertex_struct = struct.Struct("<ddd")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This binary PLY reader ignores the property declarations in the header and hardcodes vertices as three double values and faces as uchar plus uint32 indices. Valid external PLY files commonly use float coordinates or include normals and colors, which shifts the binary layout and causes incorrect parsing or EOF errors. Since #140 requires external .ply inputs, please parse the declared property types and order or delegate PLY loading to a format-aware library such as Open3D.

Haralishev77 and others added 4 commits September 15, 2026 18:59
- Build the adv-shape template with Open3D's exact vertex/triangle
  ordering (create_box + subdivide_midpoint) and use it as the single
  source of truth in early_fusion_attack; share the default removal
  partition (advshape_default_divide) between generator and runtime.
- Subdivide the default generated car mesh and validate that every
  divide group yields a non-empty mesh piece (generators, runtime
  helper and validator), preventing empty RaycastingScene inputs.
- Replace the hardcoded binary PLY reader with a header-driven parser
  (float/double, extra properties, both byte orders, polygons).
- Measure the reference bbox of car meshes missing from
  CAR_MESH_3D_EXAMPLES instead of borrowing car_mesh_0200's.
- Add regression tests pinned to Open3D 0.18/0.19 output; apply ruff
  format to the asset utilities.
Keep asset_utils and runtime_assets in opencda/core/attack/advcp/utils
as the library shared with the attack runtime, and move the
command-line wrappers (generate_car_mesh, generate_mesh_divide,
generate_remove_advshape_assets, validate_advcp_assets) to
scripts/advcp, run as `python -m scripts.advcp.<name>` from the
repository root. Their smoke tests move to test/ alongside the other
script tests; usage examples now point at a models-repository bundle
instead of the removed in-tree 3d_models directory.
Removal divide and perturbation assets are indexed against the
98-vertex adv-shape template used by the attack runtime, not against
--car-mesh. The validator now resolves the template vertex count
separately (--advshape-template-vertices takes precedence when given),
checks both removal assets against it, and verifies that every removal
group keeps template triangles. The documented "validate all assets"
workflow now accepts assets produced by the generation scripts.
Blueprint-based mesh generation only looks up approximate dimensions in
a small table and builds a generic box; no CARLA geometry is queried.
Document it as such, and raise an actionable error for blueprints that
are not in the table (pass --dimensions / car_mesh_dimensions or an
external mesh instead) rather than silently using Tesla Model 3
dimensions. The CLI reports the error through argparse.
AdvCoperceptionModelManager.load_config now calls
AdvCPRuntimeAssetHelper.prepare_assets, so `asset_runtime_generation:
true` in the AdvCP YAML generates the car mesh and spoof divide (and,
with advshape, removal assets) on demand and points the config at them.

Generation and caching are now independent:
- explicit asset paths: missing files are generated there; files
  without generation metadata are user-provided and never overwritten
  (a missing spoof divide is derived from a user mesh);
- asset_cache_dir: assets are stored under a key hashing every
  generation input (blueprint, dimensions, source mesh content,
  preserve_aspect, subdivision, format version) and a metadata.json is
  checked before reuse, so changed inputs never reuse stale assets;
- neither: spoof assets go to a per-process temporary directory and
  removal assets fall back to the identical runtime default partition.

Bundle default paths are never written to. Relative paths resolve
against the AdvCP YAML directory. New config keys are documented in
AdvCPConfig and example.yaml.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add AdvCP utilities for generating vehicle mesh and mesh-division assets

2 participants