|
| 1 | +""" |
| 2 | +IDT Prosumer Camera Generator |
| 3 | +============================= |
| 4 | +
|
| 5 | +Define the *IDT* generator class for a *Prosumer Camera*. |
| 6 | +""" |
| 7 | + |
| 8 | +import logging |
| 9 | + |
| 10 | +import matplotlib as mpl |
| 11 | +from colour import LUT3x1D |
| 12 | +from colour.utilities import as_float_array |
| 13 | + |
| 14 | +from aces.idt import DirectoryStructure |
| 15 | +from aces.idt.generators.prosumer_camera import IDTGeneratorProsumerCamera |
| 16 | + |
| 17 | +# TODO are the mpl.use things needed in every file? |
| 18 | +mpl.use("Agg") |
| 19 | + |
| 20 | +__author__ = "Alex Forsythe, Joshua Pines, Thomas Mansencal, Nick Shaw, Adam Davis" |
| 21 | +__copyright__ = "Copyright 2022 Academy of Motion Picture Arts and Sciences" |
| 22 | +__license__ = "Academy of Motion Picture Arts and Sciences License Terms" |
| 23 | +__maintainer__ = "Academy of Motion Picture Arts and Sciences" |
| 24 | + |
| 25 | +__status__ = "Production" |
| 26 | + |
| 27 | +__all__ = [ |
| 28 | + "IDTGeneratorPreLinearizedCamera", |
| 29 | +] |
| 30 | + |
| 31 | +LOGGER = logging.getLogger(__name__) |
| 32 | + |
| 33 | + |
| 34 | +class IDTGeneratorPreLinearizedCamera(IDTGeneratorProsumerCamera): |
| 35 | + """ |
| 36 | + Define an *IDT* generator for a *PreLinearized Camera*. |
| 37 | +
|
| 38 | + Parameters |
| 39 | + ---------- |
| 40 | + project_settings : IDTProjectSettings, optional |
| 41 | + *IDT* generator settings. |
| 42 | +
|
| 43 | + Attributes |
| 44 | + ---------- |
| 45 | + - :attr:`~aces.idt.IDTBaseGenerator.GENERATOR_NAME` |
| 46 | +
|
| 47 | + Methods |
| 48 | + ------- |
| 49 | + - :meth:`~aces.idt.IDTBaseGenerator.generate_LUT` |
| 50 | + - :meth:`~aces.idt.IDTBaseGenerator.filter_LUT` |
| 51 | + """ |
| 52 | + |
| 53 | + GENERATOR_NAME = "IDTGeneratorPreLinearizedCamera" |
| 54 | + """*IDT* generator name.""" |
| 55 | + |
| 56 | + def __init__(self, project_settings): |
| 57 | + super().__init__(project_settings) |
| 58 | + |
| 59 | + def generate_LUT(self) -> LUT3x1D: |
| 60 | + """ |
| 61 | + Generate an unfiltered linearisation *LUT* for the camera samples. |
| 62 | +
|
| 63 | + The *LUT* generation process is worth describing, the camera samples are |
| 64 | + unlikely to cover the [0, 1] domain and thus need to be extrapolated. |
| 65 | +
|
| 66 | + Two extrapolated datasets are generated: |
| 67 | +
|
| 68 | + - Linearly extrapolated for the left edge missing data whose |
| 69 | + clipping likelihood is low and thus can be extrapolated safely. |
| 70 | + - Constant extrapolated for the right edge missing data whose |
| 71 | + clipping likelihood is high and thus cannot be extrapolated |
| 72 | + safely |
| 73 | +
|
| 74 | + Because the camera encoded data response is logarithmic, the slope of |
| 75 | + the center portion of the data is computed and fitted. The fitted line |
| 76 | + is used to extrapolate the right edge missing data. It is blended |
| 77 | + through a smoothstep with the constant extrapolated samples. The blend |
| 78 | + is fully achieved at the right edge of the camera samples. |
| 79 | +
|
| 80 | + Returns |
| 81 | + ------- |
| 82 | + :class:`LUT3x1D` |
| 83 | + Unfiltered linearisation *LUT* for the camera samples. |
| 84 | + """ |
| 85 | + # size = self.project_settings.lut_size |
| 86 | + # LOGGER.info('Generating unfiltered "LUT3x1D" with "%s" size...', size) |
| 87 | + # self._LUT_unfiltered = LUT3x1D(size=size, name="LUT - Unfiltered") |
| 88 | + # return self._LUT_unfiltered |
| 89 | + return None |
| 90 | + |
| 91 | + def filter_LUT(self) -> LUT3x1D: |
| 92 | + """ |
| 93 | + Filter/smooth the linearisation *LUT* for the camera samples. |
| 94 | +
|
| 95 | + The *LUT* filtering is performed with a gaussian convolution, the sigma |
| 96 | + value represents the window size. To prevent that the edges of the |
| 97 | + *LUT* are affected by the convolution, the *LUT* is extended, i.e. |
| 98 | + extrapolated at a safe two sigmas in both directions. The left edge is |
| 99 | + linearly extrapolated while the right edge is logarithmically |
| 100 | + extrapolated. |
| 101 | +
|
| 102 | + Returns |
| 103 | + ------- |
| 104 | + :class:`LUT3x1D` |
| 105 | + Filtered linearisation *LUT* for the camera samples. |
| 106 | + """ |
| 107 | + # LOGGER.info('No Filtering Simply Copying "LUT3x1D"') |
| 108 | + # |
| 109 | + # self._LUT_filtered = self._LUT_unfiltered.copy() |
| 110 | + # self._LUT_filtered.name = "LUT - Filtered" |
| 111 | + # return self._LUT_filtered |
| 112 | + return None |
| 113 | + |
| 114 | + def decode(self) -> None: |
| 115 | + """ |
| 116 | + Decode the camera samples. |
| 117 | +
|
| 118 | + The camera samples are decoded using the camera's *IDT* and the |
| 119 | + *IDT* generator settings. |
| 120 | +
|
| 121 | + Returns |
| 122 | + ------- |
| 123 | + None |
| 124 | + """ |
| 125 | + LOGGER.info("Decoding camera samples...") |
| 126 | + self._samples_decoded = {} |
| 127 | + for EV in sorted(self._samples_analysis[DirectoryStructure.COLOUR_CHECKER]): |
| 128 | + self._samples_decoded[EV] = as_float_array( |
| 129 | + self._samples_analysis[DirectoryStructure.COLOUR_CHECKER][EV][ |
| 130 | + "samples_median" |
| 131 | + ] |
| 132 | + ) |
0 commit comments