Source code for src.data_handlers.lightfield

"""
Author: Ismael Seidel (ismael.seidel@ufsc.br) 
Affiliation: Embedded Computing Lab (ECL), Federal University of Santa Catarina (UFSC)

Description:
    This module contains the base classes for representing light fields and dealing with filenames.
"""

import os
import shutil
from hashlib import md5
from pathlib import Path
from typing import Any, Optional, Union

from .encoding_orders import (ScanFunctionType, get_raster_scan_list,
                              get_serpentine_scan_list)
from .formatters import (get_formatted_filename_for_lf,
                         get_formatted_pgx_or_ppm_path,
                         get_view_name_with_leading_zeros)


[docs] class LightField: def __init__( self, name: str, view_width: int, view_height: int, n_views_width: int, n_views_height: int, ) -> None: self.name = name self.view_width = view_width self.view_height = view_height self.n_views_width = n_views_width self.n_views_height = n_views_height
[docs] def get_number_of_pixels(self) -> int: """Calculates the total number of pixels in the Light Field. :return: Total number of pixels :rtype: int """ return self.get_number_of_pixels_per_view() * self.get_number_of_views()
[docs] def get_number_of_pixels_per_view(self) -> int: """Calculates the number of pixels in a single view of the Light Field. :return: Number of pixels per view :rtype: int """ return self.view_width * self.view_height
[docs] def get_number_of_views(self) -> int: """Calculates the total number of views in the Light Field. :return: Total number of views :rtype: int """ return self.n_views_width * self.n_views_height
[docs] def copy(self) -> "LightField": """Creates a copy of the Light Field instance. :return: A new LightField instance with the same properties :rtype: LightField """ return LightField( self.name, self.view_width, self.view_height, self.n_views_width, self.n_views_height, )
[docs] def get_md5(self) -> str: """Raises an error as MD5 calculation is undefined for this class. :raises NotImplementedError: Behavior undefined for this class :return: Never returns :rtype: str """ raise NotImplementedError("Behavior undefined for this class of light fields. Try using another class for light fields!")
[docs] class RAWLightFieldData(LightField): def __del__(self) -> None: if self.remove_after_using: if Path(self.raw_path).is_file(): print(f"Removing temp LF file {self.raw_path} (MD5: {self.get_md5()}).") os.remove(self.raw_path) elif Path(self.raw_path).is_dir(): print(f"Removing temp LF {self.raw_path} (MD5: {self.get_md5()}).") shutil.rmtree(self.raw_path) else: raise Exception(f"Which file should i remove? {self.raw_path} is not file and not directory")
[docs] def __init__( self, lightfield: LightField, raw_path: Union[str, Path], bits_per_sample: int = 10, type: Optional[str] = None, pix_fmt: str = "yuv444p10le", colour_space: str = "BT.709 Full Range", initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ): """Initializes a RAWLightFieldData instance with the given parameters. :param lightfield: Base LightField instance :type lightfield: LightField :param raw_path: Path to the raw data :type raw_path: Union[str, Path] :param bits_per_sample: Number of bits per sample, defaults to 10 :type bits_per_sample: int, optional :param type: Type of the raw data, defaults to None :type type: str, optional :param pix_fmt: Pixel format, defaults to "yuv444p10le" :type pix_fmt: str, optional :param colour_space: Colour space, defaults to "BT.709 Full Range" :type colour_space: str, optional :param initial_width: Initial width of the Light Field, defaults to 0 :type initial_width: int, optional :param initial_height: Initial height of the Light Field, defaults to 0 :type initial_height: int, optional :param step_width: Step width for processing, defaults to 1 :type step_width: int, optional :param step_height: Step height for processing, defaults to 1 :type step_height: int, optional :param bpp_for_naming: Bits per pixel for naming purposes, defaults to None :type bpp_for_naming: optional :param scan_order: Scan order function, defaults to get_serpentine_scan_list :type scan_order: ScanFunctionType, optional :param remove_after_using: Whether to remove data after use, defaults to False :type remove_after_using: bool, optional """ LightField.__init__( self, lightfield.name, lightfield.view_width, lightfield.view_height, lightfield.n_views_width, lightfield.n_views_height, ) self.raw_path = Path(raw_path) if raw_path is not None else None self.bits_per_sample = bits_per_sample self.type = type self.pix_fmt = pix_fmt self.colour_space = colour_space self.initial_width = initial_width self.initial_height = initial_height self.step_width = step_width self.step_height = step_height self.bpp_for_naming = bpp_for_naming self.scan_order = scan_order self.remove_after_using = remove_after_using
[docs] class RAW_RGB_PPM_LightField_Data(RAWLightFieldData): def __init__( self, lightfield: LightField, ppm_path: Union[str, Path], initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ) -> None: RAWLightFieldData.__init__( self, lightfield=lightfield, raw_path=None, bits_per_sample=10, type="ppm", pix_fmt="PPM", colour_space="sRGB", initial_width=initial_width, initial_height=initial_height, step_width=step_width, step_height=step_height, bpp_for_naming=bpp_for_naming, scan_order=scan_order, remove_after_using=remove_after_using ) self.raw_path: Path = get_formatted_pgx_or_ppm_path( ppm_path, lightfield, bpp=bpp_for_naming ) if (Path(self.raw_path) / f"{get_view_name_with_leading_zeros(0,0)}.{self.type}").is_file() and remove_after_using: print("File exists in constructor, not going to remove") print(self.raw_path) self.remove_after_using = False
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the PPM light field data. :return: MD5 hash string, or None if raw_path is undefined :rtype: Optional[str] """ raster_scan = get_raster_scan_list( self.n_views_width, self.n_views_height, self.initial_width, self.initial_height, self.step_width, self.step_height ) view_concat_md5 = "" for x, y in raster_scan: view_name = get_view_name_with_leading_zeros(x, y) try: cur_path = self.raw_path / f"{view_name}.ppm" except TypeError: print("Undefined raw_path. Cannot generate MD5 without a raw path.") return None else: view_concat_md5 += md5(cur_path.read_bytes()).hexdigest() return md5(view_concat_md5.encode()).hexdigest()
[docs] class RAW_RGB_PNG_LightField_Data(RAWLightFieldData): def __init__( self, lightfield: LightField, png_path: Union[str, Path], bits_per_sample: int = 8, initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ) -> None: RAWLightFieldData.__init__( self, lightfield=lightfield, raw_path=None, bits_per_sample=bits_per_sample, type="png", pix_fmt="rgb24", colour_space="sRGB", initial_width=initial_width, initial_height=initial_height, step_width=step_width, step_height=step_height, bpp_for_naming=bpp_for_naming, scan_order=scan_order, remove_after_using=remove_after_using ) self.raw_path: Path = get_formatted_pgx_or_ppm_path( png_path, lightfield, bpp=bpp_for_naming ) if (Path(self.raw_path) / f"{get_view_name_with_leading_zeros(0,0)}.{self.type}").is_file() and remove_after_using: print("File exists in constructor, not going to remove") print(self.raw_path) self.remove_after_using = False
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the PNG light field data. :return: MD5 hash string, or None if raw_path is undefined :rtype: Optional[str] """ raster_scan = get_raster_scan_list( self.n_views_width, self.n_views_height, self.initial_width, self.initial_height, self.step_width, self.step_height ) view_concat_md5 = "" for x, y in raster_scan: view_name = get_view_name_with_leading_zeros(x, y) try: cur_path = self.raw_path / f"{view_name}.png" except TypeError: print("Undefined raw_path. Cannot generate MD5 without a raw path.") return None else: view_concat_md5 += md5(cur_path.read_bytes()).hexdigest() return md5(view_concat_md5.encode()).hexdigest()
[docs] class RAW_BT709_FR_PGX_LightField_Data(RAWLightFieldData): def __init__( self, lightfield: LightField, pgx_path: Union[str, Path], initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ) -> None: RAWLightFieldData.__init__( self, lightfield=lightfield, raw_path=None, bits_per_sample=10, type="pgx", pix_fmt="PGX", colour_space="BT.709 Full Range", initial_width=initial_width, initial_height=initial_height, step_width=step_width, step_height=step_height, bpp_for_naming=bpp_for_naming, scan_order=scan_order, remove_after_using=remove_after_using ) self.raw_path: Path = get_formatted_pgx_or_ppm_path( pgx_path, lightfield, bpp=bpp_for_naming ) if (Path(self.raw_path) / f"{get_view_name_with_leading_zeros(0,0)}.{self.type}").is_file() and remove_after_using: print("File exists in constructor, not going to remove") print(self.raw_path) self.remove_after_using = False
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the PGX light field data. :return: MD5 hash string, or None if raw_path is undefined :rtype: Optional[str] """ raster_scan = get_raster_scan_list( self.n_views_width, self.n_views_height, self.initial_width, self.initial_height, self.step_width, self.step_height ) view_concat_md5 = "" for x, y in raster_scan: view_name = get_view_name_with_leading_zeros(x, y) ch_concat_md5 = "" for ch in range(3): try: # This block of code may raise an exception in case the raw_path is None cur_path = self.raw_path / str(ch) / f"{view_name}.pgx" except TypeError: print("Undefined raw_path. Cannot generate MD5 without a raw path.") return None else: ch_concat_md5 += md5(cur_path.read_bytes()).hexdigest() view_concat_md5 += md5(ch_concat_md5.encode()).hexdigest() return md5(view_concat_md5.encode()).hexdigest()
[docs] class RAW_BT709_FR_YUV444p10le_LightField_Data(RAWLightFieldData): def __init__( self, lightfield: LightField, yuv_path: Union[str, Path, None], initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ) -> None: RAWLightFieldData.__init__( self, lightfield=lightfield, raw_path=None, bits_per_sample=10, type="yuv", pix_fmt="yuv444p10le", colour_space="BT.709 Full Range", initial_width=initial_width, initial_height=initial_height, step_width=step_width, step_height=step_height, bpp_for_naming=bpp_for_naming, scan_order=scan_order, remove_after_using=remove_after_using ) self.raw_path = get_formatted_filename_for_lf( path=yuv_path, lightfield=self, bpp=bpp_for_naming, file_extension="yuv", ) if yuv_path is not None: self.yuv_path = Path(yuv_path) if Path(self.raw_path).is_file() and remove_after_using: print("File exists in constructor, not going to remove") print(self.raw_path) self.remove_after_using = False
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the YUV light field file. :return: MD5 hash string, or None if raw_path is undefined :rtype: Optional[str] """ try: return md5(self.raw_path.read_bytes()).hexdigest() except AttributeError: print("Undefined raw_path. Cannot generate MD5 without a raw path.") return None
[docs] class RAW_Decoded_BT709_FR_YUV444p10le_LightField_Data(RAWLightFieldData): def __init__( self, lightfield: LightField, decoded_filename: Union[str, Path], initial_width: int = 0, initial_height: int = 0, step_width: int = 1, step_height: int = 1, bpp_for_naming: Optional[Any] = None, scan_order: ScanFunctionType = get_serpentine_scan_list, remove_after_using: bool = False ) -> None: RAWLightFieldData.__init__( self, lightfield=lightfield, raw_path=decoded_filename, bits_per_sample=10, type="yuv", pix_fmt="yuv444p10le", colour_space="BT.709 Full Range", initial_width=initial_width, initial_height=initial_height, step_width=step_width, step_height=step_height, bpp_for_naming=bpp_for_naming, scan_order=scan_order, remove_after_using=remove_after_using ) if Path(self.raw_path).is_file() and remove_after_using: print("File exists in constructor, not going to remove") print(self.raw_path) self.remove_after_using = False
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the decoded YUV light field file. :return: MD5 hash string, or None if raw_path is undefined :rtype: Optional[str] """ try: return md5(self.raw_path.read_bytes()).hexdigest() except AttributeError: print("Undefined raw_path. Cannot generate MD5 without a raw path.") return None
[docs] class EncodedLightField(LightField): def __init__( self, lightfield: LightField, encoded_path: Union[str, Path], target_bitrate: float, actual_bitrate: float, encoder_name: str, pix_fmt: str = "yuv444p10le", colour_space: str = "BT.709 Full Range", ) -> None: LightField.__init__( self, lightfield.name, lightfield.view_width, lightfield.view_height, lightfield.n_views_width, lightfield.n_views_height, ) self.encoded_path = encoded_path self.target_bitrate = target_bitrate self.actual_bitrate = actual_bitrate self.encoder_name = encoder_name self.pix_fmt = pix_fmt self.colour_space = colour_space
[docs] def get_md5(self) -> Optional[str]: """Computes MD5 hash of the encoded light field file. :return: MD5 hash string, or None if encoded_path is undefined :rtype: Optional[str] """ try: return md5(self.encoded_path.read_bytes()).hexdigest() except AttributeError: print("Undefined encoded_path. Cannot generate MD5 without a raw path.") return None