blob: e8af830a3d3892eb651791a18ae4bcf84b98a432 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Column transforms for materialized training windows."""
import io
def to_tensor(values, dtype=None):
"""Convert numeric window values to a tensor with a leading time axis.
Use ``partial(to_tensor, dtype=torch.float32)`` from ``functools``
to choose a dtype.
This conversion does not normalize values.
"""
import torch
result = torch.as_tensor(values, dtype=dtype)
if result.ndim == 0:
raise ValueError("Window values must have a time axis.")
return result
def images_to_tensor(values, return_uint8=False):
"""Decode a non-empty sequence of image bytes to a TCHW tensor.
Apply EXIF orientation, expand palettes to RGB/RGBA, and preserve grayscale
as one channel. Eight-bit pixels become float32 in [0, 1], or stay uint8
with ``return_uint8=True``.
Higher-bit-depth pixels always become float32 in their original units.
All decoded frames must have the same shape.
"""
import torch
if not isinstance(return_uint8, bool):
raise TypeError("return_uint8 must be a boolean.")
frames = []
for payload in values:
if not isinstance(payload, (bytes, bytearray, memoryview)):
raise ValueError("Image window values must contain image bytes.")
frame = _image_array_to_tensor(_decode_image(payload), return_uint8)
if frames and frame.shape != frames[0].shape:
raise ValueError("Image window frames must have the same shape.")
frames.append(frame)
if not frames:
raise ValueError("Image window must contain at least one frame.")
return torch.stack(frames)
def _decode_image(payload):
import numpy as np
try:
from PIL import Image, ImageOps
except ImportError as error:
raise ImportError(
"Image decoding requires Pillow; install 'pypaimon[torch]' "
"or Pillow.") from error
with Image.open(io.BytesIO(payload)) as image:
image = ImageOps.exif_transpose(image)
if image.mode == "P":
image = image.convert("RGBA" if "transparency" in image.info else "RGB")
array = np.array(image, copy=True)
if array.ndim == 2:
array = array[:, :, None]
return array
def _image_array_to_tensor(array, return_uint8=False):
import numpy as np
import torch
normalize = array.dtype == np.uint8
tensor = torch.from_numpy(array).permute(2, 0, 1)
if normalize and return_uint8:
return tensor
tensor = tensor.float()
return tensor.div_(255) if normalize else tensor