Module facetorch.analyzer.reader

Sub-modules

facetorch.analyzer.reader.core

Public image readers feeding facetorch's canonical input pipeline.

Classes

class ImageReader (transform: torchvision.transforms.transforms.Compose,
device: torch.device,
optimize_transform: bool,
max_decoded_pixels: int = 16777216)
Expand source code
class ImageReader(BaseReader):
    """Reader restricted to local filesystem paths."""

    def __init__(
        self,
        transform: torchvision.transforms.Compose,
        device: torch.device,
        optimize_transform: bool,
        max_decoded_pixels: int = DEFAULT_MAX_DECODED_PIXELS,
    ):
        super().__init__(transform, device, optimize_transform)
        self.max_decoded_pixels = _validate_max_decoded_pixels(max_decoded_pixels)

    read_pil_image = UniversalReader.read_pil_image
    read_image_from_path = UniversalReader.read_image_from_path

    @Timer("ImageReader.run", "{name}: {milliseconds:.2f} ms", logger=logger.debug)
    def run(
        self,
        image_source: LocalPath,
        fix_img_size: bool = False,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        if not isinstance(image_source, (str, os.PathLike)):
            raise InputError(
                f"ImageReader accepts only a local path, got {type(image_source).__name__}."
            )
        path = os.fspath(image_source)
        if _is_remote_reference(path):
            raise InputError(
                "ImageReader does not permit remote URLs; configure URLReader."
            )
        return self.read_image_from_path(
            path,
            fix_img_size,
            input_policy=input_policy,
            input_spec=input_spec,
        )

Reader restricted to local filesystem paths.

Base class for image reader.

All image readers should subclass it. All subclass should overwrite:

  • Methods:run, used for running the reading process and return a tensor.
Args
-----=
transform : transforms.Compose
Transform to be applied to the image.
device : torch.device
Torch device cpu or cuda.
optimize_transform : bool
Whether to optimize the transforms that are resizing

the image to a fixed size.

Ancestors

Methods

def read_pil_image(self,
pil_image: PIL.Image.Image,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None,
path_input: str | None = None) ‑> ImageData
Expand source code
def read_pil_image(
    self,
    pil_image: Image.Image,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
    path_input: Optional[str] = None,
) -> ImageData:
    try:
        width, height = pil_image.size
    except (AttributeError, TypeError, ValueError) as exc:
        raise InputError("Decoded image dimensions are invalid.") from exc
    if (
        isinstance(width, bool)
        or not isinstance(width, int)
        or isinstance(height, bool)
        or not isinstance(height, int)
    ):
        raise InputError("Decoded image dimensions are invalid.")
    decoded_pixels = width * height
    if width < 1 or height < 1 or decoded_pixels > self.max_decoded_pixels:
        raise InputError(
            f"Decoded image contains {decoded_pixels} pixels; the configured "
            f"limit is {self.max_decoded_pixels}."
        )

    mode = getattr(pil_image, "mode", None)
    source = pil_image
    converted = None
    conversion_message = None
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", Image.DecompressionBombWarning)
            if mode not in {"L", "RGB", "RGBA"}:
                if str(input_policy).lower().strip() == "strict":
                    raise InputError(
                        f"Strict mode does not accept decoded PIL mode {mode!r}; "
                        "convert it explicitly to L, RGB, or RGBA."
                    )
                conversion_message = f"Converted decoded PIL mode {mode!r} to RGB."
                warnings.warn(
                    conversion_message,
                    InputCoercionWarning,
                    stacklevel=3,
                )
                converted = pil_image.convert("RGB")
                source = converted
            array = np.array(source, copy=True)
    except FacetorchError:
        raise
    except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
        raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
    except (OSError, ValueError) as exc:
        raise InputError(
            "Could not decode or convert the supplied PIL image."
        ) from exc
    finally:
        if converted is not None:
            converted.close()

    data = self.process_tensor(
        _array_to_tensor(array),
        fix_img_size,
        input_policy=input_policy,
        input_spec=input_spec,
        source_kind="decoded",
        path_input=path_input,
    )
    if conversion_message is not None:
        data.warnings.insert(0, conversion_message)
    return data
def read_image_from_path(self,
path_image: str,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None) ‑> ImageData
Expand source code
def read_image_from_path(
    self,
    path_image: str,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
) -> ImageData:
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", Image.DecompressionBombWarning)
            with Image.open(path_image) as pil_image:
                return self.read_pil_image(
                    pil_image,
                    fix_img_size,
                    input_policy=input_policy,
                    input_spec=input_spec,
                    path_input=str(Path(path_image)),
                )
    except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
        raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
    except (
        FileNotFoundError,
        PermissionError,
        UnidentifiedImageError,
        OSError,
    ) as exc:
        raise InputError(
            f"Could not read local image path {path_image!r}."
        ) from exc

Inherited members

class ReaderProtocol (*args, **kwargs)
Expand source code
@runtime_checkable
class ReaderProtocol(Protocol):
    """Small public reader extension point used by :class:`FaceAnalyzer`."""

    def run(
        self,
        image_source: ImageSource,
        fix_img_size: bool = False,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        """Decode and canonicalize exactly one source image."""

Small public reader extension point used by :class:FaceAnalyzer.

Ancestors

  • typing.Protocol
  • typing.Generic

Methods

def run(self,
image_source: str | os.PathLike | torch.Tensor | numpy.ndarray | bytes | PIL.Image.Image,
fix_img_size: bool = False,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None) ‑> ImageData
Expand source code
def run(
    self,
    image_source: ImageSource,
    fix_img_size: bool = False,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
) -> ImageData:
    """Decode and canonicalize exactly one source image."""

Decode and canonicalize exactly one source image.

class TensorReader (transform: torchvision.transforms.transforms.Compose,
device: torch.device,
optimize_transform: bool)
Expand source code
class TensorReader(BaseReader):
    """Reader restricted to Torch tensors."""

    def __init__(
        self,
        transform: torchvision.transforms.Compose,
        device: torch.device,
        optimize_transform: bool,
    ):
        super().__init__(transform, device, optimize_transform)

    @Timer("TensorReader.run", "{name}: {milliseconds:.2f} ms", logger=logger.debug)
    def run(
        self,
        image_source: torch.Tensor,
        fix_img_size: bool = False,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        if not isinstance(image_source, torch.Tensor):
            raise InputError(
                f"TensorReader accepts only Torch tensors, got "
                f"{type(image_source).__name__}."
            )
        return self.process_tensor(
            image_source,
            fix_img_size,
            input_policy=input_policy,
            input_spec=input_spec,
            source_kind="torch",
        )

Reader restricted to Torch tensors.

Base class for image reader.

All image readers should subclass it. All subclass should overwrite:

  • Methods:run, used for running the reading process and return a tensor.
Args
-----=
transform : transforms.Compose
Transform to be applied to the image.
device : torch.device
Torch device cpu or cuda.
optimize_transform : bool
Whether to optimize the transforms that are resizing

the image to a fixed size.

Ancestors

Inherited members

class UniversalReader (transform: torchvision.transforms.transforms.Compose,
device: torch.device,
optimize_transform: bool,
max_decoded_pixels: int = 16777216)
Expand source code
class UniversalReader(BaseReader):
    """Read local paths and in-memory images; network access is intentionally absent."""

    def __init__(
        self,
        transform: torchvision.transforms.Compose,
        device: torch.device,
        optimize_transform: bool,
        max_decoded_pixels: int = DEFAULT_MAX_DECODED_PIXELS,
    ):
        super().__init__(transform, device, optimize_transform)
        self.max_decoded_pixels = _validate_max_decoded_pixels(max_decoded_pixels)

    @Timer("UniversalReader.run", "{name}: {milliseconds:.2f} ms", logger=logger.debug)
    def run(
        self,
        image_source: ImageSource,
        fix_img_size: bool = False,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        if isinstance(image_source, (str, os.PathLike)):
            path = os.fspath(image_source)
            if _is_remote_reference(path):
                raise InputError(
                    "Remote image input requires an explicit URLReader configuration."
                )
            return self.read_image_from_path(
                path,
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
            )
        if isinstance(image_source, torch.Tensor):
            return self.read_tensor(
                image_source,
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
            )
        if isinstance(image_source, np.ndarray):
            return self.read_numpy_array(
                image_source,
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
            )
        if isinstance(image_source, bytes):
            return self.read_image_from_bytes(
                image_source,
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
            )
        if isinstance(image_source, Image.Image):
            return self.read_pil_image(
                image_source,
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
            )
        raise InputError(
            f"Unsupported image source type {type(image_source).__name__}; expected a "
            "local path, bytes, PIL image, NumPy array, or Torch tensor."
        )

    def read_tensor(
        self,
        tensor: torch.Tensor,
        fix_img_size: bool,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        return self.process_tensor(
            tensor,
            fix_img_size,
            input_policy=input_policy,
            input_spec=input_spec,
            source_kind="torch",
        )

    def read_pil_image(
        self,
        pil_image: Image.Image,
        fix_img_size: bool,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
        path_input: Optional[str] = None,
    ) -> ImageData:
        try:
            width, height = pil_image.size
        except (AttributeError, TypeError, ValueError) as exc:
            raise InputError("Decoded image dimensions are invalid.") from exc
        if (
            isinstance(width, bool)
            or not isinstance(width, int)
            or isinstance(height, bool)
            or not isinstance(height, int)
        ):
            raise InputError("Decoded image dimensions are invalid.")
        decoded_pixels = width * height
        if width < 1 or height < 1 or decoded_pixels > self.max_decoded_pixels:
            raise InputError(
                f"Decoded image contains {decoded_pixels} pixels; the configured "
                f"limit is {self.max_decoded_pixels}."
            )

        mode = getattr(pil_image, "mode", None)
        source = pil_image
        converted = None
        conversion_message = None
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("error", Image.DecompressionBombWarning)
                if mode not in {"L", "RGB", "RGBA"}:
                    if str(input_policy).lower().strip() == "strict":
                        raise InputError(
                            f"Strict mode does not accept decoded PIL mode {mode!r}; "
                            "convert it explicitly to L, RGB, or RGBA."
                        )
                    conversion_message = f"Converted decoded PIL mode {mode!r} to RGB."
                    warnings.warn(
                        conversion_message,
                        InputCoercionWarning,
                        stacklevel=3,
                    )
                    converted = pil_image.convert("RGB")
                    source = converted
                array = np.array(source, copy=True)
        except FacetorchError:
            raise
        except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
            raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
        except (OSError, ValueError) as exc:
            raise InputError(
                "Could not decode or convert the supplied PIL image."
            ) from exc
        finally:
            if converted is not None:
                converted.close()

        data = self.process_tensor(
            _array_to_tensor(array),
            fix_img_size,
            input_policy=input_policy,
            input_spec=input_spec,
            source_kind="decoded",
            path_input=path_input,
        )
        if conversion_message is not None:
            data.warnings.insert(0, conversion_message)
        return data

    def read_numpy_array(
        self,
        array: np.ndarray,
        fix_img_size: bool,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        return self.process_tensor(
            _array_to_tensor(array),
            fix_img_size,
            input_policy=input_policy,
            input_spec=input_spec,
            source_kind="numpy",
        )

    def read_image_from_bytes(
        self,
        image_bytes: bytes,
        fix_img_size: bool,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
        path_input: Optional[str] = None,
    ) -> ImageData:
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("error", Image.DecompressionBombWarning)
                with io.BytesIO(image_bytes) as buffer, Image.open(buffer) as pil_image:
                    return self.read_pil_image(
                        pil_image,
                        fix_img_size,
                        input_policy=input_policy,
                        input_spec=input_spec,
                        path_input=path_input,
                    )
        except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
            raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
        except (UnidentifiedImageError, OSError, ValueError) as exc:
            if isinstance(exc, FacetorchError):
                raise
            raise InputError("The supplied bytes are not a supported image.") from exc

    def read_image_from_path(
        self,
        path_image: str,
        fix_img_size: bool,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("error", Image.DecompressionBombWarning)
                with Image.open(path_image) as pil_image:
                    return self.read_pil_image(
                        pil_image,
                        fix_img_size,
                        input_policy=input_policy,
                        input_spec=input_spec,
                        path_input=str(Path(path_image)),
                    )
        except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
            raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
        except (
            FileNotFoundError,
            PermissionError,
            UnidentifiedImageError,
            OSError,
        ) as exc:
            raise InputError(
                f"Could not read local image path {path_image!r}."
            ) from exc

    def read_image_from_url(self, *_args, **_kwargs) -> ImageData:
        """Compatibility guard for callers that previously used implicit networking."""
        raise InputError(
            "Remote image input requires an explicit URLReader configuration."
        )

Read local paths and in-memory images; network access is intentionally absent.

Base class for image reader.

All image readers should subclass it. All subclass should overwrite:

  • Methods:run, used for running the reading process and return a tensor.
Args
-----=
transform : transforms.Compose
Transform to be applied to the image.
device : torch.device
Torch device cpu or cuda.
optimize_transform : bool
Whether to optimize the transforms that are resizing

the image to a fixed size.

Ancestors

Subclasses

Methods

def read_tensor(self,
tensor: torch.Tensor,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None) ‑> ImageData
Expand source code
def read_tensor(
    self,
    tensor: torch.Tensor,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
) -> ImageData:
    return self.process_tensor(
        tensor,
        fix_img_size,
        input_policy=input_policy,
        input_spec=input_spec,
        source_kind="torch",
    )
def read_pil_image(self,
pil_image: PIL.Image.Image,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None,
path_input: str | None = None) ‑> ImageData
Expand source code
def read_pil_image(
    self,
    pil_image: Image.Image,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
    path_input: Optional[str] = None,
) -> ImageData:
    try:
        width, height = pil_image.size
    except (AttributeError, TypeError, ValueError) as exc:
        raise InputError("Decoded image dimensions are invalid.") from exc
    if (
        isinstance(width, bool)
        or not isinstance(width, int)
        or isinstance(height, bool)
        or not isinstance(height, int)
    ):
        raise InputError("Decoded image dimensions are invalid.")
    decoded_pixels = width * height
    if width < 1 or height < 1 or decoded_pixels > self.max_decoded_pixels:
        raise InputError(
            f"Decoded image contains {decoded_pixels} pixels; the configured "
            f"limit is {self.max_decoded_pixels}."
        )

    mode = getattr(pil_image, "mode", None)
    source = pil_image
    converted = None
    conversion_message = None
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", Image.DecompressionBombWarning)
            if mode not in {"L", "RGB", "RGBA"}:
                if str(input_policy).lower().strip() == "strict":
                    raise InputError(
                        f"Strict mode does not accept decoded PIL mode {mode!r}; "
                        "convert it explicitly to L, RGB, or RGBA."
                    )
                conversion_message = f"Converted decoded PIL mode {mode!r} to RGB."
                warnings.warn(
                    conversion_message,
                    InputCoercionWarning,
                    stacklevel=3,
                )
                converted = pil_image.convert("RGB")
                source = converted
            array = np.array(source, copy=True)
    except FacetorchError:
        raise
    except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
        raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
    except (OSError, ValueError) as exc:
        raise InputError(
            "Could not decode or convert the supplied PIL image."
        ) from exc
    finally:
        if converted is not None:
            converted.close()

    data = self.process_tensor(
        _array_to_tensor(array),
        fix_img_size,
        input_policy=input_policy,
        input_spec=input_spec,
        source_kind="decoded",
        path_input=path_input,
    )
    if conversion_message is not None:
        data.warnings.insert(0, conversion_message)
    return data
def read_numpy_array(self,
array: numpy.ndarray,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None) ‑> ImageData
Expand source code
def read_numpy_array(
    self,
    array: np.ndarray,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
) -> ImageData:
    return self.process_tensor(
        _array_to_tensor(array),
        fix_img_size,
        input_policy=input_policy,
        input_spec=input_spec,
        source_kind="numpy",
    )
def read_image_from_bytes(self,
image_bytes: bytes,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None,
path_input: str | None = None) ‑> ImageData
Expand source code
def read_image_from_bytes(
    self,
    image_bytes: bytes,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
    path_input: Optional[str] = None,
) -> ImageData:
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", Image.DecompressionBombWarning)
            with io.BytesIO(image_bytes) as buffer, Image.open(buffer) as pil_image:
                return self.read_pil_image(
                    pil_image,
                    fix_img_size,
                    input_policy=input_policy,
                    input_spec=input_spec,
                    path_input=path_input,
                )
    except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
        raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
    except (UnidentifiedImageError, OSError, ValueError) as exc:
        if isinstance(exc, FacetorchError):
            raise
        raise InputError("The supplied bytes are not a supported image.") from exc
def read_image_from_path(self,
path_image: str,
fix_img_size: bool,
*,
input_policy: str = 'coerce',
input_spec: InputSpec | None = None) ‑> ImageData
Expand source code
def read_image_from_path(
    self,
    path_image: str,
    fix_img_size: bool,
    *,
    input_policy: str = "coerce",
    input_spec: Optional[InputSpec] = None,
) -> ImageData:
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", Image.DecompressionBombWarning)
            with Image.open(path_image) as pil_image:
                return self.read_pil_image(
                    pil_image,
                    fix_img_size,
                    input_policy=input_policy,
                    input_spec=input_spec,
                    path_input=str(Path(path_image)),
                )
    except (Image.DecompressionBombError, Image.DecompressionBombWarning) as exc:
        raise InputError("Decoded image exceeds Pillow's safety limit.") from exc
    except (
        FileNotFoundError,
        PermissionError,
        UnidentifiedImageError,
        OSError,
    ) as exc:
        raise InputError(
            f"Could not read local image path {path_image!r}."
        ) from exc
def read_image_from_url(self, *_args, **_kwargs) ‑> ImageData
Expand source code
def read_image_from_url(self, *_args, **_kwargs) -> ImageData:
    """Compatibility guard for callers that previously used implicit networking."""
    raise InputError(
        "Remote image input requires an explicit URLReader configuration."
    )

Compatibility guard for callers that previously used implicit networking.

Inherited members

class URLReader (transform: torchvision.transforms.transforms.Compose,
device: torch.device,
optimize_transform: bool,
allowed_schemes: Sequence[str] = ('https',),
timeout: float = 10.0,
max_redirects: int = 3,
max_bytes: int = 10485760,
max_decoded_pixels: int = 16777216)
Expand source code
class URLReader(UniversalReader):
    """Explicit, bounded HTTP(S) image reader."""

    _REDIRECT_STATUSES = {301, 302, 303, 307, 308}

    def __init__(
        self,
        transform: torchvision.transforms.Compose,
        device: torch.device,
        optimize_transform: bool,
        allowed_schemes: Sequence[str] = ("https",),
        timeout: float = 10.0,
        max_redirects: int = 3,
        max_bytes: int = 10 * 1024 * 1024,
        max_decoded_pixels: int = DEFAULT_MAX_DECODED_PIXELS,
    ):
        super().__init__(
            transform,
            device,
            optimize_transform,
            max_decoded_pixels=max_decoded_pixels,
        )
        if isinstance(allowed_schemes, str):
            allowed_schemes = (allowed_schemes,)
        self.allowed_schemes = tuple(
            str(scheme).lower().strip() for scheme in allowed_schemes
        )
        if not self.allowed_schemes or any(
            scheme not in {"http", "https"} for scheme in self.allowed_schemes
        ):
            raise InputError("allowed_schemes must contain only 'http' and/or 'https'.")
        if timeout <= 0:
            raise InputError("URLReader timeout must be greater than zero.")
        if max_redirects < 0:
            raise InputError("URLReader max_redirects must be non-negative.")
        if max_bytes < 1:
            raise InputError("URLReader max_bytes must be at least one byte.")
        self.timeout = timeout
        self.max_redirects = max_redirects
        self.max_bytes = max_bytes

    @Timer("URLReader.run", "{name}: {milliseconds:.2f} ms", logger=logger.debug)
    def run(
        self,
        image_source: str,
        fix_img_size: bool = False,
        *,
        input_policy: str = "coerce",
        input_spec: Optional[InputSpec] = None,
    ) -> ImageData:
        if not isinstance(image_source, str):
            raise InputError(
                f"URLReader accepts only a URL string, got {type(image_source).__name__}."
            )

        current_url = image_source
        deadline = time.monotonic() + self.timeout
        for redirect_count in range(self.max_redirects + 1):
            _remaining_timeout(deadline)
            parsed = urlsplit(current_url)
            if parsed.scheme.lower() not in self.allowed_schemes or not parsed.netloc:
                raise InputError("URL scheme is not allowed or the URL has no host.")
            addresses = _validate_public_url_target(parsed, deadline)
            _remaining_timeout(deadline)
            connection = None
            response = None
            last_error = None
            for address in addresses:
                try:
                    connection, response = _open_pinned_response(
                        parsed,
                        address,
                        _remaining_timeout(deadline),
                    )
                    try:
                        _remaining_timeout(deadline)
                    except InputError:
                        response.close()
                        connection.close()
                        raise
                    break
                except InputError:
                    raise
                except TimeoutError as exc:
                    last_error = exc
                    if time.monotonic() >= deadline:
                        raise InputError("Remote image request timed out.") from exc
                except (OSError, ValueError, http.client.HTTPException) as exc:
                    last_error = exc
            if connection is None or response is None:
                if isinstance(last_error, TimeoutError) or time.monotonic() >= deadline:
                    raise InputError("Remote image request timed out.") from last_error
                raise InputError(
                    "Remote image request failed or timed out."
                ) from last_error

            try:
                if response.status in self._REDIRECT_STATUSES:
                    location = response.headers.get("Location")
                    if location is None:
                        raise InputError("Remote image redirect omitted its target.")
                    if redirect_count >= self.max_redirects:
                        raise InputError("Remote image exceeded the redirect limit.")
                    current_url = urljoin(current_url, location)
                    continue

                if response.status < 200 or response.status >= 300:
                    raise InputError("Remote image returned an unsuccessful response.")
                content_length = response.headers.get("Content-Length")
                if content_length is not None:
                    try:
                        declared_size = int(content_length)
                    except ValueError as exc:
                        raise InputError(
                            "Remote image returned an invalid Content-Length header."
                        ) from exc
                    if declared_size > self.max_bytes:
                        raise InputError(
                            "Remote image exceeds the configured size limit."
                        )

                chunks = []
                received = 0
                while True:
                    remaining = _remaining_timeout(deadline)
                    _set_response_timeout(connection, response, remaining)
                    chunk = response.read(64 * 1024)
                    _remaining_timeout(deadline)
                    if not chunk:
                        break
                    received += len(chunk)
                    if received > self.max_bytes:
                        raise InputError(
                            "Remote image exceeds the configured size limit."
                        )
                    chunks.append(chunk)
            except InputError:
                raise
            except TimeoutError as exc:
                raise InputError("Remote image request timed out.") from exc
            except (OSError, ValueError, http.client.HTTPException) as exc:
                raise InputError(
                    "Remote image returned an unsuccessful response."
                ) from exc
            finally:
                response.close()
                connection.close()

            hostname = parsed.hostname or ""
            if ":" in hostname:
                hostname = f"[{hostname}]"
            safe_netloc = hostname
            if parsed.port is not None:
                safe_netloc = f"{safe_netloc}:{parsed.port}"
            safe_url = urlunsplit((parsed.scheme, safe_netloc, parsed.path, "", ""))
            return self.read_image_from_bytes(
                b"".join(chunks),
                fix_img_size,
                input_policy=input_policy,
                input_spec=input_spec,
                path_input=safe_url,
            )

Explicit, bounded HTTP(S) image reader.

Base class for image reader.

All image readers should subclass it. All subclass should overwrite:

  • Methods:run, used for running the reading process and return a tensor.
Args
-----=
transform : transforms.Compose
Transform to be applied to the image.
device : torch.device
Torch device cpu or cuda.
optimize_transform : bool
Whether to optimize the transforms that are resizing

the image to a fixed size.

Ancestors

Inherited members