"""
Implements the Image's base class :class:`ImageBase` which defines the classes
helper functions and base properties.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import PIL.Image
import numpy as np
from scistag.imagestag.pixel_format import PixelFormat
from scistag.imagestag.definitions import ImsFramework
from scistag.imagestag import opencv_available, cv
if TYPE_CHECKING:
from .image import Image
[docs]class ImageBase:
"""
Defines the base class for :class:`Image` and provides object indepedent
helper methods.
"""
[docs] @classmethod
def _pixel_data_from_source(cls,
source: (str | np.ndarray | bytes |
PIL.Image.Image)) -> np.ndarray:
"""
Loads an arbitrary source and returns it as pixel data
:param source: The data source. A filename, a http url, numpy array
or a PIL image
:return: The pixel data
"""
if isinstance(source, np.ndarray):
return source
elif isinstance(source, PIL.Image.Image):
# noinspection PyTypeChecker
return np.array(source)
elif isinstance(source, str) or isinstance(source, bytes):
from .image import Image
return Image(source, framework=ImsFramework.PIL).get_pixels()
else:
raise NotImplemented
[docs] @staticmethod
def bgr_to_rgb(pixel_data: np.ndarray) -> np.ndarray:
"""
Converts BGR to RGB or the otherwise round
:param pixel_data: The input pixel data
:return: The output pixel data
"""
if len(pixel_data.shape) == 3 and pixel_data.shape[2] == 3:
return pixel_data[..., ::-1].copy()
elif len(pixel_data.shape) == 3 and pixel_data.shape[2] == 4:
return pixel_data[..., [2, 1, 0, 3]].copy()
[docs] @classmethod
def normalize_to_rgb(cls, pixels: np.ndarray,
input_format: PixelFormat = PixelFormat,
keep_gray=False) -> np.ndarray:
"""
Guarantees that the output will be in the RGB or RGBA format
:param pixels: The pixel data as :class:`np.ndarray`
:param input_format: The input format representation, e.g. see
:class:`.PixelFormat`
:param keep_gray: Defines if single channel formats shall be kept
intact. False by default.
:return: The RGB image as numpy array. If keep_gray was set and the
input was single channeled the original.
"""
if len(pixels.shape) == 2: # grayscale?
if keep_gray:
return pixels
return np.stack((pixels,) * 3, axis=-1)
if input_format == PixelFormat.BGR or input_format == PixelFormat.BGRA:
return cls.bgr_to_rgb(pixels)
else:
return pixels
[docs] @classmethod
def normalize_to_bgr(cls, pixels: np.ndarray,
input_format: PixelFormat = PixelFormat.RGB,
keep_gray=False) -> np.ndarray:
"""
Guarantees that the output will be in the BGR or BGRA format
:param pixels: The pixel data
:param input_format: The input format representation, e.g.
see :class:`.PixelFormat`
:param keep_gray: Defines if single channel formats shall be
kept intact. False by default.
:return: The BGR image as numpy array. If keep_gray was set and
the input was single channeled the original.
"""
if len(pixels.shape) == 2: # grayscale?
if keep_gray:
return pixels
return np.stack((pixels,) * 3, axis=-1)
if input_format == PixelFormat.BGR or input_format == PixelFormat.BGRA:
return pixels
else:
return cls.bgr_to_rgb(pixels)
[docs] @classmethod
def normalize_to_gray(cls, pixels: np.ndarray,
input_format: PixelFormat = PixelFormat.RGB) \
-> np.ndarray:
"""
Guarantees that the output will be grayscale
:param pixels: The pixel data :class:`np.ndarray`
:param input_format: The input format representation,
e.g. see :class:`.PixelFormat`
:return: The grayscale image as :class:`np.ndarray`
"""
if len(pixels.shape) == 2: # grayscale?
return pixels
if input_format in [PixelFormat.BGR, PixelFormat.BGRA]:
if opencv_available():
if input_format == PixelFormat.BGR:
return cv.cvtColor(pixels, cv.COLOR_BGR2GRAY)
if input_format == PixelFormat.BGRA:
return cv.cvtColor(pixels, cv.COLOR_BGRA2GRAY)
blue, green, red = pixels[:, :, 0], pixels[:, :, 1], pixels[:, :, 2]
else:
if opencv_available():
if input_format == PixelFormat.RGB:
return cv.cvtColor(pixels, cv.COLOR_RGB2GRAY)
if input_format == PixelFormat.RGBA:
return cv.cvtColor(pixels, cv.COLOR_RGBA2GRAY)
red, green, blue = pixels[:, :, 0], pixels[:, :, 1], pixels[:, :, 2]
return (0.2989 * red + 0.5870 * green + 0.1140 * blue).round().astype(
np.uint8)
[docs] @classmethod
def from_cv2(cls, pixel_data: np.ndarray) -> "Image":
"""
Creates an image from a "classic" bgr, bgra or grayscale OpenCV source.
For more advanced type please use the standard constructor.
:param pixel_data: The pixel data
:return: The image instance
"""
from .image import Image
return Image(pixel_data,
pixel_format=cls.detect_format(pixel_data,
is_cv2=True))