Remap360¶
Remap-table flattening for legacy 360 formats.
GoPro MAX/MAX2 .360 files store the sphere as two strips of a custom
equi-angular cubemap (EAC) that stock ffmpeg cannot unwrap; legacy Ricoh
Theta S files store two 90-degree-rotated fisheye circles in one 16:9
frame. Both become plain equirectangular through the same machinery:
numpy-generated remap tables (16-bit PGM) driving ffmpeg's remap filter,
with a feathered maskedmerge blend across the unstitched seams — the
same two-pass pattern as stitch_dual_fisheye in _360video.
The GoPro mapping is a port of Paul Bourke's max2sphere reference (paulbourke.net/panorama/gopromax2sphere/). MAX2-resolution files are handled by proportional template scaling and are experimental until validated against a real recording.
probe_gopro360 ¶
probe_gopro360(path)
Stream inventory + strip geometry of a GoPro two-strip container.
Works on original .360 files and on chunk-merged .mkv copies. Returns {"video": [{index,width,height} x2], "audio": [{index,codec,channels}], "centerwidth", "sidewidth", "blendwidth", "experimental"}. Raises ValueError naming what was found when the file does not match the two-strip pattern.
Source code in musicalgestures/_remap360.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
write_remap_pgm ¶
write_remap_pgm(xmap, ymap, tmpdir)
Write x/y remap tables as 16-bit binary PGMs for ffmpeg's remap.
Values are integer source-pixel coordinates; 16-bit PGM payloads are big-endian per the Netpbm spec.
Source code in musicalgestures/_remap360.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | |
gopro_maps ¶
gopro_maps(track_w, track_h, centerwidth, sidewidth, blendwidth, out_w, out_h)
Equirect -> vstacked GoPro strips: dual sample maps + blend alpha.
Port of max2sphere's FindFaceUV/GetColour (Paul Bourke). Returns (xmapL, ymapL, xmapR, ymapR, alpha): two source-coordinate maps into the double-height stacked frame (strip 1 on top) and the weight of the R sample (nonzero only in the unstitched seam zones of the four side faces).
Source code in musicalgestures/_remap360.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
gopro360_dual_fisheye_average ¶
gopro360_dual_fisheye_average(path, target_name=None, fov=180.0, size=704, fps=2.0, transparent=True, print_cmd=False)
The time-average of a .360 as one dual-fisheye image, without writing a video first.
For a recording of somebody standing still this is the useful still: whatever held position
resolves, whatever moved smears, and a single frame cannot show either. Returns the path to a
PNG, RGBA with the area outside each circle transparent when transparent is set.
fps decimates before averaging. The mean of a stationary scene converges long before every
frame is used -- a few hundred samples is plenty -- and decoding 4K equi-angular cubemap frames
is the whole cost of this operation, so sampling at 2 Hz rather than 30 does the same job for a
fifteenth of the work. Pass fps=None to average every frame.
Frames are accumulated in float64 from a raw pipe rather than written out and re-read. An 8-bit running mean over a few hundred frames loses roughly a bit of precision at the point where the averaging is meant to be revealing motion smaller than a pixel.
path may be several files. GoPro splits a recording into chapters, and averaging each chapter
separately and combining the means weighted by frame count is arithmetically identical to
averaging their concatenation -- while skipping the concatenation, which for a full session is
an 8 GB lossless copy written and read back before any useful work starts.
See gopro360_to_dual_fisheye for what fov means and why it has to be recorded.
Source code in musicalgestures/_remap360.py
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
gopro360_to_dual_fisheye ¶
gopro360_to_dual_fisheye(path, target_name=None, fov=180.0, size=704, circular=True, crf=21, preset='fast', print_cmd=False)
Convert a GoPro MAX .360 to side-by-side fisheye circles, front then back.
The output is 2*size by size: two inscribed circles of size pixels, the layout GoPro's
own LRV proxies use and what most dual-fisheye viewers expect.
fov is the angular width each circle covers, and it is a parameter to set deliberately rather
than leave at a default. At 180 degrees a circle holds exactly a hemisphere and the two together
hold the sphere with nothing to spare. Above 180 each holds more than a hemisphere, the pair
overlap, and a given real-world direction lands closer to the centre of the circle --- at 195
degrees by a factor of 180/195, about eight per cent at the rim. Two renders at different fov
have identical pixel dimensions and are not comparable as measurements, so anything measuring
direction or angular size in the result must record which was used.
Why this is not v360=input=eac on the strips. GoPro's .360 is a custom equi-angular cubemap
that stock ffmpeg cannot unwrap: pointing v360 at one 4096x1344 strip, or at the two stacked,
yields a plausible-looking frame with scrambled corners rather than an error. The sphere is
recovered here with the same remap tables flatten_gopro360 uses, and only then projected.
circular masks everything outside the inscribed circle to black, which is the convention for
dual-fisheye files and what GoPro's own proxies look like. Without it v360 fills the square
out to the corners, and those corners hold real image content at an angle wider than fov --
harmless to look at, wrong to measure, and enough to make two otherwise identical renders
disagree about where the image ends.
Geometry is validated against synthetic fixtures and the max2sphere reference; strip
order/orientation against real camera files is still unverified, as for flatten_gopro360.
Source code in musicalgestures/_remap360.py
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | |
flatten_gopro360 ¶
flatten_gopro360(path, target_name=None, width=None, height=None, crf=21, preset='fast', print_cmd=False)
Flatten a GoPro MAX/MAX2 .360 (or chunk-merged .mkv) to equirect.
vstacks the two EAC strips, runs two remap passes (left/right seam
samples) and blends the unstitched zones with maskedmerge. The best
audio stream (most channels — the ambisonic PCM track on a MAX) is
carried over as AAC. Files that are not exact GoPro templates (e.g.
MAX2) use proportionally scaled geometry and are experimental.
Geometry is validated against synthetic fixtures and the max2sphere reference; strip order/orientation against real camera files is still unverified.
Source code in musicalgestures/_remap360.py
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 | |
theta_maps ¶
theta_maps(in_w, in_h, out_w, out_h, fov_deg=191.5, roll_deg=(90.0, -90.0))
Equirect -> Ricoh Theta S rotated dual-fisheye source coordinates.
Legacy Theta S videos hold two fisheye circles side by side, each
rotated 90 degrees in plane, in a 16:9 frame whose bottom band is
unused. Front lens = left circle (axis +y), back = right (axis -y);
equidistant fisheye model. Returns dual maps + seam-blend alpha like
gopro_maps. fov_deg and roll_deg are tunable against a real file.
Source code in musicalgestures/_remap360.py
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 | |
flatten_theta360 ¶
flatten_theta360(path, target_name=None, width=1920, height=960, fov_deg=191.5, roll_deg=(90.0, -90.0), crf=21, preset='fast', print_cmd=False)
Flatten a legacy Ricoh Theta S dual-fisheye MP4 to equirectangular.
Explicit invocation only: a 16:9 MP4 is not identifiable as a Theta file by probing. Audio (mono on the Theta S) is passed through as AAC.
The 191.5-degree/±90-degree defaults are validated only against synthetic fixtures; a real Theta S recording may need fov_deg/roll_deg fine-tuning.
Source code in musicalgestures/_remap360.py
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 | |