Code source de cutcutcodec.core.io.read_svg
#!/usr/bin/env python3
"""Decode the svg vectorial images based on `cairosvg` lib."""
from fractions import Fraction
import math
import pathlib
import typing
import xml
import cairosvg
import cv2
import numpy as np
import torch
from cutcutcodec.core.classes.container import ContainerInput
from cutcutcodec.core.classes.frame_video import FrameVideo
from cutcutcodec.core.classes.stream import Stream
from cutcutcodec.core.classes.stream_video import StreamVideo
from cutcutcodec.core.exceptions import DecodeError
from cutcutcodec.core.exceptions import OutOfTimeRange
[docs]
class ContainerInputSVG(ContainerInput):
"""Decode an svg image to a matricial image of any dimension.
Attributes
----------
filename : pathlib.Path
The path to the physical file that contains the svg data (readonly).
Examples
--------
>>> from cutcutcodec.core.io.read_svg import ContainerInputSVG
>>> from cutcutcodec.utils import get_project_root
>>> (stream,) = ContainerInputSVG(get_project_root() / "examples" / "logo.svg").out_streams
>>> stream.snapshot(0, (9, 9))[..., 3]
tensor([[0.0000, 0.0627, 0.5529, 0.8275, 0.9608, 0.8275, 0.5529, 0.0627, 0.0000],
[0.0745, 0.8471, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.8471, 0.0745],
[0.5686, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.5647],
[0.8863, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.8824],
[0.9765, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.9765],
[0.8863, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.8824],
[0.5686, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.5647],
[0.0745, 0.8471, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 0.8471, 0.0745],
[0.0000, 0.0627, 0.5529, 0.8275, 0.9608, 0.8275, 0.5529, 0.0627, 0.0000]])
>>>
"""
def __init__(self, filename: typing.Union[str, bytes, pathlib.Path], *, unsafe=False):
"""Initialise and create the class.
Parameters
----------
filename : pathlike
Path to the file to be decoded.
unsafe : bool
Transmitted to ``cairosvg.svg2png``.
Raises
------
cutcutcodec.core.exceptions.DecodeError
If it fail to extract any multimedia stream from the provided file.
"""
filename = pathlib.Path(filename).expanduser().resolve()
assert filename.is_file(), filename
assert isinstance(unsafe, bool), unsafe.__class__.__name__
self._filename = filename
self.unsafe = unsafe
super().__init__([_StreamVideoSVG(self)])
def __enter__(self):
"""Make the object compatible with a context manager."""
return self
def __exit__(self, *_):
"""Exit the context manager."""
def _getstate(self) -> dict:
return {
"filename": str(self.filename),
"unsafe": self.unsafe,
}
def _setstate(self, in_streams: typing.Iterable[Stream], state: dict) -> None:
keys = {"filename", "unsafe"}
assert state.keys() == keys, set(state)-keys
ContainerInputSVG.__init__(self, state["filename"], unsafe=state["unsafe"])
@property
def filename(self) -> pathlib.Path:
"""Return the path to the physical file that contains the svg data."""
return self._filename
class _StreamVideoSVG(StreamVideo):
"""Read SVG as a video stream.
Parameters
----------
height : int
The preconised dimension i (vertical) of the picture in pxl (readonly).
width : int
The preconised dimension j (horizontal) of the picture in pxl (readonly).
"""
is_space_continuous = True
is_time_continuous = True
def __init__(self, node: ContainerInputSVG):
assert isinstance(node, ContainerInputSVG), node.__class__.__name__
super().__init__(node)
with open(node.filename, "rb") as raw:
self._bytestring = raw.read()
try:
pngdata = cairosvg.svg2png(self._bytestring, unsafe=self.node.unsafe)
except xml.etree.ElementTree.ParseError as err:
raise DecodeError(f"failed to read the svg file {node.filename} with cairosvg") from err
img = torch.from_numpy(cv2.imdecode(np.frombuffer(pngdata, np.uint8), cv2.IMREAD_UNCHANGED))
self._height, self._width, _ = img.shape
self._shape_and_img = ((self._height, self._width), img)
def _get_img(self, shape: tuple[int, int]) -> torch.Tensor:
"""Cache the image."""
if self._shape_and_img[0] != shape:
self._shape_and_img = (
shape,
torch.from_numpy(
cv2.imdecode(
np.frombuffer(
cairosvg.svg2png(
self._bytestring,
unsafe=self.node.unsafe,
output_height=shape[0],
output_width=shape[1],
),
np.uint8,
),
cv2.IMREAD_UNCHANGED,
),
).to(torch.float32) / 255.0,
)
return self._shape_and_img[1]
def _snapshot(self, timestamp: Fraction, mask: torch.Tensor) -> torch.Tensor:
if timestamp < 0:
raise OutOfTimeRange(f"there is no svg frame at timestamp {timestamp} (need >= 0)")
return FrameVideo(timestamp, self._get_img(mask.shape).clone())
@property
def beginning(self) -> Fraction:
return Fraction(0)
@property
def duration(self) -> typing.Union[Fraction, float]:
return math.inf
@property
def height(self) -> int:
"""Return the preconised dimension i (vertical) of the picture in pxl."""
return self._height
@property
def width(self) -> int:
"""Return the preconised dimension j (horizontal) of the picture in pxl."""
return self._width