From a187d2698790dbf2bef210981c12067d4ddd0ff4 Mon Sep 17 00:00:00 2001 From: Cristian Predut Date: Wed, 3 Dec 2025 18:15:54 +0100 Subject: [PATCH] reOrg B --- .../configs/camera_config_v01.json | 2 +- .../docs/NukeCam-2-Wan_explained_no-HTML.md | 131 -------- .../{ => _BK}/nukeCam_unit-1_onlyZoom.asci | 0 .../{ => _BK}/nukeCam_unit-1_wRotation.asci | 0 .../nukeCam_unit-1_wRotation_v2.asci | 0 .../outputs/nukeCam_unit-1_2-files.txt | 81 +++++ .../testing_tools/check_mapping.py | 111 ------- .../testing_tools/debug_rotation.py | 180 ----------- .../testing_tools/pose_diagnostics.py | 301 ++++++++++++++++++ .../testing_tools/validate_conversion.py | 209 ------------ 10 files changed, 383 insertions(+), 632 deletions(-) delete mode 100644 nuke_CAM_exporter/docs/NukeCam-2-Wan_explained_no-HTML.md rename nuke_CAM_exporter/inputs/{ => _BK}/nukeCam_unit-1_onlyZoom.asci (100%) rename nuke_CAM_exporter/inputs/{ => _BK}/nukeCam_unit-1_wRotation.asci (100%) rename nuke_CAM_exporter/inputs/{ => _BK}/nukeCam_unit-1_wRotation_v2.asci (100%) create mode 100644 nuke_CAM_exporter/outputs/nukeCam_unit-1_2-files.txt delete mode 100644 nuke_CAM_exporter/testing_tools/check_mapping.py delete mode 100644 nuke_CAM_exporter/testing_tools/debug_rotation.py create mode 100644 nuke_CAM_exporter/testing_tools/pose_diagnostics.py delete mode 100644 nuke_CAM_exporter/testing_tools/validate_conversion.py diff --git a/nuke_CAM_exporter/configs/camera_config_v01.json b/nuke_CAM_exporter/configs/camera_config_v01.json index 24cd0bf..11adc2a 100644 --- a/nuke_CAM_exporter/configs/camera_config_v01.json +++ b/nuke_CAM_exporter/configs/camera_config_v01.json @@ -2,7 +2,7 @@ "input": "../inputs/nukeCam_unit-1_wRotation.asci", "input_translation": "../inputs/nukeCam_unit-1-split_tr.asci", "input_rotation": "../inputs/nukeCam_unit-1-split_rot.asci", - "output": "../outputs/nukeCam_unit-1_wRotation_converted.txt", + "output": "../outputs/nukeCam_unit-1_2-files.txt", "fps": 25.0, "unit_scale": 1, "lens": { diff --git a/nuke_CAM_exporter/docs/NukeCam-2-Wan_explained_no-HTML.md b/nuke_CAM_exporter/docs/NukeCam-2-Wan_explained_no-HTML.md deleted file mode 100644 index 3953c57..0000000 --- a/nuke_CAM_exporter/docs/NukeCam-2-Wan_explained_no-HTML.md +++ /dev/null @@ -1,131 +0,0 @@ -# Camera → Wan Pipeline Cheatsheet - -This is the quick reference for translating DCC camera exports (Nuke / Houdini) into something Wan understands. - -### Mental model - -**Nuke / Houdini (Scanline, Karma, etc.)** -- Camera input: native camera node from the scene graph -- Coordinate frame: whatever the scene uses -- Intrinsics: stored on the camera node (filmback + focal) -- Translation: camera center `C` in scene units - -**Wan video pipeline** -- Camera input: RealEstate10k TXT (`timestamp fx fy cx cy width height R\|t`) -- Coordinate frame: fixed OpenCV axes (+X right, +Y down, +Z forward) -- Intrinsics: requires normalized values (`fx = f_mm / sensor_w_mm`) -- Translation: needs **world→camera** translation `t = –R · C` in meters - -Wan was trained on RealEstate10k, so its camera encoder only understands that schema. Anything else (raw ASCI, FBX, etc.) must be converted first. - -## Why the converter is mandatory - -1. **Different math objects** - - **Nuke’s ASCI** stores the camera center `(Tx, Ty, Tz)` plus ZXY Euler angles `(Rx, Ry, Rz)` in degrees. It tells you where the camera sits in world space and which way it’s pointing. - - **Wan / RealEstate** wants the inverse: a 3×4 matrix `[R|t]` that transforms any world point into the camera’s local space. In other words, “rotate + shift the world so the camera becomes the origin.” - - To bridge the two, we: - 1. Convert the Euler angles to a rotation matrix `R` (standard Z→X→Y order). - 2. Turn the camera center `C` into the translation part of the inverse matrix via `t = -R · C`. - The path doesn’t change—we simply express the same motion from the camera’s frame of reference so Wan’s math matches Nuke’s. -2. **Coordinate transforms** - - Nuke: +X right, +Y up, +Z forward. - - RealEstate: +X right, +Y down, +Z forward (OpenCV). - - Wan’s visualizer applies another transform for Matplotlib’s +Y-up space. - The converter reorients the axes so Wan’s `ray_condition()` can produce sensible rays. -3. **Units** - - Wan assumes translations are in meters. - - Nuke scenes often live in centimeters or abstract units, so we apply `unit_scale = real_distance / asci_delta` before writing the TXT. - -## Practical workflow - -1. **Export Nuke camera as ASCI** (columns `[Tx Rx Ty Ry Rz Tz]`). -2. **Measure real-world scale** so you can compute `unit_scale`. - Example: camera dolly moves 3.5 m, ASCI `Tz` delta is `0.11` ⇒ `unit_scale = 3.5 / 0.11 = 31.82`. -3. **Fill `configs/camera_config_*.json`** with: - - Input/output paths - - Lens data (focal + filmback) → converter derives `fx/fy/cx/cy` - - Render resolution placeholders - - `unit_scale` and optional frame caps -4. **Run** `python nuke_ASCI-2-Pose_converter.py --config configs/.json` -5. **Load TXT in ComfyUI** via `Koolook Load Camera Poses` → `WanVideoFunCameraEmbeds`. - -Following this flow keeps the rendered motion aligned with the original plate, even though the raw numbers look different after conversion.*** - -## Where things happen inside the ComfyUI graph - -1. **`Text Multiline` → `Koolook Load Camera Poses`** - - You paste the RealEstate10k TXT path. - - Koolook parses the header plus each row and returns `CAMERACTRL_POSES` (the same intrinsics + `[R|t]` data we exported). - -2. **`WanVideo FunCamera Embeds`** - This node performs all the heavy lifting described in the converter: - - `process_poses()` rescales intrinsics to match the render width/height. - - `get_relative_pose()` turns each `[R|t]` row into camera-to-world matrices. - - `ray_condition()` converts those matrices into Plücker rays (origin + direction per pixel), the representation the Wan motion model was trained on. - - The tensor is reshaped and averaged to match Wan’s token budget (21 camera tokens per chunk) and the VAE stride. - - The result is a dictionary: - ```python - { - "target_shape": (channels, t, h/8, w/8), - "num_frames": len(poses), - "control_embeds": { - "control_camera_latents": …, - "control_camera_start_percent": …, - "control_camera_end_percent": …, - ... - } - } - ``` - - This payload is what the node outputs on `image_embeds`. - -3. **`WanVideo ImageToVideo Encode`** - - Takes VAE/CLIP/start/end latents plus the `image_embeds` dict. - - Reads `control_embeds["control_camera_latents"]` and injects those camera rays into the diffusion network so every generated frame follows the exact path from Nuke. - - `target_shape` and `num_frames` keep the camera tokens aligned with the render resolution and animation length. - -So all the conversions (intrinsics, `t = –R·C`, Plücker rays, token packing) happen before the encoder ever runs; by the time `image_embeds` hits Wan, it’s a set of “camera control instructions” telling the model where to point each ray for every frame.*** - -## Why Wan still drifts relative to the original plate - -Even with perfect pose conversion, Wan isn’t a deterministic renderer—it’s still a diffusion model inventing geometry on the fly. Keep these limitations in mind: - -- **Diffusion ≠ projection.** Wan reconstructs a scene that *statistically* fits the camera rays; it doesn’t ray-trace your tracked mesh, so pixels can land a little off compared to a Nuke render. -- **No shared 3D points.** Your tracked points in Nuke are actual world markers. Wan fabricates its own depth, so there’s nothing forcing it to align to those anchors. -- **Token downsampling.** Camera embeddings get compressed (Plücker rays → 21 tokens → VAE stride). That’s enough to guide global motion but not sub-pixel parallax. -- **Scale sensitivity.** Any tiny error in focal length, sensor size, or `unit_scale` shows up as sliding when you composite back with live-action elements. - -### Tips when you need zero sliding - -1. **Lock scale & intrinsics.** Double-check the measurement and lens metadata; millimeter accuracy matters. -2. **Treat Wan as a background pass.** Keep your foreground in Nuke and use Wan only for the new environment, then composite the two with projections/cards to fix residual offsets. -3. **Use camera projections for critical geometry.** Project the plate (or a painted matte) onto simple geo driven by your tracked camera, and blend that with Wan’s output. -4. **Post-warp if needed.** If the drift is small, track Wan’s render and apply a 2D/3D match-move or morph to align with the original plate. - -Wan’s camera control ensures the *motion* matches; matching the *exact* pixel positions still requires traditional VFX techniques when you need perfectly locked foreground/background interaction.*** - -## Getting `unit_scale` and lens data dialed in - -The most common reason for visible drift is a scale mismatch. Because Wan assumes the translations in `[R|t]` are in meters, a small error gets amplified—especially on wide lenses where foreground points travel further per degree of rotation. - -### Why it matters - -- **Focal length & filmback set the field of view.** If the RealEstate TXT says `fx = 33 mm / 36 mm = 0.916` but the real lens was 31 mm, Wan thinks the camera is *slightly tighter*. To match the same screen-space travel, it pushes the scene farther away, and your foreground doesn’t line up. -- **`unit_scale` scales the entire translation vector.** Doubling `unit_scale` doubles the apparent dolly move. On a 24 mm lens a 5 cm error is huge; on a 135 mm it’s barely noticeable. - -### How to measure accurately - -1. **Grab real production data whenever possible.** Use the on-set focal length and documented dolly track distance. Enter those directly into the config. -2. **Use Nuke’s reference tools.** In CameraTracker you can “Set Reference Distance” between two tracked points. If you tell it “this wall is exactly 4.2 m apart,” the entire camera solves into meters and `unit_scale` becomes 1.0. -3. **Manual measurement workflow:** - - Pick two frames (start/end) and read the raw ASCI translation delta (e.g., Tz changes by 0.11842). - Formula: - ``` - unit_scale = real_world_distance_meters / abs(asci_delta) - ``` - Example: camera travels 6.30 m, ASCI delta is 0.1891 ⇒ `unit_scale = 6.30 / 0.1891 = 33.31`. - - Measure the real-world distance for that move (tape measure on set, LiDAR scan, or deduced from perspective grids). - - Compute the ratio using the formula above. Keep at least 3 significant decimals. - - Verify by projecting a checkerboard onto set geometry: if it still drifts, adjust by the measured percent difference. -4. **Wide lenses need tighter tolerances.** For anything 35 mm or wider, aim for centimeter-level accuracy. Every 1% error in scale equals ~1% sliding on screen. - -Document the measurement in the config file (e.g. `"reference_distance_m": 6.53`). That way anyone regenerating the poses knows where the number came from and can double-check if the set changes.*** \ No newline at end of file diff --git a/nuke_CAM_exporter/inputs/nukeCam_unit-1_onlyZoom.asci b/nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_onlyZoom.asci similarity index 100% rename from nuke_CAM_exporter/inputs/nukeCam_unit-1_onlyZoom.asci rename to nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_onlyZoom.asci diff --git a/nuke_CAM_exporter/inputs/nukeCam_unit-1_wRotation.asci b/nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_wRotation.asci similarity index 100% rename from nuke_CAM_exporter/inputs/nukeCam_unit-1_wRotation.asci rename to nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_wRotation.asci diff --git a/nuke_CAM_exporter/inputs/nukeCam_unit-1_wRotation_v2.asci b/nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_wRotation_v2.asci similarity index 100% rename from nuke_CAM_exporter/inputs/nukeCam_unit-1_wRotation_v2.asci rename to nuke_CAM_exporter/inputs/_BK/nukeCam_unit-1_wRotation_v2.asci diff --git a/nuke_CAM_exporter/outputs/nukeCam_unit-1_2-files.txt b/nuke_CAM_exporter/outputs/nukeCam_unit-1_2-files.txt new file mode 100644 index 0000000..c3d8302 --- /dev/null +++ b/nuke_CAM_exporter/outputs/nukeCam_unit-1_2-files.txt @@ -0,0 +1,81 @@ +source:translation:configs/../inputs/nukeCam_unit-1-split_tr.asci rotation:configs/../inputs/nukeCam_unit-1-split_rot.asci fps:25.0 fx:0.9722222222222222 fy:1.4583333333333333 cx:0.5 cy:0.5 width:1920 height:1080 unit_scale:1 +0.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 1.000000000000 -0.000000000000 0.000000000000 -0.000000000000 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 0.000000000000 -0.000000000000 1.000000000000 -0.500000000000 +40000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999997620177 -0.000000000000 0.002181659834 -0.006063547170 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.002181659834 -0.000000000000 0.999997620177 -0.487487931338 +80000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999990480721 -0.000000000000 0.004363309285 -0.012072476516 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.004363309285 -0.000000000000 0.999990480721 -0.474951844950 +120000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999978581664 -0.000000000000 0.006544937967 -0.018026712232 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.006544937967 -0.000000000000 0.999978581664 -0.462391919452 +160000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999961923064 -0.000000000000 0.008726535498 -0.023926179030 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.008726535498 -0.000000000000 0.999961923064 -0.449808334072 +200000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999940505000 -0.000000000000 0.010908091494 -0.029770802145 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.010908091494 -0.000000000000 0.999940505000 -0.437201267856 +240000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999914327574 -0.000000000000 0.013089595571 -0.035560507433 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.013089595571 -0.000000000000 0.999914327574 -0.424570900458 +280000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999883390910 -0.000000000000 0.015271037347 -0.041295220972 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.015271037347 -0.000000000000 0.999883390910 -0.411917411453 +320000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999847695156 -0.000000000000 0.017452406437 -0.046974869762 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.017452406437 -0.000000000000 0.999847695156 -0.399240980716 +360000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999807240482 -0.000000000000 0.019633692461 -0.052599380927 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.019633692461 -0.000000000000 0.999807240482 -0.386541788340 +400000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999762027080 -0.000000000000 0.021814885035 -0.058168682516 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.021814885035 -0.000000000000 0.999762027080 -0.373820014719 +440000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999712055165 -0.000000000000 0.023995973777 -0.063682702697 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.023995973777 -0.000000000000 0.999712055165 -0.361075840159 +480000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999657324976 -0.000000000000 0.026176948308 -0.069141370573 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.026176948308 -0.000000000000 0.999657324976 -0.348309445564 +520000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999597836772 -0.000000000000 0.028357798245 -0.074544615459 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.028357798245 -0.000000000000 0.999597836772 -0.335521011648 +560000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999533590837 -0.000000000000 0.030538513210 -0.079892367309 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.030538513210 -0.000000000000 0.999533590837 -0.322710719722 +600000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999464587476 -0.000000000000 0.032719082822 -0.085184556497 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.032719082822 -0.000000000000 0.999464587476 -0.309878751005 +640000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999390827019 -0.000000000000 0.034899496703 -0.090421114224 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.034899496703 -0.000000000000 0.999390827019 -0.297025287002 +680000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999312309816 -0.000000000000 0.037079744474 -0.095601971919 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.037079744474 -0.000000000000 0.999312309816 -0.284150509426 +720000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999229036241 -0.000000000000 0.039259815759 -0.100727061641 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.039259815759 -0.000000000000 0.999229036241 -0.271254600184 +760000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999141006690 -0.000000000000 0.041439700181 -0.105796316076 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.041439700181 -0.000000000000 0.999141006690 -0.258337741373 +800000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.999048221582 -0.000000000000 0.043619387365 -0.110809668139 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.043619387365 -0.000000000000 0.999048221582 -0.245400115295 +840000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998950681359 -0.000000000000 0.045798866937 -0.115767051577 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.045798866937 -0.000000000000 0.998950681359 -0.232441904433 +880000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998848386485 -0.000000000000 0.047978128521 -0.120668400569 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.047978128521 -0.000000000000 0.998848386485 -0.219463291562 +920000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998741337447 -0.000000000000 0.050157161747 -0.125513649814 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.050157161747 -0.000000000000 0.998741337447 -0.206464459348 +960000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998629534755 -0.000000000000 0.052335956243 -0.130302734552 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.052335956243 -0.000000000000 0.998629534755 -0.193445590945 +1000000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998512978940 -0.000000000000 0.054514501638 -0.135035590660 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.054514501638 -0.000000000000 0.998512978940 -0.180406869686 +1040000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998391670557 -0.000000000000 0.056692787563 -0.139712154235 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.056692787563 -0.000000000000 0.998391670557 -0.167348478906 +1080000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998265610185 -0.000000000000 0.058870803651 -0.144332362305 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.058870803651 -0.000000000000 0.998265610185 -0.154270602099 +1120000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.998134798422 -0.000000000000 0.061048539535 -0.148896152049 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.061048539535 -0.000000000000 0.998134798422 -0.141173423267 +1160000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997999235891 -0.000000000000 0.063225984849 -0.153403461465 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.063225984849 -0.000000000000 0.997999235891 -0.128057126272 +1200000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997858923239 -0.000000000000 0.065403129230 -0.157854229082 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.065403129230 -0.000000000000 0.997858923239 -0.114921895155 +1240000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997713861131 -0.000000000000 0.067579962315 -0.162248393683 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.067579962315 -0.000000000000 0.997713861131 -0.101767914454 +1280000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997564050260 -0.000000000000 0.069756473744 -0.166585894968 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.069756473744 -0.000000000000 0.997564050260 -0.088595368557 +1320000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997409491337 -0.000000000000 0.071932653157 -0.170866672780 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.071932653157 -0.000000000000 0.997409491337 -0.075404442151 +1360000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997250185099 -0.000000000000 0.074108490195 -0.175090667888 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.074108490195 -0.000000000000 0.997250185099 -0.062195319973 +1400000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.997086132304 -0.000000000000 0.076283974504 -0.179257821223 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.076283974504 -0.000000000000 0.997086132304 -0.048968187255 +1440000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996917333733 -0.000000000000 0.078459095728 -0.183368074628 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.078459095728 -0.000000000000 0.996917333733 -0.035723229073 +1480000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996743790189 -0.000000000000 0.080633843514 -0.187421370093 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.080633843514 -0.000000000000 0.996743790189 -0.022460630799 +1520000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996565502498 -0.000000000000 0.082808207512 -0.191417650546 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.082808207512 -0.000000000000 0.996565502498 -0.009180577944 +1560000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996382471508 -0.000000000000 0.084982177372 -0.195356859053 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.084982177372 -0.000000000000 0.996382471508 0.004116743785 +1600000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996194698092 -0.000000000000 0.087155742748 -0.199238939618 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.087155742748 -0.000000000000 0.996194698092 0.017431148550 +1640000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.996002183142 -0.000000000000 0.089328893292 -0.203063836486 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.089328893292 -0.000000000000 0.996002183142 0.030762450324 +1680000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.995804927575 -0.000000000000 0.091501618663 -0.206831494442 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.091501618663 -0.000000000000 0.995804927575 0.044110462919 +1720000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.995602932329 -0.000000000000 0.093673908519 -0.210541859109 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.093673908519 -0.000000000000 0.995602932329 0.057475000014 +1760000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.995396198367 -0.000000000000 0.095845752520 -0.214194876252 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.095845752520 -0.000000000000 0.995396198367 0.070855875094 +1800000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.995184726672 -0.000000000000 0.098017140330 -0.217790492578 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.098017140330 -0.000000000000 0.995184726672 0.084252901523 +1840000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.994968518251 -0.000000000000 0.100188061612 -0.221328654935 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.100188061612 -0.000000000000 0.994968518251 0.097665892473 +1880000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.994747574132 -0.000000000000 0.102358506035 -0.224809311102 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.102358506035 -0.000000000000 0.994747574132 0.111094661099 +1920000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.994521895368 -0.000000000000 0.104528463268 -0.228232409033 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.104528463268 -0.000000000000 0.994521895368 0.124539020067 +1960000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.994291483033 -0.000000000000 0.106697922982 -0.231597897590 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.106697922982 -0.000000000000 0.994291483033 0.137998782230 +2000000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.994056338222 -0.000000000000 0.108866874852 -0.234905725794 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.108866874852 -0.000000000000 0.994056338222 0.151473760151 +2040000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.993816462056 -0.000000000000 0.111035308554 -0.238155843594 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.111035308554 -0.000000000000 0.993816462056 0.164963766386 +2080000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.993571855677 -0.000000000000 0.113203213768 -0.241348201120 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.113203213768 -0.000000000000 0.993571855677 0.178468613005 +2120000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.993322520247 -0.000000000000 0.115370580174 -0.244482749310 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.115370580174 -0.000000000000 0.993322520247 0.191988112263 +2160000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.993068456955 -0.000000000000 0.117537397458 -0.247559439647 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.117537397458 -0.000000000000 0.993068456955 0.205522076271 +2200000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.992809667009 -0.000000000000 0.119703655305 -0.250578223896 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.119703655305 -0.000000000000 0.992809667009 0.219070316665 +2240000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.992546151641 -0.000000000000 0.121869343405 -0.253539054731 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.121869343405 -0.000000000000 0.992546151641 0.232632645289 +2280000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.992277912106 -0.000000000000 0.124034451450 -0.256441884972 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.124034451450 -0.000000000000 0.992277912106 0.246208873795 +2320000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.992004949680 -0.000000000000 0.126198969136 -0.259286668309 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.126198969136 -0.000000000000 0.992004949680 0.259798813539 +2360000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.991727265662 -0.000000000000 0.128362886159 -0.262073358881 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.128362886159 -0.000000000000 0.991727265662 0.273402275726 +2400000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.991444861374 -0.000000000000 0.130526192220 -0.264801911333 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.130526192220 -0.000000000000 0.991444861374 0.287019071726 +2440000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.991157738160 -0.000000000000 0.132688877023 -0.267472280998 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.132688877023 -0.000000000000 0.991157738160 0.300649012491 +2480000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.990865897387 -0.000000000000 0.134850930274 -0.270084423445 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.134850930274 -0.000000000000 0.990865897387 0.314291908900 +2520000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.990569340444 -0.000000000000 0.137012341682 -0.272638295089 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.137012341682 -0.000000000000 0.990569340444 0.327947571744 +2560000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.990268068742 -0.000000000000 0.139173100960 -0.275133852795 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.139173100960 -0.000000000000 0.990268068742 0.341615811669 +2600000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.989962083715 -0.000000000000 0.141333197824 -0.277571053976 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.141333197824 -0.000000000000 0.989962083715 0.355296439195 +2640000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.989651386820 -0.000000000000 0.143492621991 -0.279949856496 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.143492621991 -0.000000000000 0.989651386820 0.368989264703 +2680000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.989335979535 -0.000000000000 0.145651363185 -0.282270219065 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.145651363185 -0.000000000000 0.989335979535 0.382694098491 +2720000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.989015863362 -0.000000000000 0.147809411130 -0.284532100631 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.147809411130 -0.000000000000 0.989015863362 0.396410750793 +2760000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.988691039824 -0.000000000000 0.149966755554 -0.286735460837 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.149966755554 -0.000000000000 0.988691039824 0.410139031437 +2800000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.988361510468 -0.000000000000 0.152123386190 -0.288880259717 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.152123386190 -0.000000000000 0.988361510468 0.423878750512 +2840000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.988027276861 -0.000000000000 0.154279292772 -0.290966458213 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.154279292772 -0.000000000000 0.988027276861 0.437629717640 +2880000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.987688340595 -0.000000000000 0.156434465040 -0.292994017363 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.156434465040 -0.000000000000 0.987688340595 0.451391742660 +2920000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.987344703283 -0.000000000000 0.158588892735 -0.294962899196 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.158588892735 -0.000000000000 0.987344703283 0.465164635060 +2960000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.986996366560 -0.000000000000 0.160742565604 -0.296873065884 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.160742565604 -0.000000000000 0.986996366560 0.478948204254 +3000000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.986643332085 -0.000000000000 0.162895473395 -0.298724480545 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.162895473395 -0.000000000000 0.986643332085 0.492742259602 +3040000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.986285601537 -0.000000000000 0.165047605861 -0.300517106440 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.165047605861 -0.000000000000 0.986285601537 0.506546610393 +3080000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.985923176620 -0.000000000000 0.167198952759 -0.302250907727 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.167198952759 -0.000000000000 0.985923176620 0.520361065556 +3120000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.985556059058 -0.000000000000 0.169349503849 -0.303925848952 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.169349503849 -0.000000000000 0.985556059058 0.534185434292 +3160000.000000000000 0.972222222222 1.458333333333 0.500000000000 0.500000000000 1920.000000000000 1080.000000000000 0.985184250599 -0.000000000000 0.171499248896 -0.305541895283 -0.000000000000 1.000000000000 -0.000000000000 -0.500000000000 -0.171499248896 -0.000000000000 0.985184250599 0.548019525301 diff --git a/nuke_CAM_exporter/testing_tools/check_mapping.py b/nuke_CAM_exporter/testing_tools/check_mapping.py deleted file mode 100644 index 865de92..0000000 --- a/nuke_CAM_exporter/testing_tools/check_mapping.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -import math -from pathlib import Path -from typing import Sequence - -ASCI_PATH = Path(__file__).resolve().parents[1] / "inputs" / "nukeCam_unit-1_v2.asci" -POSE_PATH = Path(__file__).resolve().parents[1] / "outputs" / "nukeCam_unit-1_v4_converted.txt" - - -def mat_mul(a: Sequence[Sequence[float]], b: Sequence[Sequence[float]]) -> list[list[float]]: - return [ - [a[r][0] * b[0][c] + a[r][1] * b[1][c] + a[r][2] * b[2][c] for c in range(3)] - for r in range(3) - ] - - -def euler_zxy(rx: float, ry: float, rz: float) -> list[list[float]]: - sx, cx = math.sin(rx), math.cos(rx) - sy, cy = math.sin(ry), math.cos(ry) - sz, cz = math.sin(rz), math.cos(rz) - - Rx = ( - (1.0, 0.0, 0.0), - (0.0, cx, -sx), - (0.0, sx, cx), - ) - Ry = ( - (cy, 0.0, sy), - (0.0, 1.0, 0.0), - (-sy, 0.0, cy), - ) - Rz = ( - (cz, -sz, 0.0), - (sz, cz, 0.0), - (0.0, 0.0, 1.0), - ) - - return mat_mul(Ry, mat_mul(Rx, Rz)) - - -def convert_yup_to_ydown(R: list[list[float]]) -> list[list[float]]: - # T = diag(1, -1, 1) - return [ - [R[0][0], -R[0][1], R[0][2]], - [-R[1][0], R[1][1], -R[1][2]], - [R[2][0], -R[2][1], R[2][2]], - ] - - -def mat_vec_mul(R: Sequence[Sequence[float]], v: Sequence[float]) -> list[float]: - return [ - R[0][0] * v[0] + R[0][1] * v[1] + R[0][2] * v[2], - R[1][0] * v[0] + R[1][1] * v[1] + R[1][2] * v[2], - R[2][0] * v[0] + R[2][1] * v[1] + R[2][2] * v[2], - ] - - -def read_rows(path: Path) -> list[list[float]]: - rows: list[list[float]] = [] - for line in path.read_text().splitlines(): - stripped = line.strip() - if not stripped: - continue - # Skip header/metadata lines (e.g. "source:…") that are not numeric. - first = stripped[0] - if first not in "0123456789-.": - continue - rows.append(list(map(float, stripped.split()))) - return rows - - -def compare_rows(indices: list[int]) -> None: - asci_rows = read_rows(ASCI_PATH) - pose_rows = read_rows(POSE_PATH) - - print(f"Frames in ASCI: {len(asci_rows)}") - print(f"Frames in TXT: {len(pose_rows)}") - - for idx in indices: - if idx >= len(pose_rows): - continue - tx, rx, ty, ry, tz, rz = asci_rows[idx] - R_nuke = euler_zxy(math.radians(rx), math.radians(ry), math.radians(rz)) - R_cv = convert_yup_to_ydown(R_nuke) - t = [-val for val in mat_vec_mul(R_cv, (tx, ty, tz))] - - pose_vals = pose_rows[idx] - R_export = [ - pose_vals[7:11], - pose_vals[11:15], - pose_vals[15:19], - ] - - rot_diff = max(abs(R_export[r][c] - R_cv[r][c]) for r in range(3) for c in range(3)) - - print(f"\nFrame {idx}") - print("Nuke Tx Ty Tz :", (tx, ty, tz)) - print("Export t :", tuple(round(pose_vals[10 + i * 4], 6) for i in range(3))) - print("Expected t :", tuple(round(val, 6) for val in t)) - print("Rotation diff :", rot_diff) - print("R_nuke→OpenCV :") - for row in R_cv: - print(" ", " ".join(f"{val: .6f}" for val in row)) - print("R_export :") - for row in R_export: - print(" ", " ".join(f"{val: .6f}" for val in row)) - - -if __name__ == "__main__": - compare_rows([0, 79]) - diff --git a/nuke_CAM_exporter/testing_tools/debug_rotation.py b/nuke_CAM_exporter/testing_tools/debug_rotation.py deleted file mode 100644 index 0f170d1..0000000 --- a/nuke_CAM_exporter/testing_tools/debug_rotation.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug tool to analyze camera rotations and verify coordinate system conversion. - -This helps identify if a 90° Y-axis rotation (or any rotation) is being handled -correctly by the converter. -""" - -import argparse -import math -import numpy as np -from pathlib import Path -from typing import Tuple - - -def euler_zxy_to_matrix(rx: float, ry: float, rz: float) -> np.ndarray: - """Convert ZXY Euler angles to rotation matrix (Nuke's system).""" - sx, cx = math.sin(rx), math.cos(rx) - sy, cy = math.sin(ry), math.cos(ry) - sz, cz = math.sin(rz), math.cos(rz) - - Rx = np.array([[1.0, 0.0, 0.0], [0.0, cx, -sx], [0.0, sx, cx]]) - Ry = np.array([[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]]) - Rz = np.array([[cz, -sz, 0.0], [sz, cz, 0.0], [0.0, 0.0, 1.0]]) - - return Ry @ Rx @ Rz - - -def convert_yup_to_ydown(rotation: np.ndarray) -> np.ndarray: - """Convert from Nuke Y-up to OpenCV Y-down coordinate system.""" - r = rotation - return np.array([ - [r[0, 0], -r[0, 1], r[0, 2]], - [-r[1, 0], r[1, 1], -r[1, 2]], - [r[2, 0], -r[2, 1], r[2, 2]], - ]) - - -def rotation_to_euler_xyz(R: np.ndarray) -> Tuple[float, float, float]: - """Extract XYZ Euler angles from rotation matrix (for debugging).""" - # Extract angles (this is approximate, works for most cases) - sy = -R[2, 0] - cy = math.sqrt(max(0, 1 - sy * sy)) - - if abs(cy) > 1e-6: - sx = R[2, 1] / cy - cx = R[2, 2] / cy - sz = R[1, 0] / cy - cz = R[0, 0] / cy - else: - # Gimbal lock case - sx = R[1, 2] - cx = R[1, 1] - sz = 0.0 - cz = 1.0 - - return math.atan2(sx, cx), math.atan2(sy, cy), math.atan2(sz, cz) - - -def analyze_asci_rotations(path: Path, num_frames: int = 5) -> None: - """Analyze rotations in ASCI file to identify patterns.""" - print(f"Analyzing rotations in: {path}") - print("=" * 80) - - lines = [l.strip() for l in path.read_text().splitlines() if l.strip()] - num_frames = min(num_frames, len(lines)) - - print(f"\nFirst {num_frames} frames:") - print("-" * 80) - - for i in range(num_frames): - values = lines[i].split() - if len(values) != 6: - continue - - tx, rx_deg, ty, ry_deg, rz_deg, tz = [float(v) for v in values] - - print(f"\nFrame {i+1}:") - print(f" Translation: ({tx:.3f}, {ty:.3f}, {tz:.3f})") - print(f" Rotation (degrees): X={rx_deg:.2f}°, Y={ry_deg:.2f}°, Z={rz_deg:.2f}°") - - # Convert to rotation matrix - rx, ry, rz = math.radians(rx_deg), math.radians(ry_deg), math.radians(rz_deg) - R_nuke = euler_zxy_to_matrix(rx, ry, rz) - - # Check if Y rotation is near 90° - if abs(ry_deg - 90.0) < 5.0 or abs(ry_deg - (-90.0)) < 5.0: - print(f" ⚠️ WARNING: Y rotation is near ±90° ({ry_deg:.2f}°)") - print(f" This might indicate a coordinate system orientation issue") - - # Convert to OpenCV coordinate system - R_opencv = convert_yup_to_ydown(R_nuke) - - # Verify rotation matrix properties - det_nuke = np.linalg.det(R_nuke) - det_opencv = np.linalg.det(R_opencv) - - print(f" Rotation matrix determinant: Nuke={det_nuke:.6f}, OpenCV={det_opencv:.6f}") - - # Check forward direction (Z axis in camera space) - forward_nuke = R_nuke[:, 2] # Third column - forward_opencv = R_opencv[:, 2] - - print(f" Forward direction (Nuke): ({forward_nuke[0]:.3f}, {forward_nuke[1]:.3f}, {forward_nuke[2]:.3f})") - print(f" Forward direction (OpenCV): ({forward_opencv[0]:.3f}, {forward_opencv[1]:.3f}, {forward_opencv[2]:.3f})") - - # Check if coordinate conversion preserved the rotation correctly - # The forward direction should point in the same physical direction - # (just expressed in different coordinate systems) - dot_product = np.dot(forward_nuke, forward_opencv) - if abs(dot_product - 1.0) < 0.01: - print(f" ✓ Coordinate conversion preserves forward direction") - else: - print(f" ⚠️ Forward directions differ (dot={dot_product:.6f})") - - print("\n" + "=" * 80) - print("\nInterpretation:") - print("- If Y rotation is consistently near ±90°, this might indicate:") - print(" 1. Camera solver oriented the scene differently (this is OK)") - print(" 2. Coordinate system mismatch in the solver (check Nuke settings)") - print("- The converter should preserve all rotations correctly") - print("- If motion doesn't match, check unit_scale and lens parameters") - - -def test_90_degree_rotation() -> None: - """Test if 90° Y rotation is handled correctly.""" - print("\n" + "=" * 80) - print("Testing 90° Y-axis rotation conversion:") - print("-" * 80) - - # Test case: 90° rotation around Y axis - ry_90 = math.radians(90.0) - R_nuke = euler_zxy_to_matrix(0.0, ry_90, 0.0) - - print(f"\nNuke rotation matrix (90° Y):") - print(R_nuke) - - R_opencv = convert_yup_to_ydown(R_nuke) - - print(f"\nOpenCV rotation matrix (after conversion):") - print(R_opencv) - - # Verify properties - det_nuke = np.linalg.det(R_nuke) - det_opencv = np.linalg.det(R_opencv) - - print(f"\nDeterminants: Nuke={det_nuke:.6f}, OpenCV={det_opencv:.6f}") - - # Test forward direction - forward_nuke = R_nuke[:, 2] - forward_opencv = R_opencv[:, 2] - - print(f"\nForward direction:") - print(f" Nuke: ({forward_nuke[0]:.3f}, {forward_nuke[1]:.3f}, {forward_nuke[2]:.3f})") - print(f" OpenCV: ({forward_opencv[0]:.3f}, {forward_opencv[1]:.3f}, {forward_opencv[2]:.3f})") - - # In Nuke: +Z forward, so 90° Y rotation should point +X - # After conversion, should still point in same physical direction - print(f"\n✓ 90° Y rotation conversion test complete") - - -def main(): - parser = argparse.ArgumentParser(description="Debug camera rotations") - parser.add_argument("asci", type=Path, help="ASCI file to analyze") - parser.add_argument("--frames", type=int, default=5, help="Number of frames to analyze") - parser.add_argument("--test-90deg", action="store_true", help="Run 90° rotation test") - args = parser.parse_args() - - if args.test_90deg: - test_90_degree_rotation() - - if args.asci.exists(): - analyze_asci_rotations(args.asci, args.frames) - else: - print(f"Error: {args.asci} not found") - - -if __name__ == "__main__": - main() - diff --git a/nuke_CAM_exporter/testing_tools/pose_diagnostics.py b/nuke_CAM_exporter/testing_tools/pose_diagnostics.py new file mode 100644 index 0000000..bc6d7c7 --- /dev/null +++ b/nuke_CAM_exporter/testing_tools/pose_diagnostics.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 +""" +Pose diagnostics CLI. + +Replaces the previous individual helpers (check_mapping, validate_conversion, +debug_rotation) with a single entry point that exposes three subcommands: + + - validate: compare ASCI vs. converted TXT in detail. + - check: probe specific frames for translation/rotation parity. + - rotations:inspect raw ASCI rotations or run a 90° sanity test. +""" + +from __future__ import annotations + +import argparse +import math +from pathlib import Path +from typing import Iterable, List, Sequence, Tuple + +import numpy as np + + +# --------------------------------------------------------------------------- +# Shared math helpers +# --------------------------------------------------------------------------- + +def euler_zxy_to_matrix(rx: float, ry: float, rz: float) -> List[List[float]]: + sx, cx = math.sin(rx), math.cos(rx) + sy, cy = math.sin(ry), math.cos(ry) + sz, cz = math.sin(rz), math.cos(rz) + + Rx = [[1.0, 0.0, 0.0], [0.0, cx, -sx], [0.0, sx, cx]] + Ry = [[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]] + Rz = [[cz, -sz, 0.0], [sz, cz, 0.0], [0.0, 0.0, 1.0]] + + return (np.dot(np.dot(Ry, Rx), Rz)).tolist() + + +def convert_yup_to_ydown(rotation: Sequence[Sequence[float]]) -> List[List[float]]: + r = rotation + return [ + [r[0][0], -r[0][1], r[0][2]], + [-r[1][0], r[1][1], -r[1][2]], + [r[2][0], -r[2][1], r[2][2]], + ] + + +def mat_vec_mul(R: Sequence[Sequence[float]], v: Sequence[float]) -> List[float]: + return [ + R[0][0] * v[0] + R[0][1] * v[1] + R[0][2] * v[2], + R[1][0] * v[0] + R[1][1] * v[1] + R[1][2] * v[2], + R[2][0] * v[0] + R[2][1] * v[1] + R[2][2] * v[2], + ] + + +def read_numeric_rows(path: Path) -> List[List[float]]: + rows: List[List[float]] = [] + for line in path.read_text().splitlines(): + stripped = line.strip() + if not stripped: + continue + if stripped[0] not in "0123456789-.": + continue + rows.append(list(map(float, stripped.split()))) + return rows + + +# --------------------------------------------------------------------------- +# validate command +# --------------------------------------------------------------------------- + +def parse_pose_line(line: str) -> Tuple[float, List[float], List[List[float]]]: + values = line.strip().split() + if len(values) != 19: + raise ValueError(f"Expected 19 columns, got {len(values)}") + timestamp = float(values[0]) + intrinsics = [float(v) for v in values[1:7]] + matrix_values = [float(v) for v in values[7:19]] + R = [ + matrix_values[0:4], + matrix_values[4:8], + matrix_values[8:12], + ] + return timestamp, intrinsics, R + + +def validate_rotation_matrix(R: Sequence[Sequence[float]], name: str) -> List[str]: + issues: List[str] = [] + R_np = np.array(R) + det = np.linalg.det(R_np) + if abs(det - 1.0) > 0.01: + issues.append(f"{name}: determinant {det:.6f} (expected 1.0)") + should_be_identity = np.dot(R_np, R_np.T) + identity = np.eye(3) + max_diff = np.abs(should_be_identity - identity).max() + if max_diff > 0.01: + issues.append(f"{name}: not orthogonal (max diff {max_diff:.6f})") + return issues + + +def cmd_validate(args: argparse.Namespace) -> None: + asci_path = args.asci + pose_path = args.pose + unit_scale = args.unit_scale + + print(f"Reading ASCI: {asci_path}") + print(f"Reading poses: {pose_path}") + print(f"Using unit_scale: {unit_scale}") + print() + + asci_lines = [l for l in asci_path.read_text().splitlines() if l.strip()] + pose_lines = pose_path.read_text().splitlines() + header = pose_lines[0] if pose_lines else "" + pose_data = [parse_pose_line(l) for l in pose_lines[1:] if l.strip()] + + print(f"Found {len(asci_lines)} frames in ASCI") + print(f"Found {len(pose_data)} frames in pose file") + print(f"Header: {header}") + if len(asci_lines) != len(pose_data): + print( + f"WARNING: Frame count mismatch! ASCI: {len(asci_lines)}, Poses: {len(pose_data)}" + ) + print() + + num_check = min(args.frames, len(asci_lines), len(pose_data)) + print(f"Detailed comparison of first {num_check} frames:") + print("-" * 80) + + all_issues: List[str] = [] + for i in range(num_check): + tx, rx_deg, ty, ry_deg, tz, rz_deg = map(float, asci_lines[i].split()) + rx, ry, rz = math.radians(rx_deg), math.radians(ry_deg), math.radians(rz_deg) + + print(f"\nFrame {i}:") + print(f" ASCI: T=({tx:.3f}, {ty:.3f}, {tz:.3f}), R=({rx_deg:.2f}°, {ry_deg:.2f}°, {rz_deg:.2f}°)") + + R_asci = euler_zxy_to_matrix(rx, ry, rz) + R_pose = [row[:3] for row in pose_data[i][2]] + t_pose = [row[3] for row in pose_data[i][2]] + print(f" Pose t: ({t_pose[0]:.6f}, {t_pose[1]:.6f}, {t_pose[2]:.6f})") + + all_issues.extend(validate_rotation_matrix(R_asci, f"ASCI_R_{i}")) + all_issues.extend(validate_rotation_matrix(R_pose, f"Pose_R_{i}")) + + # Translation check + camera_center_scaled = np.array([tx * unit_scale, ty * unit_scale, tz * unit_scale]) + expected_t = -np.array(R_pose) @ camera_center_scaled + t_diff = np.abs(np.array(t_pose) - expected_t) + max_t_diff = t_diff.max() + if max_t_diff > 0.01: + print(f" WARNING: translation mismatch (max diff {max_t_diff:.6f})") + all_issues.append(f"Frame {i}: translation mismatch ({max_t_diff:.6f})") + else: + print(f" ✓ translation matches (max diff {max_t_diff:.6f})") + + print("\n" + "=" * 80) + if all_issues: + print("SUMMARY: issues detected:") + for issue in all_issues: + print(" -", issue) + else: + print("SUMMARY: All checks passed ✓") + + +# --------------------------------------------------------------------------- +# check command (specific frame parity) +# --------------------------------------------------------------------------- + +def cmd_check(args: argparse.Namespace) -> None: + asci_rows = read_numeric_rows(args.asci) + pose_rows = read_numeric_rows(args.pose)[1:] # skip header already filtered + + print(f"Frames in ASCI: {len(asci_rows)}") + print(f"Frames in TXT : {len(pose_rows)}") + + indices = args.frames or [0, len(pose_rows) - 1] + + for idx in indices: + actual_idx = idx if idx >= 0 else len(pose_rows) + idx + if actual_idx < 0 or actual_idx >= len(pose_rows): + continue + tx, rx, ty, ry, tz, rz = asci_rows[actual_idx] + R_nuke = euler_zxy_to_matrix(math.radians(rx), math.radians(ry), math.radians(rz)) + R_cv = convert_yup_to_ydown(R_nuke) + t_expected = [-val for val in mat_vec_mul(R_cv, (tx, ty, tz))] + + pose_vals = pose_rows[actual_idx] + R_export = [ + pose_vals[7:11], + pose_vals[11:15], + pose_vals[15:19], + ] + + rot_diff = max(abs(R_export[r][c] - R_cv[r][c]) for r in range(3) for c in range(3)) + + print(f"\nFrame {actual_idx}") + print("Nuke Tx Ty Tz :", (tx, ty, tz)) + print("Export t :", tuple(round(pose_vals[10 + i * 4], 6) for i in range(3))) + print("Expected t :", tuple(round(val, 6) for val in t_expected)) + print("Rotation diff :", rot_diff) + print("R_nuke→OpenCV :") + for row in R_cv: + print(" ", " ".join(f"{val: .6f}" for val in row)) + print("R_export :") + for row in R_export: + print(" ", " ".join(f"{val: .6f}" for val in row)) + + +# --------------------------------------------------------------------------- +# rotations command (ASCI analysis) +# --------------------------------------------------------------------------- + +def analyze_asci_rotations(path: Path, num_frames: int = 5) -> None: + print(f"Analyzing rotations in: {path}") + print("=" * 80) + + lines = [l.strip() for l in path.read_text().splitlines() if l.strip()] + num_frames = min(num_frames, len(lines)) + + for i in range(num_frames): + values = list(map(float, lines[i].split())) + tx, rx_deg, ty, ry_deg, tz, rz_deg = values + + print(f"\nFrame {i}: T=({tx:.3f}, {ty:.3f}, {tz:.3f}) R=({rx_deg:.2f}°, {ry_deg:.2f}°, {rz_deg:.2f}°)") + R_nuke = np.array(euler_zxy_to_matrix(math.radians(rx_deg), math.radians(ry_deg), math.radians(rz_deg))) + R_cv = np.array(convert_yup_to_ydown(R_nuke)) + print(f" det(Nuke)={np.linalg.det(R_nuke):.6f} det(OpenCV)={np.linalg.det(R_cv):.6f}") + + +def test_90deg_rotation() -> None: + print("=" * 80) + print("Testing 90° Y-axis rotation conversion") + ry = math.radians(90.0) + R_nuke = np.array(euler_zxy_to_matrix(0.0, ry, 0.0)) + R_cv = np.array(convert_yup_to_ydown(R_nuke)) + print("Nuke rotation:\n", R_nuke) + print("OpenCV rotation:\n", R_cv) + print(f"det Nuke={np.linalg.det(R_nuke):.6f}, det OpenCV={np.linalg.det(R_cv):.6f}") + + +def cmd_rotations(args: argparse.Namespace) -> None: + if args.test_90deg: + test_90deg_rotation() + analyze_asci_rotations(args.asci, args.frames) + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Pose diagnostics helper") + subparsers = parser.add_subparsers(dest="command", required=True) + + validate_parser = subparsers.add_parser( + "validate", help="Cross-check ASCI vs converted TXT (detailed)" + ) + validate_parser.add_argument("asci", type=Path) + validate_parser.add_argument("pose", type=Path) + validate_parser.add_argument("--unit-scale", type=float, default=1.0) + validate_parser.add_argument( + "--frames", type=int, default=5, help="Number of initial frames to inspect" + ) + validate_parser.set_defaults(func=cmd_validate) + + check_parser = subparsers.add_parser( + "check", help="Inspect specific frame indices for rotation/translation parity" + ) + check_parser.add_argument("asci", type=Path) + check_parser.add_argument("pose", type=Path) + check_parser.add_argument( + "--frames", + type=int, + nargs="*", + default=None, + help="Frame indices to inspect (use negative indices for reverse order)", + ) + check_parser.set_defaults(func=cmd_check) + + rot_parser = subparsers.add_parser( + "rotations", help="Inspect rotations inside an ASCI export" + ) + rot_parser.add_argument("asci", type=Path) + rot_parser.add_argument("--frames", type=int, default=5) + rot_parser.add_argument("--test-90deg", action="store_true") + rot_parser.set_defaults(func=cmd_rotations) + + return parser + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() + + diff --git a/nuke_CAM_exporter/testing_tools/validate_conversion.py b/nuke_CAM_exporter/testing_tools/validate_conversion.py deleted file mode 100644 index 73f011a..0000000 --- a/nuke_CAM_exporter/testing_tools/validate_conversion.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python3 -""" -Validation script to check camera pose conversion accuracy. - -This script: -1. Reads the original ASCI file -2. Reads the converted pose file -3. Validates the transformations -4. Reports any issues with coordinate systems, scales, etc. -""" - -import argparse -import math -from pathlib import Path -from typing import List, Tuple -import numpy as np - - -def parse_asci_line(line: str) -> Tuple[float, float, float, float, float, float]: - """Parse one line of ASCI file: [Tx, Rx, Ty, Ry, Tz, Rz]""" - values = line.strip().split() - if len(values) != 6: - raise ValueError(f"Expected 6 columns, got {len(values)}") - tx, rx, ty, ry, tz, rz = (float(v) for v in values) - return tx, rx, ty, ry, tz, rz - - -def parse_pose_line(line: str) -> Tuple[float, List[float], List[float]]: - """Parse one line of pose file: timestamp, intrinsics, [R|t] matrix""" - values = line.strip().split() - if len(values) != 19: - raise ValueError(f"Expected 19 columns, got {len(values)}") - - timestamp = float(values[0]) - intrinsics = [float(v) for v in values[1:7]] # fx, fy, cx, cy, width, height - - # Extract 3x4 [R|t] matrix (row-major) - matrix_values = [float(v) for v in values[7:19]] - R = [ - matrix_values[0:4], # Row 0: r00, r01, r02, t0 - matrix_values[4:8], # Row 1: r10, r11, r12, t1 - matrix_values[8:12], # Row 2: r20, r21, r22, t2 - ] - - return timestamp, intrinsics, R - - -def euler_zxy_to_matrix(rx: float, ry: float, rz: float) -> List[List[float]]: - """Convert ZXY Euler angles to rotation matrix.""" - sx, cx = math.sin(rx), math.cos(rx) - sy, cy = math.sin(ry), math.cos(ry) - sz, cz = math.sin(rz), math.cos(rz) - - Rx = [[1.0, 0.0, 0.0], [0.0, cx, -sx], [0.0, sx, cx]] - Ry = [[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]] - Rz = [[cz, -sz, 0.0], [sz, cz, 0.0], [0.0, 0.0, 1.0]] - - # R = Ry * Rx * Rz - R_temp = np.dot(Ry, Rx) - R = np.dot(R_temp, Rz) - return R.tolist() - - -def validate_rotation_matrix(R: List[List[float]], name: str = "R") -> List[str]: - """Check if rotation matrix is valid (orthogonal, determinant = 1).""" - issues = [] - R_np = np.array(R) - - # Check determinant (should be ~1.0 for rotation matrix) - det = np.linalg.det(R_np) - if abs(det - 1.0) > 0.01: - issues.append(f"{name}: determinant = {det:.6f} (expected ~1.0)") - - # Check orthogonality (R * R^T should be identity) - should_be_identity = np.dot(R_np, R_np.T) - identity = np.eye(3) - max_diff = np.abs(should_be_identity - identity).max() - if max_diff > 0.01: - issues.append(f"{name}: not orthogonal (max diff: {max_diff:.6f})") - - return issues - - -def compare_poses(asci_path: Path, pose_path: Path, unit_scale: float = 1.0) -> None: - """Compare ASCI input with converted pose output.""" - print(f"Reading ASCI: {asci_path}") - print(f"Reading poses: {pose_path}") - print(f"Using unit_scale: {unit_scale}") - print() - - # Read ASCI - asci_lines = [l for l in asci_path.read_text().splitlines() if l.strip()] - print(f"Found {len(asci_lines)} frames in ASCI") - - # Read pose header - pose_lines = pose_path.read_text().splitlines() - header = pose_lines[0] - pose_data = [parse_pose_line(l) for l in pose_lines[1:] if l.strip()] - print(f"Found {len(pose_data)} frames in pose file") - print(f"Header: {header}") - print() - - if len(asci_lines) != len(pose_data): - print(f"WARNING: Frame count mismatch! ASCI: {len(asci_lines)}, Poses: {len(pose_data)}") - print() - - # Compare first few frames in detail - num_check = min(5, len(asci_lines), len(pose_data)) - print(f"Detailed comparison of first {num_check} frames:") - print("-" * 80) - - all_issues = [] - - for i in range(num_check): - print(f"\nFrame {i}:") - - # Parse ASCI - tx, rx_deg, ty, ry_deg, tz, rz_deg = parse_asci_line(asci_lines[i]) - rx, ry, rz = math.radians(rx_deg), math.radians(ry_deg), math.radians(rz_deg) - - print(f" ASCI: T=({tx:.3f}, {ty:.3f}, {tz:.3f}), R=({rx_deg:.1f}°, {ry_deg:.1f}°, {rz_deg:.1f}°)") - - # Reconstruct rotation matrix from ASCI - R_asci = euler_zxy_to_matrix(rx, ry, rz) - R_asci_np = np.array(R_asci) - - # Get pose data (already parsed on line 97) - timestamp, intrinsics, R_pose_row = pose_data[i] - R_pose = [row[:3] for row in R_pose_row] # Extract rotation part - t_pose = [row[3] for row in R_pose_row] # Extract translation part - R_pose_np = np.array(R_pose) - - print(f" Pose: timestamp={timestamp:.0f}µs, fx={intrinsics[0]:.6f}, fy={intrinsics[1]:.6f}") - print(f" Pose R[0]: [{R_pose[0][0]:.6f}, {R_pose[0][1]:.6f}, {R_pose[0][2]:.6f}]") - print(f" Pose R[1]: [{R_pose[1][0]:.6f}, {R_pose[1][1]:.6f}, {R_pose[1][2]:.6f}]") - print(f" Pose t: ({t_pose[0]:.6f}, {t_pose[1]:.6f}, {t_pose[2]:.6f})") - - # Validate rotation matrices - issues_asci = validate_rotation_matrix(R_asci, f"ASCI_R_frame{i}") - issues_pose = validate_rotation_matrix(R_pose, f"Pose_R_frame{i}") - - if issues_asci: - print(f" ISSUES with ASCI rotation matrix:") - for issue in issues_asci: - print(f" - {issue}") - all_issues.extend(issues_asci) - - if issues_pose: - print(f" ISSUES with Pose rotation matrix:") - for issue in issues_pose: - print(f" - {issue}") - all_issues.extend(issues_pose) - - # Check coordinate system conversion - # In Y-up to Y-down conversion, Y row and Y column should be negated - # Check if this pattern exists - y_row_flipped = ( - abs(R_pose[1][0] + R_asci[1][0]) < 0.01 and - abs(R_pose[1][1] - R_asci[1][1]) < 0.01 and - abs(R_pose[1][2] + R_asci[1][2]) < 0.01 - ) - y_col_flipped = ( - abs(R_pose[0][1] + R_asci[0][1]) < 0.01 and - abs(R_pose[2][1] + R_asci[2][1]) < 0.01 - ) - - if not y_row_flipped or not y_col_flipped: - print(f" WARNING: Coordinate system conversion may be incorrect!") - print(f" Y-row flipped: {y_row_flipped}, Y-col flipped: {y_col_flipped}") - all_issues.append(f"Frame {i}: Coordinate system conversion issue") - - # Check translation conversion - # Expected: t = -R_opencv * C_scaled - camera_center_scaled = np.array([tx * unit_scale, ty * unit_scale, tz * unit_scale]) - expected_t = -R_pose_np @ camera_center_scaled - - t_diff = np.abs(np.array(t_pose) - expected_t) - max_t_diff = t_diff.max() - if max_t_diff > 0.01: - print(f" WARNING: Translation mismatch!") - print(f" Expected t: ({expected_t[0]:.6f}, {expected_t[1]:.6f}, {expected_t[2]:.6f})") - print(f" Actual t: ({t_pose[0]:.6f}, {t_pose[1]:.6f}, {t_pose[2]:.6f})") - print(f" Max diff: {max_t_diff:.6f}") - all_issues.append(f"Frame {i}: Translation conversion issue (max diff: {max_t_diff:.6f})") - else: - print(f" ✓ Translation conversion looks correct (max diff: {max_t_diff:.6f})") - - print("\n" + "=" * 80) - if all_issues: - print(f"SUMMARY: Found {len(all_issues)} issues:") - for issue in all_issues: - print(f" - {issue}") - else: - print("SUMMARY: All checks passed! ✓") - - -def main(): - parser = argparse.ArgumentParser(description="Validate camera pose conversion") - parser.add_argument("asci", type=Path, help="Input ASCI file") - parser.add_argument("pose", type=Path, help="Converted pose file") - parser.add_argument("--unit-scale", type=float, default=1.0, help="Unit scale used in conversion") - args = parser.parse_args() - - compare_poses(args.asci, args.pose, args.unit_scale) - - -if __name__ == "__main__": - main() -