Skip to content

DJI Avata 360: D-Log M curve (--fit avata360) and its colour mode field - #1

Merged
Kemerd merged 1 commit into
Kemerd:mainfrom
debsahu:avata360-dlogm
Sep 27, 2026
Merged

Kemerd merged 1 commit into
Kemerd:mainfrom
debsahu:avata360-dlogm

Conversation

@debsahu

@debsahu debsahu commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Thank you for OpenOSV. We ported your Osmo 360 D-Log M curve and primaries matrix into spirula-studio, a Gaussian splatting trainer (harry7557558/spirula-studio#97). The fit write-ups in DlogM.h and Matrices.h made that easy. This PR gives back what we learned on the DJI Avata 360, which also records .OSV.

What it adds

  1. An Avata 360 D-Log M curve and primaries matrix, selectable as --fit avata360 in osvtool render and osvtool lut. kDlogMAvata360 uses the same 7-parameter form and constraints as kDlogMOsmo360. kNativeToRec2020_Avata360 has rows that sum to 1. Osmo 360 stays the default. DlogMFit::Avata360 is appended as 3.
  2. The Avata 360's colour mode. It is at StreamMeta 2.4.1, not 4.1. On the Avata, StreamMeta 4 is an empty message, so today every Avata clip probes as Normal, D-Log M included. For dvtm_AVATA360.proto the decoder now reads 2.4.1. 19 is D-Log M and an empty 2.4 is Normal. Anything else stays Unknown with a warning, since no sample has shown it.

How the curve was derived

DJI publishes no D-Log M LUT for the Avata 360. So we fitted one from our own footage. We used an Avata 360 D-Log M clip and DJI Studio's export of the same clip with its D-Log M conversion applied. A Normal clip and its export set the noise floor.

  • Each fisheye was registered to the export by optical flow and a fitted Kannala-Brandt lens.
  • Samples came from flat, unclipped regions away from the seam. 7 frames were used to fit and 6 were held out.
  • Model: BT.709 OETF(2^e * M709 * M * lin(code)). The exposure e is fitted separately and kept out of the curve, so code 0.4 still maps to 0.18.

Held-out error, median 8-bit display levels (R / G / B):

p50 p90
Normal control (the floor) 0.70 / 0.38 / 0.53 1.75 / 0.99 / 1.36
osmo360 curve and matrix 3.90 / 3.71 / 4.19 10.42 / 10.04 / 10.25
avata360 curve and matrix 0.57 / 0.50 / 0.60 1.69 / 1.45 / 1.72

Method check: the same procedure on an Osmo 360 clip and its DJI Studio export lands within 0.045 stops of your kDlogMOsmo360 for codes 0.15 to 0.50. It drifts to 0.22 stops dark at code 0.8.

Limits

  • One hovering clip, in one white room lit by daylight.
  • The neutral samples span codes 0.125 to 0.785. The toe and the highlights are extrapolated: code 1.0 maps to 1.415, against 3.765 for osmo360.
  • Saturated colours are weakly constrained. The matrix's implied red primary has positive luminance but lies outside the spectral locus. The tests pin this so the caveat cannot go stale.
  • The fit models DJI Studio's rendering. It is not a sensor calibration. Your Rec.709 look still applies on top, as it does for the other fits.

No footage and no DJI output is included. Only the 16 constants are. NOTICE, docs/LEGAL.md section 5 and docs/COLOR.md have entries.

Tests and verification

  • New unit tests: Avata 360 D-Log M curve and primaries, and The Avata 360 colour mode is read from StreamMeta 2.4 and not from 4. The Avata curve and matrix were also added to the existing monotonic, round-trip and white-preservation loops.
  • I checked that each test fails against a wrong implementation: the Osmo matrix or curve swapped in, a perturbed constant, a broken row sum, a dropped alias, the Avata rule applied to every proto, unverified codes accepted, and a read before ClipMeta is known.
  • Built on macOS with CPU only and Homebrew dependencies, not vcpkg. That needed local shims for FindFFMPEG and tinyexr, which are not part of this PR. ctest: 395 of 396 pass. The one failure, FFmpeg build is LGPL and reports its version, fails the same way before this change, because Homebrew's FFmpeg is a GPL build. I did not run the [sample] tests, since I do not have your sample clip. I did not build Windows, CUDA or the plug-ins.
  • osvtool probe on three Avata 360 clips now reports D-Log M, D-Log M and Normal, as recorded. Two Osmo 360 clips (one D-Log M, one Normal) read the same as before.

Things you may want to know

  • osvtool probe finds no calibration on Avata 360 clips. Their PanoDewarpParams-like message appears at StreamMeta field 5 rather than 6. So Avata stitching does not work yet. This PR does not try to fix that. The curve is still usable through osvtool lut --fit avata360.
  • I did not add avata360 to the Premiere and Resolve curve menus (PrefsDlogmFit). That would touch the persisted preferences and the UI, so it seemed your call.
  • For touched lines I matched the surrounding style where clang-format 23 disagrees with the existing code.

Please change or drop anything you would rather do differently. You know this code far better than we do. Thanks again.

Adds kDlogMAvata360 and kNativeToRec2020_Avata360, selectable as
--fit avata360 in osvtool render and osvtool lut. DJI publishes no
Avata 360 LUT, so both were fitted to DJI Studio's export of one Avata
360 D-Log M clip, in the same 7-parameter form and with the same
constraints as kDlogMOsmo360. Method, held-out result and limits are in
the comment on kDlogMAvata360 and in docs/COLOR.md. Osmo 360 stays the
default; DlogMFit::Avata360 is appended as 3.

The Avata 360 (dvtm_AVATA360.proto) records its colour mode at
StreamMeta 2.4.1, and its StreamMeta 4 is empty, so every Avata clip read
as Normal. DjmdDecoder now reads 2.4.1 for that proto: 19 is D-Log M, an
empty 2.4 is Normal, anything else is left Unknown with a warning.

Verified on macOS (Homebrew deps, CPU only): all unit tests pass except
the FFmpeg LGPL check, which fails the same way before this change
because Homebrew's FFmpeg is a GPL build. [sample] tests skipped.
osvtool probe on three Avata 360 clips (two D-Log M, one Normal) and two
Osmo 360 clips reports the recorded mode for each.
@Kemerd

Kemerd commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Nice work, I read through the code and looks good, will test a build out in the morning and then merge this in. Thanks for the contribution!

@Kemerd

Kemerd commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Also a note, if you can record some footage with a brightly lit room and a color reference card, you may be able to get your own calibration data.

It was strange for me but the references I used from DJI to convert D Log M to Rec 709 are identical for both the Osmo 360 and Pocket 3. In fact the LUT is originally from an older device..

I did something very similar and used some test footage, tried maybe 9 different methods, until I found the result that gave the most true to life look on my calibrated monitor and just set that as the default.

I settled on the ACES 2.0 color space, based on the DJI references.

Gaussian splats are very cool. I actually considered trying to do stitching with them for the seams, but didn’t get any results that were worth the speed trade off.

@Kemerd
Kemerd merged commit 3eefa63 into Kemerd:main Sep 27, 2026
@Kemerd

Kemerd commented Sep 27, 2026

Copy link
Copy Markdown
Owner

The Avata 360's metadata layout uses different field numbers for colour mode, calibration, and focal length than the Osmo's, which is why its calibration looked missing. Fixing this and maybe even adding stitching support

@debsahu

debsahu commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Yes, it's different. I can share osv + srt file along with mp4 stitched together by dji studio? I switched both my lens as they scratched, with official replacement ones - I have to do panaromic calibration before I capture this for you. Please tell me how I can share these files privately with you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants