Skip to content

Commit

Permalink
Merge pull request #28 from Netflix-Skunkworks/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
davisadam10 authored Mar 4, 2024
2 parents c1772a3 + 4dfe783 commit c1e0fdc
Show file tree
Hide file tree
Showing 83 changed files with 275,859 additions and 256 deletions.
2 changes: 1 addition & 1 deletion src/open_vp_cal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Init Module defines a few module level variables
"""
__version__ = "1.0.0-rc.11"
__version__ = "1.0.0-rc.12"
__authors__ = [
"Adam Davis", "Adrian Pueyo", "Carol Payne", "Francesco Luigi Giardiello"
]
Expand Down
133 changes: 133 additions & 0 deletions src/open_vp_cal/application_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@
application to perform the analysis, calibration and export of the LED walls. This class is inherited by the UI and
CLI classes which implement the methods to display the results to the user.
"""
import json
import os
import tempfile
from typing import List, Dict, Tuple, Any

from open_vp_cal.core import constants, utils
from open_vp_cal.core.resource_loader import ResourceLoader
from open_vp_cal.framework.configuraton import Configuration
from open_vp_cal.framework.processing import Processing, SeparationException
from open_vp_cal.framework.utils import export_pre_calibration_ocio_config
from open_vp_cal.framework.validation import Validation
from open_vp_cal.led_wall_settings import LedWallSettings
from open_vp_cal.project_settings import ProjectSettings

from spg.projectSettings import ProjectSettings as SPGProjectSettings
from spg.main import run_spg_pattern_generator
from stageassets.ledWall import LEDWall as SPGLedWall
from stageassets.ledPanel import LEDPanel as SPGLedPanel
from stageassets.rasterMap import RasterMap as SPGRasterMap
from stageassets.rasterMap import Mapping as SPGMapping


class OpenVPCalBase:
"""
Expand Down Expand Up @@ -99,6 +110,11 @@ def run_pre_checks(self, led_walls: List[LedWallSettings]) -> bool:
"""
led_wall_names = [led_wall.name for led_wall in led_walls]
for led_wall in led_walls:
if led_wall.native_camera_gamut == led_wall.target_gamut:
message = f"Target Gamut & Native Camera Gamut Can Not Be The Same For {led_wall.name}"
self.error_message(message)
return False

if not led_wall.has_valid_white_balance_options():
message = f"Only Select 1 option from AutoWB, or Reference Wall or External White {led_wall.name}"
self.error_message(message)
Expand Down Expand Up @@ -288,3 +304,120 @@ def export(self, project_settings_model: ProjectSettings, led_walls: List[LedWal

walls = Processing.run_export(project_settings_model, led_walls)
return True, walls

@staticmethod
def generate_spg_patterns_for_led_walls(
project_settings: ProjectSettings, led_walls: List) -> None:
""" For the given project settings and list of led walls, generate the patterns for SPG which is used to
evaluate and diagnose issues with the imaging chain
Args:
project_settings: The project settings used for the project
led_walls: the led walls we want to generate patterns from
"""

config_writer, ocio_config_path = export_pre_calibration_ocio_config(project_settings, led_walls)
spg_project_settings = SPGProjectSettings()
spg_project_settings.frame_rate = project_settings.frame_rate
spg_project_settings.image_file_format = project_settings.file_format
spg_project_settings.image_file_bit_depth = 10
spg_project_settings.output_folder = os.path.join(
project_settings.export_folder,
constants.ProjectFolders.SPG
)
spg_project_settings.channel_mapping = "RGB"
spg_project_settings.ocio_config_path = ocio_config_path
apply_eotf_colour_transform = False
if spg_project_settings.image_file_format != constants.FileFormats.FF_EXR:
apply_eotf_colour_transform = True

spg_led_walls = []
spg_led_panels = []
spg_raster_maps = []

for count, led_wall in enumerate(led_walls):
idx = count + 1

# As this is a basic setup we default to a typical panel as we are not doing a deep dive and pixel perfect
# match
spg_panel = SPGLedPanel()
spg_panel.name = f"Panel_{idx}_{led_wall.name}"
spg_panel.manufacturer = "Unknown"
spg_panel.panel_width = 500
spg_panel.panel_height = 500
spg_panel.panel_depth = 80
spg_panel.pixel_pitch = 2.85
spg_panel.brightness = led_wall.target_max_lum_nits
spg_panel.refresh_rate = "3840"
spg_panel.scan_rate = "1/8"
spg_led_panels.append(spg_panel)

# We create a faux led wall which is the largest which we can fit into a given resolution image
# as we are not doing a pixel perfect diagnosis
target_gamut_only_cs = config_writer.get_target_gamut_only_cs(led_wall)
target_gamut_and_tf_cs = config_writer.get_target_gamut_and_transfer_function_cs(led_wall)
transfer_function_only_cs = config_writer.get_transfer_function_only_cs(led_wall)

# If we are not using an EXR file format, we apply the EOTF colour transform
if not apply_eotf_colour_transform:
target_gamut_and_tf_cs = target_gamut_only_cs

spg_led_wall = SPGLedWall()
spg_led_wall.gamut_only_cs_name = target_gamut_only_cs.getName()
spg_led_wall.gamut_and_transfer_function_cs_name = target_gamut_and_tf_cs.getName()
spg_led_wall.transfer_function_only_cs_name = transfer_function_only_cs.getName()
spg_led_wall.id = idx
spg_led_wall.name = led_wall.name
spg_led_wall.panel_name = spg_panel.name
spg_led_wall.panel = spg_panel
spg_led_wall.panel_count_width = int(project_settings.resolution_width / spg_panel.panel_resolution_width)
spg_led_wall.panel_count_height = int(
project_settings.resolution_height / spg_panel.panel_resolution_height
)
spg_led_wall.wall_default_color = utils.generate_color(led_wall.name)

spg_led_walls.append(spg_led_wall)

spg_mapping = SPGMapping()
spg_mapping.wall_name = spg_led_wall.name
spg_mapping.raster_u = 0
spg_mapping.raster_v = 0
spg_mapping.wall_segment_u_start = 0
spg_mapping.wall_segment_u_end = spg_led_wall.resolution_width
spg_mapping.wall_segment_v_start = 0
spg_mapping.wall_segment_v_end = spg_led_wall.resolution_height
spg_mapping.wall_segment_orientation = 0

spg_raster_map = SPGRasterMap()
spg_raster_map.name = f"Raster_{led_wall.name}"
spg_raster_map.resolution_width = project_settings.resolution_width
spg_raster_map.resolution_height = project_settings.resolution_height
spg_raster_map.mappings = [spg_mapping]

spg_raster_maps.append(spg_raster_map)

spg_led_panel_json = [json.loads(spg_led_panel.to_json()) for spg_led_panel in spg_led_panels]
spg_led_wall_json = [json.loads(spg_led_wall.to_json()) for spg_led_wall in spg_led_walls]
spg_raster_map_json = [json.loads(spg_raster_map.to_json()) for spg_raster_map in spg_raster_maps]

tmp_dir = tempfile.TemporaryDirectory()
spg_led_panel_json_file = os.path.join(tmp_dir.name, "led_panel_settings.json")
spg_led_wall_json_file = os.path.join(tmp_dir.name, "led_wall_settings.json")
spg_raster_map_json_file = os.path.join(tmp_dir.name, "raster_map_settings.json")
spg_project_settings_json_file = os.path.join(tmp_dir.name, "spg_project_settings.json")

with open(spg_led_panel_json_file, 'w') as f:
json.dump(spg_led_panel_json, f, indent=4)
with open(spg_led_wall_json_file, 'w') as f:
json.dump(spg_led_wall_json, f, indent=4)
with open(spg_raster_map_json_file, 'w') as f:
json.dump(spg_raster_map_json, f, indent=4)
with open(spg_project_settings_json_file, 'w') as f:
json.dump(json.loads(spg_project_settings.to_json()), f, indent=4)

run_spg_pattern_generator(
spg_led_panel_json_file,
spg_led_wall_json_file,
spg_raster_map_json_file,
spg_project_settings_json_file,
ResourceLoader.spg_pattern_basic_config())
3 changes: 2 additions & 1 deletion src/open_vp_cal/core/calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,8 @@ def run(
Results.EOTF_LUT_B: lut_b.tolist(),
Results.MAX_DISTANCES: max_distances.tolist(),
Results.TARGET_EOTF: target_EOTF,
Results.NATIVE_CAMERA_GAMUT: native_camera_gamut,
Results.NATIVE_CAMERA_GAMUT: native_camera_gamut if isinstance(native_camera_gamut, str)
else native_camera_gamut.name,
Results.OCIO_REFERENCE_GAMUT: ocio_reference_cs.name,
Results.POST_CALIBRATION_SCREEN_PRIMARIES: calibrated_screen_cs.primaries.tolist(),
Results.POST_CALIBRATION_SCREEN_WHITEPOINT: calibrated_screen_cs.whitepoint.tolist(),
Expand Down
2 changes: 1 addition & 1 deletion src/open_vp_cal/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ class CameraColourSpace:
CS_ACES_CCT = "ACEScct"
ARRI_WIDE_GAMUT_3 = "ARRI Wide Gamut 3"
ARRI_WIDE_GAMUT_4 = "ARRI Wide Gamut 4"
BLACKMAGIC_WIDE_GAMUT = "Blackmagic Design Wide Gamut"
BLACKMAGIC_WIDE_GAMUT = "Blackmagic Wide Gamut"
CANON_CINEMA_GAMUT = "Cinema Gamut"
DJI_D_GAMUT = "DJI D-Gamut"
CS_BT2020 = "ITU-R BT.2020"
Expand Down
27 changes: 26 additions & 1 deletion src/open_vp_cal/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,25 @@ def get_target_colourspace_for_led_wall(led_wall: "LedWallSettings") -> colour.R
return color_space


def get_native_camera_colourspace_for_led_wall(led_wall: "LedWallSettings") -> colour.RGB_Colourspace:
""" Gets the native camera colour space for the given led wall based on the native camera gamut
If its standard gamut we return this directly from colour
If its custom gamut we create a custom colour space from the primaries and white point
Args:
led_wall: The led wall to get the target colour space for
Returns: The target colour space
"""
try:
color_space = colour.RGB_COLOURSPACES[led_wall.native_camera_gamut]
except KeyError:
custom_primaries = led_wall.project_settings.project_custom_primaries[led_wall.native_camera_gamut]
color_space = get_custom_colour_space_from_primaries_and_wp(led_wall.native_camera_gamut, custom_primaries)
return color_space


def get_custom_colour_space_from_primaries_and_wp(custom_name: str, values: List[List]) -> colour.RGB_Colourspace:
""" Creates a custom colour space from the given primaries and white point
Expand All @@ -188,13 +207,19 @@ def get_custom_colour_space_from_primaries_and_wp(custom_name: str, values: List
"""
if len(values) != 4:
raise ValueError("Must provide 4 tuples for 3 primaries and 1white point")
raise ValueError("Must provide 4 tuples for 3 primaries and 1 white point")

white_point = values[-1]
primaries = values[:3]
return colour.RGB_Colourspace(custom_name, primaries, white_point)


def get_primaries_and_wp_for_XYZ_matrix(XYZ_matrix) -> Tuple[np.array, np.array]:
""" Get the primaries and white point for the given XYZ matrix """
primaries, wp = colour.primaries_whitepoint(XYZ_matrix)
return primaries, wp


def replace_non_alphanumeric(input_string, replace_char):
"""
Replace any non-alphanumeric characters in a string with a given character.
Expand Down
7 changes: 5 additions & 2 deletions src/open_vp_cal/framework/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,14 +759,17 @@ def _add_slate_legal_and_extended_patches(self, patch) -> Oiio.ImageBuf:
[maximum_extended, maximum_extended, maximum_extended]
)

target_gamut_only_cs_name, _ = ocio_config.OcioConfigWriter.target_gamut_only_cs_metadata(
self.led_wall
)
transfer_function_only_cs_name, _ = ocio_config.OcioConfigWriter.transfer_function_only_cs_metadata(
self.led_wall
)
color_converted_img_buffers = []
for img_buf in img_buffers:
output_img_buf = imaging_utils.apply_color_conversion(
img_buf, transfer_function_only_cs_name,
constants.ColourSpace.CS_ACES,
target_gamut_only_cs_name,
color_config=self.generation_ocio_config_path
)
color_converted_img_buffers.append(output_img_buf)
Expand Down Expand Up @@ -1067,7 +1070,7 @@ def write_to_disk(self, img_buf, patch_name) -> str:

bit_depth = 10
if self.led_wall.project_settings.file_format == constants.FileFormats.FF_EXR:
bit_depth = "float"
bit_depth = "half"

if self.led_wall.project_settings.file_format == constants.FileFormats.FF_TIF:
bit_depth = 16
Expand Down
19 changes: 12 additions & 7 deletions src/open_vp_cal/framework/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ def run_sampling(self):
f"First Green Frame: {self.led_wall.separation_results.first_green_frame.frame_num}\n"
f"Last Frame Of Sequence: {self.led_wall.sequence_loader.end_frame}\n"
f"Calculated End Slate Last Frame: {last_frame}\n"
f"Separation result will lead to out of frame range result")
f"Separation result will lead to out of frame range result\n\n"
f"Ensure Plate Was Exported Correctly Into Linear EXR {self.led_wall.input_plate_gamut}")

self.auto_detect_roi(self.led_wall.separation_results)

Expand All @@ -144,7 +145,7 @@ def analyse(self):
"""
Runs an analysis process on the samples to generate the status of the LED wall before calibration
"""
target_cs, target_to_screen_cat = self._analysis_prep()
target_cs, target_to_screen_cat, native_camera_cs = self._analysis_prep()

reference_wall_external_white_balance_matrix = None
if self.led_wall.match_reference_wall and self.led_wall.use_external_white_point:
Expand All @@ -169,7 +170,7 @@ def analyse(self):
measured_samples=self.led_wall.processing_results.samples,
reference_samples=self.led_wall.processing_results.reference_samples,
input_plate_gamut=self.led_wall.input_plate_gamut,
native_camera_gamut=self.led_wall.native_camera_gamut,
native_camera_gamut=native_camera_cs,
target_gamut=target_cs, target_to_screen_cat=target_to_screen_cat,
reference_to_target_cat=default_wall.reference_to_target_cat,
target_max_lum_nits=self.led_wall.target_max_lum_nits,
Expand All @@ -186,7 +187,7 @@ def analyse(self):
self.led_wall.processing_results.pre_calibration_results = calibration_results
return self.led_wall.processing_results

def _analysis_prep(self) -> tuple[RGB_Colourspace, constants.CAT]:
def _analysis_prep(self) -> tuple[RGB_Colourspace, constants.CAT, RGB_Colourspace]:
""" Run the steps which are needed for both calibration and the pre-calibration analysis steps
Returns: The target colour space and the target to screen CAT
Expand All @@ -199,13 +200,17 @@ def _analysis_prep(self) -> tuple[RGB_Colourspace, constants.CAT]:
if target_to_screen_cat == constants.CAT.CAT_NONE:
target_to_screen_cat = None

return utils.get_target_colourspace_for_led_wall(self.led_wall), target_to_screen_cat
return (
utils.get_target_colourspace_for_led_wall(self.led_wall),
target_to_screen_cat,
utils.get_native_camera_colourspace_for_led_wall(self.led_wall)
)

def calibrate(self) -> ProcessingResults:
"""
Runs an analysis process on the samples to generate the calibration results
"""
target_cs, target_to_screen_cat = self._analysis_prep()
target_cs, target_to_screen_cat, native_camera_cs = self._analysis_prep()

reference_wall_external_white_balance_matrix = None
if self.led_wall.match_reference_wall and self.led_wall.use_external_white_point:
Expand All @@ -228,7 +233,7 @@ def calibrate(self) -> ProcessingResults:
measured_samples=self.led_wall.processing_results.samples,
reference_samples=self.led_wall.processing_results.reference_samples,
input_plate_gamut=self.led_wall.input_plate_gamut,
native_camera_gamut=self.led_wall.native_camera_gamut,
native_camera_gamut=native_camera_cs,
target_gamut=target_cs, target_to_screen_cat=target_to_screen_cat,
reference_to_target_cat=self.led_wall.reference_to_target_cat,
target_max_lum_nits=self.led_wall.target_max_lum_nits,
Expand Down
16 changes: 13 additions & 3 deletions src/open_vp_cal/framework/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import os
import uuid
from datetime import datetime, timezone
from typing import Dict, Union, TYPE_CHECKING, List
from typing import Dict, Union, TYPE_CHECKING, List, Tuple

import requests

from open_vp_cal.core import constants, ocio_config
from open_vp_cal.core.ocio_config import OcioConfigWriter
from open_vp_cal.core.resource_loader import ResourceLoader
from open_vp_cal.framework.generation import PatchGeneration


if TYPE_CHECKING:
from open_vp_cal.led_wall_settings import LedWallSettings
from open_vp_cal.project_settings import ProjectSettings
Expand Down Expand Up @@ -72,5 +72,15 @@ def generate_patterns_for_led_walls(project_settings: 'ProjectSettings', led_wal
patch_generator = PatchGeneration(led_wall)
patch_generator.generate_patches(constants.PATCHES.PATCH_ORDER)

_, ocio_config_path = export_pre_calibration_ocio_config(project_settings, led_walls)
return ocio_config_path


def export_pre_calibration_ocio_config(
project_settings: 'ProjectSettings',
led_walls: List['LedWallSettings']) -> tuple[OcioConfigWriter, str]:
""" Export the pre calibration ocio config file for the given walls and project settings
"""
config_writer = ocio_config.OcioConfigWriter(project_settings.export_folder)
return config_writer.generate_pre_calibration_ocio_config(led_walls)
return config_writer, config_writer.generate_pre_calibration_ocio_config(led_walls)
Loading

0 comments on commit c1e0fdc

Please sign in to comment.