rayli commited on
Commit
d53ee59
·
verified ·
1 Parent(s): 602c1f2

Promote inference helpers to public API

Browse files
app.py CHANGED
@@ -43,17 +43,18 @@ except ImportError:
43
  spaces = None
44
 
45
  from infer import (
46
- _base_metadata,
47
- _compute_motion_prediction_artifacts,
48
- _decode_face_part_ids,
49
- _denormalize_points,
50
- _joint_refit_metadata,
51
- _resolve_visualized_batch_link_point_prompts,
52
- _save_articulated_mesh_outputs,
53
- _write_kinematic_and_overparam_visualization,
54
- _write_mesh_like_prediction_files,
55
- _write_metadata_and_summary,
56
- _tensor_to_numpy,
 
57
  )
58
  from instruct_particulate.model import Particulate2ArticulationModel
59
  from instruct_particulate.utils.auto_kinematics_utils import (
@@ -2860,7 +2861,7 @@ def _up_dir_slug(up_dir: str) -> str:
2860
  return up_dir.replace("+", "pos").replace("-", "neg")
2861
 
2862
 
2863
- def _upright_rendering_gallery_item() -> list[tuple[str, str]]:
2864
  from PIL import Image, ImageDraw, ImageFont
2865
 
2866
  placeholder_path = OUTPUT_ROOT / "_ui" / "upright_orientation_rendering.png"
@@ -2891,11 +2892,11 @@ def _upright_rendering_gallery_item() -> list[tuple[str, str]]:
2891
  font=small_font,
2892
  )
2893
  image.save(placeholder_path)
2894
- return [(str(placeholder_path), "Rendering")]
2895
 
2896
 
2897
  def _upright_rendering_preview_paths() -> list[str]:
2898
- placeholder_path = _upright_rendering_gallery_item()[0][0]
2899
  return [placeholder_path for _ in UP_DIR_CHOICES]
2900
 
2901
 
@@ -4052,8 +4053,8 @@ class InstructParticulateApp:
4052
  .numpy()
4053
  .astype(np.int32)
4054
  )
4055
- query_points = _tensor_to_numpy(batch["query_points"][0], dtype=np.float32)
4056
- query_normals = _tensor_to_numpy(
4057
  batch["query_point_normals"][0],
4058
  dtype=np.float32,
4059
  )
@@ -4130,7 +4131,7 @@ class InstructParticulateApp:
4130
  ]
4131
  batch = payload["batch"]
4132
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
4133
- _resolve_visualized_batch_link_point_prompts(
4134
  args=args,
4135
  batch=batch,
4136
  links=links_for_query_visualization,
@@ -4144,7 +4145,7 @@ class InstructParticulateApp:
4144
  query_face_indices = np.asarray(payload["query_face_indices"], dtype=np.int64)
4145
  early_visualization_path = save_predicted_point_query_rest_visualization(
4146
  output_dir,
4147
- query_points=_denormalize_points(
4148
  query_points,
4149
  center=mesh_geometry.center,
4150
  scale=mesh_geometry.scale,
@@ -4155,7 +4156,7 @@ class InstructParticulateApp:
4155
  link_point_prompt_ids=visualization_link_point_prompt_ids,
4156
  links=links_for_query_visualization,
4157
  )
4158
- face_part_ids, face_part_ids_unrefined = _decode_face_part_ids(
4159
  mesh_geometry.normalized_mesh,
4160
  point_part_ids=point_part_ids,
4161
  point_part_probabilities=np.asarray(
@@ -4212,7 +4213,7 @@ class InstructParticulateApp:
4212
  mesh_geometry = self._prepare_geometry(mesh_path, canonical_up)
4213
  batch = _to_device_payload(payload["batch"], device)
4214
  face_part_ids = np.asarray(payload["face_part_ids"], dtype=np.int32)
4215
- motion_artifacts = _compute_motion_prediction_artifacts(
4216
  args,
4217
  model=model,
4218
  batch=batch,
@@ -4323,9 +4324,7 @@ class InstructParticulateApp:
4323
  )
4324
 
4325
  def _prepare_geometry(self, mesh_path: Path, up_dir: str):
4326
- from infer import _prepare_mesh_geometry
4327
-
4328
- return _prepare_mesh_geometry(input_path=mesh_path, up_dir=up_dir)
4329
 
4330
  def _segmentation_num_query_points(
4331
  self,
@@ -4468,7 +4467,7 @@ class InstructParticulateApp:
4468
  face_part_ids = prediction["face_part_ids"]
4469
  query_points = prediction["query_points"]
4470
 
4471
- _write_mesh_like_prediction_files(
4472
  output_dir,
4473
  face_part_ids=face_part_ids,
4474
  query_points=query_points,
@@ -4495,7 +4494,7 @@ class InstructParticulateApp:
4495
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
4496
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
4497
  )
4498
- overparam_visualization_path = _write_kinematic_and_overparam_visualization(
4499
  output_dir,
4500
  kinematic_records=predicted_kinematic,
4501
  visualization_records=None,
@@ -4506,7 +4505,7 @@ class InstructParticulateApp:
4506
  center=mesh_geometry.center,
4507
  scale=mesh_geometry.scale,
4508
  )
4509
- unique_part_ids = _save_articulated_mesh_outputs(
4510
  args,
4511
  output_dir=output_dir,
4512
  original_mesh=mesh_geometry.original_mesh,
@@ -4536,7 +4535,7 @@ class InstructParticulateApp:
4536
  link_names=link_names,
4537
  )
4538
 
4539
- metadata = _base_metadata(
4540
  args,
4541
  mode="mesh",
4542
  input_path=mesh_path,
@@ -4570,11 +4569,11 @@ class InstructParticulateApp:
4570
  "reoriented_to_canonical_z_up": bool(up_dir != "+Z"),
4571
  "rotation_matrix": mesh_geometry.up_dir_rotation.tolist(),
4572
  },
4573
- } | _joint_refit_metadata(
4574
  joint_refit_sampling,
4575
  num_links=len(link_names),
4576
  )
4577
- _write_metadata_and_summary(
4578
  output_dir=output_dir,
4579
  metadata=metadata,
4580
  checkpoint_path=self.checkpoint_path,
 
43
  spaces = None
44
 
45
  from infer import (
46
+ build_base_metadata,
47
+ build_joint_refit_metadata,
48
+ compute_motion_prediction_artifacts,
49
+ decode_face_part_ids,
50
+ denormalize_points,
51
+ prepare_mesh_geometry,
52
+ resolve_visualized_batch_link_point_prompts,
53
+ save_articulated_mesh_outputs,
54
+ tensor_to_numpy,
55
+ write_kinematic_and_overparam_visualization,
56
+ write_mesh_like_prediction_files,
57
+ write_metadata_and_summary,
58
  )
59
  from instruct_particulate.model import Particulate2ArticulationModel
60
  from instruct_particulate.utils.auto_kinematics_utils import (
 
2861
  return up_dir.replace("+", "pos").replace("-", "neg")
2862
 
2863
 
2864
+ def _upright_rendering_placeholder_path() -> str:
2865
  from PIL import Image, ImageDraw, ImageFont
2866
 
2867
  placeholder_path = OUTPUT_ROOT / "_ui" / "upright_orientation_rendering.png"
 
2892
  font=small_font,
2893
  )
2894
  image.save(placeholder_path)
2895
+ return str(placeholder_path)
2896
 
2897
 
2898
  def _upright_rendering_preview_paths() -> list[str]:
2899
+ placeholder_path = _upright_rendering_placeholder_path()
2900
  return [placeholder_path for _ in UP_DIR_CHOICES]
2901
 
2902
 
 
4053
  .numpy()
4054
  .astype(np.int32)
4055
  )
4056
+ query_points = tensor_to_numpy(batch["query_points"][0], dtype=np.float32)
4057
+ query_normals = tensor_to_numpy(
4058
  batch["query_point_normals"][0],
4059
  dtype=np.float32,
4060
  )
 
4131
  ]
4132
  batch = payload["batch"]
4133
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
4134
+ resolve_visualized_batch_link_point_prompts(
4135
  args=args,
4136
  batch=batch,
4137
  links=links_for_query_visualization,
 
4145
  query_face_indices = np.asarray(payload["query_face_indices"], dtype=np.int64)
4146
  early_visualization_path = save_predicted_point_query_rest_visualization(
4147
  output_dir,
4148
+ query_points=denormalize_points(
4149
  query_points,
4150
  center=mesh_geometry.center,
4151
  scale=mesh_geometry.scale,
 
4156
  link_point_prompt_ids=visualization_link_point_prompt_ids,
4157
  links=links_for_query_visualization,
4158
  )
4159
+ face_part_ids, face_part_ids_unrefined = decode_face_part_ids(
4160
  mesh_geometry.normalized_mesh,
4161
  point_part_ids=point_part_ids,
4162
  point_part_probabilities=np.asarray(
 
4213
  mesh_geometry = self._prepare_geometry(mesh_path, canonical_up)
4214
  batch = _to_device_payload(payload["batch"], device)
4215
  face_part_ids = np.asarray(payload["face_part_ids"], dtype=np.int32)
4216
+ motion_artifacts = compute_motion_prediction_artifacts(
4217
  args,
4218
  model=model,
4219
  batch=batch,
 
4324
  )
4325
 
4326
  def _prepare_geometry(self, mesh_path: Path, up_dir: str):
4327
+ return prepare_mesh_geometry(input_path=mesh_path, up_dir=up_dir)
 
 
4328
 
4329
  def _segmentation_num_query_points(
4330
  self,
 
4467
  face_part_ids = prediction["face_part_ids"]
4468
  query_points = prediction["query_points"]
4469
 
4470
+ write_mesh_like_prediction_files(
4471
  output_dir,
4472
  face_part_ids=face_part_ids,
4473
  query_points=query_points,
 
4494
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
4495
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
4496
  )
4497
+ overparam_visualization_path = write_kinematic_and_overparam_visualization(
4498
  output_dir,
4499
  kinematic_records=predicted_kinematic,
4500
  visualization_records=None,
 
4505
  center=mesh_geometry.center,
4506
  scale=mesh_geometry.scale,
4507
  )
4508
+ unique_part_ids = save_articulated_mesh_outputs(
4509
  args,
4510
  output_dir=output_dir,
4511
  original_mesh=mesh_geometry.original_mesh,
 
4535
  link_names=link_names,
4536
  )
4537
 
4538
+ metadata = build_base_metadata(
4539
  args,
4540
  mode="mesh",
4541
  input_path=mesh_path,
 
4569
  "reoriented_to_canonical_z_up": bool(up_dir != "+Z"),
4570
  "rotation_matrix": mesh_geometry.up_dir_rotation.tolist(),
4571
  },
4572
+ } | build_joint_refit_metadata(
4573
  joint_refit_sampling,
4574
  num_links=len(link_names),
4575
  )
4576
+ write_metadata_and_summary(
4577
  output_dir=output_dir,
4578
  metadata=metadata,
4579
  checkpoint_path=self.checkpoint_path,
infer.py CHANGED
@@ -23,9 +23,6 @@ from instruct_particulate.utils.auto_kinematics_utils import (
23
  )
24
  from instruct_particulate.utils.export_utils import export_urdf
25
  from instruct_particulate.utils.inference_utils import (
26
- _range_forward_masks_by_child_link,
27
- _suppress_unforwarded_joint_ranges,
28
- _zero_unforwarded_link_ranges,
29
  axis_point_to_plucker_torch,
30
  build_predicted_joint_records_from_links_and_joints,
31
  build_predicted_kinematic_records,
@@ -43,13 +40,16 @@ from instruct_particulate.utils.inference_utils import (
43
  prepare_inference_batch_from_meta_root,
44
  prismatic_directions_from_plucker,
45
  prompt_for_link_names_and_joints,
 
46
  resolve_checkpoint_path,
47
  resolve_input_mode,
48
  resolve_inference_sampling_config,
49
  resolve_num_query_points,
50
  run_joint_refit_from_face_seg,
51
  run_batched_model_inference,
 
52
  write_json,
 
53
  )
54
  from instruct_particulate.utils.meta_root_utils import (
55
  is_supported_meta_root,
@@ -61,11 +61,11 @@ from instruct_particulate.utils.saved_auto_kinematics_utils import (
61
  load_saved_auto_kinematics_root,
62
  )
63
  from instruct_particulate.utils.inference_visualization_utils import (
64
- _compute_link_geometric_mean_confidences,
65
  print_inference_summary,
66
- save_predicted_point_query_visualization,
67
  save_joint_overparam_visualization_from_model_output,
68
  save_point_query_visualization,
 
69
  save_segmented_visualizations,
70
  select_visualized_link_point_prompts,
71
  )
@@ -334,16 +334,18 @@ def parse_args() -> argparse.Namespace:
334
  return args
335
 
336
 
337
- def _tensor_to_numpy(tensor: torch.Tensor, *, dtype: np.dtype[Any] | type) -> np.ndarray:
 
338
  return tensor.detach().cpu().numpy().astype(dtype, copy=False)
339
 
340
 
341
- def _denormalize_points(
342
  points: np.ndarray,
343
  *,
344
  center: np.ndarray | tuple[float, float, float],
345
  scale: float,
346
  ) -> np.ndarray:
 
347
  return (
348
  np.asarray(points, dtype=np.float32) / np.float32(scale)
349
  + np.asarray(center, dtype=np.float32)
@@ -1391,7 +1393,7 @@ def _apply_confidence_weighted_overparam_joint_voting(
1391
  return weighted_motion_output
1392
 
1393
 
1394
- def _joint_refit_metadata(
1395
  refit_sampling: dict[str, np.ndarray] | None,
1396
  *,
1397
  num_links: int,
@@ -1432,13 +1434,13 @@ def _query_visualization_part_confidences_from_motion_output(
1432
  if joint_decoding_link_ids is None or joint_decoding_confidences is None:
1433
  return None
1434
 
1435
- return _compute_link_geometric_mean_confidences(
1436
- query_link_ids=_tensor_to_numpy(joint_decoding_link_ids[0], dtype=np.int64),
1437
- query_confidences=_tensor_to_numpy(joint_decoding_confidences[0], dtype=np.float32),
1438
  )
1439
 
1440
 
1441
- def _decode_face_part_ids(
1442
  mesh: Any,
1443
  *,
1444
  point_part_ids: np.ndarray,
@@ -1466,7 +1468,7 @@ def _decode_face_part_ids(
1466
  return face_part_ids, face_part_ids_unrefined
1467
 
1468
 
1469
- def _compute_motion_prediction_artifacts(
1470
  args: argparse.Namespace,
1471
  *,
1472
  model: Particulate2ArticulationModel,
@@ -1590,7 +1592,7 @@ def _save_query_predictions(
1590
  ) -> None:
1591
  """Writes first-pass query predictions plus optional joint-refit debug arrays."""
1592
  payload: dict[str, np.ndarray] = {
1593
- "query_points": _denormalize_points(
1594
  query_points,
1595
  center=center,
1596
  scale=scale,
@@ -1605,7 +1607,7 @@ def _save_query_predictions(
1605
  if joint_refit_sampling is not None:
1606
  payload.update(
1607
  {
1608
- "joint_refit_query_points": _denormalize_points(
1609
  joint_refit_sampling["query_points"],
1610
  center=center,
1611
  scale=scale,
@@ -1634,7 +1636,7 @@ def _missing_link_ids(num_links: int, unique_part_ids: np.ndarray) -> list[int]:
1634
  )
1635
 
1636
 
1637
- def _resolve_visualized_batch_link_point_prompts(
1638
  *,
1639
  args: argparse.Namespace,
1640
  batch: dict[str, Any],
@@ -1647,8 +1649,8 @@ def _resolve_visualized_batch_link_point_prompts(
1647
  if link_point_prompts is None:
1648
  return np.zeros((0, 3), dtype=np.float32), np.zeros((0,), dtype=np.int64)
1649
 
1650
- link_point_prompts_world = _denormalize_points(
1651
- _tensor_to_numpy(link_point_prompts[0], dtype=np.float32),
1652
  center=center,
1653
  scale=scale,
1654
  )
@@ -1656,7 +1658,7 @@ def _resolve_visualized_batch_link_point_prompts(
1656
  dropout_eligible_np = (
1657
  None
1658
  if dropout_eligible is None
1659
- else _tensor_to_numpy(dropout_eligible[0], dtype=np.bool_)
1660
  )
1661
  return select_visualized_link_point_prompts(
1662
  link_point_prompts=link_point_prompts_world,
@@ -1666,7 +1668,7 @@ def _resolve_visualized_batch_link_point_prompts(
1666
  )
1667
 
1668
 
1669
- def _base_metadata(
1670
  args: argparse.Namespace,
1671
  *,
1672
  mode: str,
@@ -1679,6 +1681,7 @@ def _base_metadata(
1679
  joint_refit_num_query_points: int,
1680
  sharp_point_ratio: float,
1681
  ) -> dict[str, Any]:
 
1682
  return {
1683
  "mode": mode,
1684
  "input_path": str(input_path),
@@ -1704,7 +1707,7 @@ def _base_metadata(
1704
  }
1705
 
1706
 
1707
- def _write_metadata_and_summary(
1708
  *,
1709
  output_dir: Path,
1710
  metadata: dict[str, Any],
@@ -1879,7 +1882,7 @@ def _prepare_saved_auto_kinematics_mesh_batch(
1879
  )
1880
 
1881
 
1882
- def _prepare_mesh_geometry(
1883
  *,
1884
  input_path: Path,
1885
  up_dir: str,
@@ -2004,7 +2007,7 @@ def _run_mesh_like_prediction(
2004
  point_part_ids = (
2005
  output["segmentation_logits"].argmax(dim=-1)[0].detach().cpu().numpy().astype(np.int32)
2006
  )
2007
- face_part_ids, face_part_ids_unrefined = _decode_face_part_ids(
2008
  normalized_mesh,
2009
  point_part_ids=point_part_ids,
2010
  point_part_probabilities=point_part_probabilities,
@@ -2015,7 +2018,7 @@ def _run_mesh_like_prediction(
2015
  getattr(args, "enforce_connectivity_per_part", False)
2016
  ),
2017
  )
2018
- motion_artifacts = _compute_motion_prediction_artifacts(
2019
  args,
2020
  model=model,
2021
  batch=batch,
@@ -2027,8 +2030,8 @@ def _run_mesh_like_prediction(
2027
  scale=scale,
2028
  )
2029
  return {
2030
- "query_points": _tensor_to_numpy(batch["query_points"][0], dtype=np.float32),
2031
- "query_normals": _tensor_to_numpy(batch["query_point_normals"][0], dtype=np.float32),
2032
  "point_part_ids": point_part_ids,
2033
  "face_part_ids": face_part_ids,
2034
  "face_part_ids_unrefined": face_part_ids_unrefined,
@@ -2036,7 +2039,7 @@ def _run_mesh_like_prediction(
2036
  }
2037
 
2038
 
2039
- def _write_mesh_like_prediction_files(
2040
  output_dir: Path,
2041
  *,
2042
  face_part_ids: np.ndarray,
@@ -2064,7 +2067,7 @@ def _write_mesh_like_prediction_files(
2064
  )
2065
 
2066
 
2067
- def _write_kinematic_and_overparam_visualization(
2068
  output_dir: Path,
2069
  *,
2070
  kinematic_records: dict[str, Any],
@@ -2082,14 +2085,14 @@ def _write_kinematic_and_overparam_visualization(
2082
  kinematic_records if visualization_records is None else visualization_records
2083
  )
2084
  if joint_refit_sampling is None:
2085
- overparam_visualization_query_points = _denormalize_points(
2086
  query_points,
2087
  center=np.asarray(center, dtype=np.float32),
2088
  scale=scale,
2089
  )
2090
  overparam_visualization_link_ids = np.asarray(point_part_ids, dtype=np.int32)
2091
  else:
2092
- overparam_visualization_query_points = _denormalize_points(
2093
  joint_refit_sampling["query_points"],
2094
  center=np.asarray(center, dtype=np.float32),
2095
  scale=scale,
@@ -2110,7 +2113,7 @@ def _write_kinematic_and_overparam_visualization(
2110
  )
2111
 
2112
 
2113
- def _save_articulated_mesh_outputs(
2114
  args: argparse.Namespace,
2115
  *,
2116
  output_dir: Path,
@@ -2192,7 +2195,7 @@ def run_mesh_inference(
2192
  mesh_input_path = saved_auto_root.mesh_path
2193
  mesh_input_up_dir = saved_auto_root.mesh_up_dir
2194
  mode = "saved_auto_kinematics_root"
2195
- mesh_geometry = _prepare_mesh_geometry(
2196
  input_path=mesh_input_path,
2197
  up_dir=mesh_input_up_dir,
2198
  )
@@ -2249,7 +2252,7 @@ def run_mesh_inference(
2249
  face_part_ids_unrefined = prediction["face_part_ids_unrefined"]
2250
  query_points = prediction["query_points"]
2251
 
2252
- _write_mesh_like_prediction_files(
2253
  output_dir,
2254
  face_part_ids=face_part_ids,
2255
  query_points=query_points,
@@ -2276,7 +2279,7 @@ def run_mesh_inference(
2276
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2277
  )
2278
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
2279
- _resolve_visualized_batch_link_point_prompts(
2280
  args=args,
2281
  batch=mesh_batch.batch,
2282
  links=predicted_kinematic["links"],
@@ -2286,7 +2289,7 @@ def run_mesh_inference(
2286
  )
2287
  visualization_path = save_predicted_point_query_visualization(
2288
  output_dir,
2289
- query_points=_denormalize_points(
2290
  query_points,
2291
  center=mesh_geometry.center,
2292
  scale=mesh_geometry.scale,
@@ -2301,7 +2304,7 @@ def run_mesh_inference(
2301
  motion_output
2302
  ),
2303
  )
2304
- overparam_visualization_path = _write_kinematic_and_overparam_visualization(
2305
  output_dir,
2306
  kinematic_records=predicted_kinematic,
2307
  visualization_records=None,
@@ -2312,7 +2315,7 @@ def run_mesh_inference(
2312
  center=mesh_geometry.center,
2313
  scale=mesh_geometry.scale,
2314
  )
2315
- unique_part_ids = _save_articulated_mesh_outputs(
2316
  args,
2317
  output_dir=output_dir,
2318
  original_mesh=mesh_geometry.original_mesh,
@@ -2344,7 +2347,7 @@ def run_mesh_inference(
2344
  prismatic_range=motion_arrays_normalized["prismatic_range"],
2345
  )
2346
 
2347
- metadata = _base_metadata(
2348
  args,
2349
  mode=mode,
2350
  input_path=input_path,
@@ -2382,13 +2385,13 @@ def run_mesh_inference(
2382
  "reoriented_to_canonical_z_up": bool(mesh_input_up_dir != "+Z"),
2383
  "rotation_matrix": mesh_geometry.up_dir_rotation.tolist(),
2384
  },
2385
- } | _joint_refit_metadata(
2386
  joint_refit_sampling,
2387
  num_links=len(mesh_batch.link_names),
2388
  )
2389
  if mesh_batch.auto_kinematics_metadata:
2390
  metadata["auto_kinematics"] = mesh_batch.auto_kinematics_metadata
2391
- _write_metadata_and_summary(
2392
  output_dir=output_dir,
2393
  metadata=metadata,
2394
  checkpoint_path=checkpoint_path,
@@ -2465,20 +2468,20 @@ def run_meta_root_inference(
2465
  (
2466
  gt_revolute_range_forward_mask,
2467
  gt_prismatic_range_forward_mask,
2468
- ) = _range_forward_masks_by_child_link(
2469
  meta_info["sorted_joints"],
2470
  num_links=len(meta_info["link_names"]),
2471
  )
2472
- forwarded_revolute_range_world = _zero_unforwarded_link_ranges(
2473
  motion_arrays_world["revolute_range"],
2474
  forward_mask=gt_revolute_range_forward_mask,
2475
  )
2476
- forwarded_prismatic_range_world = _zero_unforwarded_link_ranges(
2477
  motion_arrays_world["prismatic_range"],
2478
  forward_mask=gt_prismatic_range_forward_mask,
2479
  )
2480
  output_dir = _resolve_output_dir(args, meta_root)
2481
- _write_mesh_like_prediction_files(
2482
  output_dir,
2483
  face_part_ids=face_part_ids,
2484
  query_points=query_points,
@@ -2502,7 +2505,7 @@ def run_meta_root_inference(
2502
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
2503
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2504
  )
2505
- predicted_kinematic["joints"] = _suppress_unforwarded_joint_ranges(
2506
  predicted_kinematic["joints"],
2507
  revolute_range_forward_mask=gt_revolute_range_forward_mask,
2508
  prismatic_range_forward_mask=gt_prismatic_range_forward_mask,
@@ -2518,13 +2521,13 @@ def run_meta_root_inference(
2518
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
2519
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2520
  )
2521
- predicted_joint_records["joints"] = _suppress_unforwarded_joint_ranges(
2522
  predicted_joint_records["joints"],
2523
  revolute_range_forward_mask=gt_revolute_range_forward_mask,
2524
  prismatic_range_forward_mask=gt_prismatic_range_forward_mask,
2525
  )
2526
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
2527
- _resolve_visualized_batch_link_point_prompts(
2528
  args=args,
2529
  batch=batch,
2530
  links=predicted_joint_records["links"],
@@ -2532,7 +2535,7 @@ def run_meta_root_inference(
2532
  scale=scale,
2533
  )
2534
  )
2535
- overparam_visualization_path = _write_kinematic_and_overparam_visualization(
2536
  output_dir,
2537
  kinematic_records=predicted_kinematic,
2538
  visualization_records=predicted_joint_records,
@@ -2548,7 +2551,7 @@ def run_meta_root_inference(
2548
  (int(joint["parent_link_id"]), int(joint["child_link_id"]))
2549
  for joint in meta_info["sorted_joints"]
2550
  ]
2551
- unique_part_ids = _save_articulated_mesh_outputs(
2552
  args,
2553
  output_dir=output_dir,
2554
  original_mesh=original_mesh,
@@ -2590,7 +2593,7 @@ def run_meta_root_inference(
2590
 
2591
  visualization_path = save_point_query_visualization(
2592
  output_dir,
2593
- query_points=_denormalize_points(
2594
  query_points,
2595
  center=np.asarray(center, dtype=np.float32),
2596
  scale=scale,
@@ -2608,7 +2611,7 @@ def run_meta_root_inference(
2608
  gt_joints=meta_info["sorted_joints"],
2609
  )
2610
 
2611
- metadata = _base_metadata(
2612
  args,
2613
  mode="meta_root",
2614
  input_path=meta_root,
@@ -2644,7 +2647,7 @@ def run_meta_root_inference(
2644
  "center": np.asarray(center, dtype=np.float32).tolist(),
2645
  "scale": float(scale),
2646
  },
2647
- } | _joint_refit_metadata(
2648
  joint_refit_sampling,
2649
  num_links=len(meta_info["link_names"]),
2650
  )
@@ -2655,7 +2658,7 @@ def run_meta_root_inference(
2655
  metadata["legacy_meta_path"] = str(meta_root / "meta.npz")
2656
  metadata["legacy_link_axes_plucker_path"] = str(meta_root / "link_axes_plucker.npz")
2657
  metadata["legacy_link_range_path"] = str(meta_root / "link_range.npz")
2658
- _write_metadata_and_summary(
2659
  output_dir=output_dir,
2660
  metadata=metadata,
2661
  checkpoint_path=checkpoint_path,
 
23
  )
24
  from instruct_particulate.utils.export_utils import export_urdf
25
  from instruct_particulate.utils.inference_utils import (
 
 
 
26
  axis_point_to_plucker_torch,
27
  build_predicted_joint_records_from_links_and_joints,
28
  build_predicted_kinematic_records,
 
40
  prepare_inference_batch_from_meta_root,
41
  prismatic_directions_from_plucker,
42
  prompt_for_link_names_and_joints,
43
+ range_forward_masks_by_child_link,
44
  resolve_checkpoint_path,
45
  resolve_input_mode,
46
  resolve_inference_sampling_config,
47
  resolve_num_query_points,
48
  run_joint_refit_from_face_seg,
49
  run_batched_model_inference,
50
+ suppress_unforwarded_joint_ranges,
51
  write_json,
52
+ zero_unforwarded_link_ranges,
53
  )
54
  from instruct_particulate.utils.meta_root_utils import (
55
  is_supported_meta_root,
 
61
  load_saved_auto_kinematics_root,
62
  )
63
  from instruct_particulate.utils.inference_visualization_utils import (
64
+ compute_link_geometric_mean_confidences,
65
  print_inference_summary,
 
66
  save_joint_overparam_visualization_from_model_output,
67
  save_point_query_visualization,
68
+ save_predicted_point_query_visualization,
69
  save_segmented_visualizations,
70
  select_visualized_link_point_prompts,
71
  )
 
334
  return args
335
 
336
 
337
+ def tensor_to_numpy(tensor: torch.Tensor, *, dtype: np.dtype[Any] | type) -> np.ndarray:
338
+ """Converts a tensor to a CPU NumPy array with the requested dtype."""
339
  return tensor.detach().cpu().numpy().astype(dtype, copy=False)
340
 
341
 
342
+ def denormalize_points(
343
  points: np.ndarray,
344
  *,
345
  center: np.ndarray | tuple[float, float, float],
346
  scale: float,
347
  ) -> np.ndarray:
348
+ """Converts normalized model-space points back to mesh-space coordinates."""
349
  return (
350
  np.asarray(points, dtype=np.float32) / np.float32(scale)
351
  + np.asarray(center, dtype=np.float32)
 
1393
  return weighted_motion_output
1394
 
1395
 
1396
+ def build_joint_refit_metadata(
1397
  refit_sampling: dict[str, np.ndarray] | None,
1398
  *,
1399
  num_links: int,
 
1434
  if joint_decoding_link_ids is None or joint_decoding_confidences is None:
1435
  return None
1436
 
1437
+ return compute_link_geometric_mean_confidences(
1438
+ query_link_ids=tensor_to_numpy(joint_decoding_link_ids[0], dtype=np.int64),
1439
+ query_confidences=tensor_to_numpy(joint_decoding_confidences[0], dtype=np.float32),
1440
  )
1441
 
1442
 
1443
+ def decode_face_part_ids(
1444
  mesh: Any,
1445
  *,
1446
  point_part_ids: np.ndarray,
 
1468
  return face_part_ids, face_part_ids_unrefined
1469
 
1470
 
1471
+ def compute_motion_prediction_artifacts(
1472
  args: argparse.Namespace,
1473
  *,
1474
  model: Particulate2ArticulationModel,
 
1592
  ) -> None:
1593
  """Writes first-pass query predictions plus optional joint-refit debug arrays."""
1594
  payload: dict[str, np.ndarray] = {
1595
+ "query_points": denormalize_points(
1596
  query_points,
1597
  center=center,
1598
  scale=scale,
 
1607
  if joint_refit_sampling is not None:
1608
  payload.update(
1609
  {
1610
+ "joint_refit_query_points": denormalize_points(
1611
  joint_refit_sampling["query_points"],
1612
  center=center,
1613
  scale=scale,
 
1636
  )
1637
 
1638
 
1639
+ def resolve_visualized_batch_link_point_prompts(
1640
  *,
1641
  args: argparse.Namespace,
1642
  batch: dict[str, Any],
 
1649
  if link_point_prompts is None:
1650
  return np.zeros((0, 3), dtype=np.float32), np.zeros((0,), dtype=np.int64)
1651
 
1652
+ link_point_prompts_world = denormalize_points(
1653
+ tensor_to_numpy(link_point_prompts[0], dtype=np.float32),
1654
  center=center,
1655
  scale=scale,
1656
  )
 
1658
  dropout_eligible_np = (
1659
  None
1660
  if dropout_eligible is None
1661
+ else tensor_to_numpy(dropout_eligible[0], dtype=np.bool_)
1662
  )
1663
  return select_visualized_link_point_prompts(
1664
  link_point_prompts=link_point_prompts_world,
 
1668
  )
1669
 
1670
 
1671
+ def build_base_metadata(
1672
  args: argparse.Namespace,
1673
  *,
1674
  mode: str,
 
1681
  joint_refit_num_query_points: int,
1682
  sharp_point_ratio: float,
1683
  ) -> dict[str, Any]:
1684
+ """Builds the metadata fields common to mesh and meta-root inference."""
1685
  return {
1686
  "mode": mode,
1687
  "input_path": str(input_path),
 
1707
  }
1708
 
1709
 
1710
+ def write_metadata_and_summary(
1711
  *,
1712
  output_dir: Path,
1713
  metadata: dict[str, Any],
 
1882
  )
1883
 
1884
 
1885
+ def prepare_mesh_geometry(
1886
  *,
1887
  input_path: Path,
1888
  up_dir: str,
 
2007
  point_part_ids = (
2008
  output["segmentation_logits"].argmax(dim=-1)[0].detach().cpu().numpy().astype(np.int32)
2009
  )
2010
+ face_part_ids, face_part_ids_unrefined = decode_face_part_ids(
2011
  normalized_mesh,
2012
  point_part_ids=point_part_ids,
2013
  point_part_probabilities=point_part_probabilities,
 
2018
  getattr(args, "enforce_connectivity_per_part", False)
2019
  ),
2020
  )
2021
+ motion_artifacts = compute_motion_prediction_artifacts(
2022
  args,
2023
  model=model,
2024
  batch=batch,
 
2030
  scale=scale,
2031
  )
2032
  return {
2033
+ "query_points": tensor_to_numpy(batch["query_points"][0], dtype=np.float32),
2034
+ "query_normals": tensor_to_numpy(batch["query_point_normals"][0], dtype=np.float32),
2035
  "point_part_ids": point_part_ids,
2036
  "face_part_ids": face_part_ids,
2037
  "face_part_ids_unrefined": face_part_ids_unrefined,
 
2039
  }
2040
 
2041
 
2042
+ def write_mesh_like_prediction_files(
2043
  output_dir: Path,
2044
  *,
2045
  face_part_ids: np.ndarray,
 
2067
  )
2068
 
2069
 
2070
+ def write_kinematic_and_overparam_visualization(
2071
  output_dir: Path,
2072
  *,
2073
  kinematic_records: dict[str, Any],
 
2085
  kinematic_records if visualization_records is None else visualization_records
2086
  )
2087
  if joint_refit_sampling is None:
2088
+ overparam_visualization_query_points = denormalize_points(
2089
  query_points,
2090
  center=np.asarray(center, dtype=np.float32),
2091
  scale=scale,
2092
  )
2093
  overparam_visualization_link_ids = np.asarray(point_part_ids, dtype=np.int32)
2094
  else:
2095
+ overparam_visualization_query_points = denormalize_points(
2096
  joint_refit_sampling["query_points"],
2097
  center=np.asarray(center, dtype=np.float32),
2098
  scale=scale,
 
2113
  )
2114
 
2115
 
2116
+ def save_articulated_mesh_outputs(
2117
  args: argparse.Namespace,
2118
  *,
2119
  output_dir: Path,
 
2195
  mesh_input_path = saved_auto_root.mesh_path
2196
  mesh_input_up_dir = saved_auto_root.mesh_up_dir
2197
  mode = "saved_auto_kinematics_root"
2198
+ mesh_geometry = prepare_mesh_geometry(
2199
  input_path=mesh_input_path,
2200
  up_dir=mesh_input_up_dir,
2201
  )
 
2252
  face_part_ids_unrefined = prediction["face_part_ids_unrefined"]
2253
  query_points = prediction["query_points"]
2254
 
2255
+ write_mesh_like_prediction_files(
2256
  output_dir,
2257
  face_part_ids=face_part_ids,
2258
  query_points=query_points,
 
2279
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2280
  )
2281
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
2282
+ resolve_visualized_batch_link_point_prompts(
2283
  args=args,
2284
  batch=mesh_batch.batch,
2285
  links=predicted_kinematic["links"],
 
2289
  )
2290
  visualization_path = save_predicted_point_query_visualization(
2291
  output_dir,
2292
+ query_points=denormalize_points(
2293
  query_points,
2294
  center=mesh_geometry.center,
2295
  scale=mesh_geometry.scale,
 
2304
  motion_output
2305
  ),
2306
  )
2307
+ overparam_visualization_path = write_kinematic_and_overparam_visualization(
2308
  output_dir,
2309
  kinematic_records=predicted_kinematic,
2310
  visualization_records=None,
 
2315
  center=mesh_geometry.center,
2316
  scale=mesh_geometry.scale,
2317
  )
2318
+ unique_part_ids = save_articulated_mesh_outputs(
2319
  args,
2320
  output_dir=output_dir,
2321
  original_mesh=mesh_geometry.original_mesh,
 
2347
  prismatic_range=motion_arrays_normalized["prismatic_range"],
2348
  )
2349
 
2350
+ metadata = build_base_metadata(
2351
  args,
2352
  mode=mode,
2353
  input_path=input_path,
 
2385
  "reoriented_to_canonical_z_up": bool(mesh_input_up_dir != "+Z"),
2386
  "rotation_matrix": mesh_geometry.up_dir_rotation.tolist(),
2387
  },
2388
+ } | build_joint_refit_metadata(
2389
  joint_refit_sampling,
2390
  num_links=len(mesh_batch.link_names),
2391
  )
2392
  if mesh_batch.auto_kinematics_metadata:
2393
  metadata["auto_kinematics"] = mesh_batch.auto_kinematics_metadata
2394
+ write_metadata_and_summary(
2395
  output_dir=output_dir,
2396
  metadata=metadata,
2397
  checkpoint_path=checkpoint_path,
 
2468
  (
2469
  gt_revolute_range_forward_mask,
2470
  gt_prismatic_range_forward_mask,
2471
+ ) = range_forward_masks_by_child_link(
2472
  meta_info["sorted_joints"],
2473
  num_links=len(meta_info["link_names"]),
2474
  )
2475
+ forwarded_revolute_range_world = zero_unforwarded_link_ranges(
2476
  motion_arrays_world["revolute_range"],
2477
  forward_mask=gt_revolute_range_forward_mask,
2478
  )
2479
+ forwarded_prismatic_range_world = zero_unforwarded_link_ranges(
2480
  motion_arrays_world["prismatic_range"],
2481
  forward_mask=gt_prismatic_range_forward_mask,
2482
  )
2483
  output_dir = _resolve_output_dir(args, meta_root)
2484
+ write_mesh_like_prediction_files(
2485
  output_dir,
2486
  face_part_ids=face_part_ids,
2487
  query_points=query_points,
 
2505
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
2506
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2507
  )
2508
+ predicted_kinematic["joints"] = suppress_unforwarded_joint_ranges(
2509
  predicted_kinematic["joints"],
2510
  revolute_range_forward_mask=gt_revolute_range_forward_mask,
2511
  prismatic_range_forward_mask=gt_prismatic_range_forward_mask,
 
2521
  revolute_parameter_valid=motion_arrays_normalized["revolute_parameter_valid"],
2522
  prismatic_parameter_valid=motion_arrays_normalized["prismatic_parameter_valid"],
2523
  )
2524
+ predicted_joint_records["joints"] = suppress_unforwarded_joint_ranges(
2525
  predicted_joint_records["joints"],
2526
  revolute_range_forward_mask=gt_revolute_range_forward_mask,
2527
  prismatic_range_forward_mask=gt_prismatic_range_forward_mask,
2528
  )
2529
  visualization_link_point_prompts_world, visualization_link_point_prompt_ids = (
2530
+ resolve_visualized_batch_link_point_prompts(
2531
  args=args,
2532
  batch=batch,
2533
  links=predicted_joint_records["links"],
 
2535
  scale=scale,
2536
  )
2537
  )
2538
+ overparam_visualization_path = write_kinematic_and_overparam_visualization(
2539
  output_dir,
2540
  kinematic_records=predicted_kinematic,
2541
  visualization_records=predicted_joint_records,
 
2551
  (int(joint["parent_link_id"]), int(joint["child_link_id"]))
2552
  for joint in meta_info["sorted_joints"]
2553
  ]
2554
+ unique_part_ids = save_articulated_mesh_outputs(
2555
  args,
2556
  output_dir=output_dir,
2557
  original_mesh=original_mesh,
 
2593
 
2594
  visualization_path = save_point_query_visualization(
2595
  output_dir,
2596
+ query_points=denormalize_points(
2597
  query_points,
2598
  center=np.asarray(center, dtype=np.float32),
2599
  scale=scale,
 
2611
  gt_joints=meta_info["sorted_joints"],
2612
  )
2613
 
2614
+ metadata = build_base_metadata(
2615
  args,
2616
  mode="meta_root",
2617
  input_path=meta_root,
 
2647
  "center": np.asarray(center, dtype=np.float32).tolist(),
2648
  "scale": float(scale),
2649
  },
2650
+ } | build_joint_refit_metadata(
2651
  joint_refit_sampling,
2652
  num_links=len(meta_info["link_names"]),
2653
  )
 
2658
  metadata["legacy_meta_path"] = str(meta_root / "meta.npz")
2659
  metadata["legacy_link_axes_plucker_path"] = str(meta_root / "link_axes_plucker.npz")
2660
  metadata["legacy_link_range_path"] = str(meta_root / "link_range.npz")
2661
+ write_metadata_and_summary(
2662
  output_dir=output_dir,
2663
  metadata=metadata,
2664
  checkpoint_path=checkpoint_path,
instruct_particulate/utils/inference_utils.py CHANGED
@@ -49,15 +49,17 @@ def resolve_num_query_points(
49
 
50
 
51
  def write_json(path: Path, payload: Any) -> None:
 
52
  with path.open("w", encoding="utf-8") as fh:
53
  json.dump(payload, fh, indent=2)
54
 
55
 
56
- def _range_forward_masks_by_child_link(
57
  joints: Sequence[dict[str, Any]],
58
  *,
59
  num_links: int,
60
  ) -> tuple[np.ndarray, np.ndarray]:
 
61
  revolute_range_forward_mask = np.zeros((num_links,), dtype=np.bool_)
62
  prismatic_range_forward_mask = np.zeros((num_links,), dtype=np.bool_)
63
  for joint in joints:
@@ -73,12 +75,13 @@ def _range_forward_masks_by_child_link(
73
  return revolute_range_forward_mask, prismatic_range_forward_mask
74
 
75
 
76
- def _suppress_unforwarded_joint_ranges(
77
  joints: Sequence[dict[str, Any]],
78
  *,
79
  revolute_range_forward_mask: np.ndarray,
80
  prismatic_range_forward_mask: np.ndarray,
81
  ) -> list[dict[str, Any]]:
 
82
  suppressed_joints: list[dict[str, Any]] = []
83
  for joint in joints:
84
  child_link_id = int(joint["child_link_id"])
@@ -91,11 +94,12 @@ def _suppress_unforwarded_joint_ranges(
91
  return suppressed_joints
92
 
93
 
94
- def _zero_unforwarded_link_ranges(
95
  link_ranges: np.ndarray,
96
  *,
97
  forward_mask: np.ndarray,
98
  ) -> np.ndarray:
 
99
  masked_ranges = np.asarray(link_ranges, dtype=np.float32).copy()
100
  if masked_ranges.shape != (len(forward_mask), 2):
101
  raise ValueError(
 
49
 
50
 
51
  def write_json(path: Path, payload: Any) -> None:
52
+ """Writes a JSON payload with stable indentation."""
53
  with path.open("w", encoding="utf-8") as fh:
54
  json.dump(payload, fh, indent=2)
55
 
56
 
57
+ def range_forward_masks_by_child_link(
58
  joints: Sequence[dict[str, Any]],
59
  *,
60
  num_links: int,
61
  ) -> tuple[np.ndarray, np.ndarray]:
62
+ """Builds masks for links whose joint ranges are present in metadata."""
63
  revolute_range_forward_mask = np.zeros((num_links,), dtype=np.bool_)
64
  prismatic_range_forward_mask = np.zeros((num_links,), dtype=np.bool_)
65
  for joint in joints:
 
75
  return revolute_range_forward_mask, prismatic_range_forward_mask
76
 
77
 
78
+ def suppress_unforwarded_joint_ranges(
79
  joints: Sequence[dict[str, Any]],
80
  *,
81
  revolute_range_forward_mask: np.ndarray,
82
  prismatic_range_forward_mask: np.ndarray,
83
  ) -> list[dict[str, Any]]:
84
+ """Returns joint records with non-forwarded ranges removed."""
85
  suppressed_joints: list[dict[str, Any]] = []
86
  for joint in joints:
87
  child_link_id = int(joint["child_link_id"])
 
94
  return suppressed_joints
95
 
96
 
97
+ def zero_unforwarded_link_ranges(
98
  link_ranges: np.ndarray,
99
  *,
100
  forward_mask: np.ndarray,
101
  ) -> np.ndarray:
102
+ """Zeros per-link ranges that were not forwarded from source metadata."""
103
  masked_ranges = np.asarray(link_ranges, dtype=np.float32).copy()
104
  if masked_ranges.shape != (len(forward_mask), 2):
105
  raise ValueError(
instruct_particulate/utils/inference_visualization_utils.py CHANGED
@@ -24,11 +24,12 @@ from instruct_particulate.utils.visualization_utils import (
24
  )
25
 
26
 
27
- def _compute_link_geometric_mean_confidences(
28
  *,
29
  query_link_ids: np.ndarray,
30
  query_confidences: np.ndarray,
31
  ) -> dict[int, float]:
 
32
  query_link_ids = np.asarray(query_link_ids, dtype=np.int64)
33
  query_confidences = np.asarray(query_confidences, dtype=np.float32)
34
  if query_link_ids.shape != query_confidences.shape:
 
24
  )
25
 
26
 
27
+ def compute_link_geometric_mean_confidences(
28
  *,
29
  query_link_ids: np.ndarray,
30
  query_confidences: np.ndarray,
31
  ) -> dict[int, float]:
32
+ """Computes per-link geometric mean confidence for query visualizations."""
33
  query_link_ids = np.asarray(query_link_ids, dtype=np.int64)
34
  query_confidences = np.asarray(query_confidences, dtype=np.float32)
35
  if query_link_ids.shape != query_confidences.shape: