Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
868
venv/Lib/site-packages/imageio/plugins/pillow.py
Normal file
868
venv/Lib/site-packages/imageio/plugins/pillow.py
Normal file
|
|
@ -0,0 +1,868 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# imageio is distributed under the terms of the (new) BSD License.
|
||||
|
||||
""" Plugin that wraps the the Pillow library.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .. import formats
|
||||
from ..core import Format, image_as_uint
|
||||
|
||||
# Get info about pillow formats without having to import PIL
|
||||
from .pillow_info import pillow_formats, pillow_docs
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# todo: Pillow ImageGrab module supports grabbing the screen on Win and OSX.
|
||||
|
||||
|
||||
GENERIC_DOCS = """
|
||||
Parameters for reading
|
||||
----------------------
|
||||
|
||||
pilmode : str
|
||||
From the Pillow documentation:
|
||||
|
||||
* 'L' (8-bit pixels, grayscale)
|
||||
* 'P' (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* 'RGB' (3x8-bit pixels, true color)
|
||||
* 'RGBA' (4x8-bit pixels, true color with transparency mask)
|
||||
* 'CMYK' (4x8-bit pixels, color separation)
|
||||
* 'YCbCr' (3x8-bit pixels, color video format)
|
||||
* 'I' (32-bit signed integer pixels)
|
||||
* 'F' (32-bit floating point pixels)
|
||||
|
||||
PIL also provides limited support for a few special modes, including
|
||||
'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
|
||||
(true color with premultiplied alpha).
|
||||
|
||||
When translating a color image to grayscale (mode 'L', 'I' or 'F'),
|
||||
the library uses the ITU-R 601-2 luma transform::
|
||||
|
||||
L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
as_gray : bool
|
||||
If True, the image is converted using mode 'F'. When `mode` is
|
||||
not None and `as_gray` is True, the image is first converted
|
||||
according to `mode`, and the result is then "flattened" using
|
||||
mode 'F'.
|
||||
"""
|
||||
|
||||
|
||||
class PillowFormat(Format):
|
||||
"""
|
||||
Base format class for Pillow formats.
|
||||
"""
|
||||
|
||||
_pillow_imported = False
|
||||
_Image = None
|
||||
_modes = "i"
|
||||
_description = ""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PillowFormat, self).__init__(*args, **kwargs)
|
||||
# Used to synchronize _init_pillow(), see #244
|
||||
self._lock = threading.RLock()
|
||||
|
||||
@property
|
||||
def plugin_id(self):
|
||||
""" The PIL plugin id.
|
||||
"""
|
||||
return self._plugin_id # Set when format is created
|
||||
|
||||
def _init_pillow(self):
|
||||
with self._lock:
|
||||
if not self._pillow_imported:
|
||||
self._pillow_imported = True # more like tried to import
|
||||
import PIL
|
||||
|
||||
if not hasattr(PIL, "__version__"): # pragma: no cover
|
||||
raise ImportError(
|
||||
"Imageio Pillow plugin requires " "Pillow, not PIL!"
|
||||
)
|
||||
from PIL import Image
|
||||
|
||||
self._Image = Image
|
||||
elif self._Image is None: # pragma: no cover
|
||||
raise RuntimeError("Imageio Pillow plugin requires " "Pillow lib.")
|
||||
Image = self._Image
|
||||
|
||||
if self.plugin_id in ("PNG", "JPEG", "BMP", "GIF", "PPM"):
|
||||
Image.preinit()
|
||||
else:
|
||||
Image.init()
|
||||
return Image
|
||||
|
||||
def _can_read(self, request):
|
||||
Image = self._init_pillow()
|
||||
if request.mode[1] in (self.modes + "?"):
|
||||
if self.plugin_id in Image.OPEN:
|
||||
factory, accept = Image.OPEN[self.plugin_id]
|
||||
if accept:
|
||||
if request.firstbytes and accept(request.firstbytes):
|
||||
return True
|
||||
|
||||
def _can_write(self, request):
|
||||
Image = self._init_pillow()
|
||||
if request.mode[1] in (self.modes + "?"):
|
||||
if request.extension in self.extensions:
|
||||
if self.plugin_id in Image.SAVE:
|
||||
return True
|
||||
|
||||
class Reader(Format.Reader):
|
||||
def _open(self, pilmode=None, as_gray=False):
|
||||
Image = self.format._init_pillow()
|
||||
try:
|
||||
factory, accept = Image.OPEN[self.format.plugin_id]
|
||||
except KeyError:
|
||||
raise RuntimeError("Format %s cannot read images." % self.format.name)
|
||||
self._fp = self._get_file()
|
||||
self._im = factory(self._fp, "")
|
||||
if hasattr(Image, "_decompression_bomb_check"):
|
||||
Image._decompression_bomb_check(self._im.size)
|
||||
# Save the raw mode used by the palette for a BMP because it may not be the number of channels
|
||||
# When the data is read, imageio hands the palette to PIL to handle and clears the rawmode argument
|
||||
# However, there is a bug in PIL with handling animated GIFs with a different color palette on each frame.
|
||||
# This issue is resolved by using the raw palette data but the rawmode information is now lost. So we
|
||||
# store the raw mode for later use
|
||||
if self._im.palette and self._im.palette.dirty:
|
||||
self._im.palette.rawmode_saved = self._im.palette.rawmode
|
||||
pil_try_read(self._im)
|
||||
# Store args
|
||||
self._kwargs = dict(
|
||||
as_gray=as_gray, is_gray=_palette_is_grayscale(self._im)
|
||||
)
|
||||
# setting mode=None is not the same as just not providing it
|
||||
if pilmode is not None:
|
||||
self._kwargs["mode"] = pilmode
|
||||
# Set length
|
||||
self._length = 1
|
||||
if hasattr(self._im, "n_frames"):
|
||||
self._length = self._im.n_frames
|
||||
|
||||
def _get_file(self):
|
||||
self._we_own_fp = False
|
||||
return self.request.get_file()
|
||||
|
||||
def _close(self):
|
||||
save_pillow_close(self._im)
|
||||
if self._we_own_fp:
|
||||
self._fp.close()
|
||||
# else: request object handles closing the _fp
|
||||
|
||||
def _get_length(self):
|
||||
return self._length
|
||||
|
||||
def _seek(self, index):
|
||||
try:
|
||||
self._im.seek(index)
|
||||
except EOFError:
|
||||
raise IndexError("Could not seek to index %i" % index)
|
||||
|
||||
def _get_data(self, index):
|
||||
if index >= self._length:
|
||||
raise IndexError("Image index %i > %i" % (index, self._length))
|
||||
i = self._im.tell()
|
||||
if i > index:
|
||||
self._seek(index) # just try
|
||||
else:
|
||||
while i < index: # some formats need to be read in sequence
|
||||
i += 1
|
||||
self._seek(i)
|
||||
if self._im.palette and self._im.palette.dirty:
|
||||
self._im.palette.rawmode_saved = self._im.palette.rawmode
|
||||
self._im.getdata()[0]
|
||||
im = pil_get_frame(self._im, **self._kwargs)
|
||||
return im, self._im.info
|
||||
|
||||
def _get_meta_data(self, index):
|
||||
if not (index is None or index == 0):
|
||||
raise IndexError()
|
||||
return self._im.info
|
||||
|
||||
class Writer(Format.Writer):
|
||||
def _open(self):
|
||||
Image = self.format._init_pillow()
|
||||
try:
|
||||
self._save_func = Image.SAVE[self.format.plugin_id]
|
||||
except KeyError:
|
||||
raise RuntimeError("Format %s cannot write images." % self.format.name)
|
||||
self._fp = self.request.get_file()
|
||||
self._meta = {}
|
||||
self._written = False
|
||||
|
||||
def _close(self):
|
||||
pass # request object handled closing _fp
|
||||
|
||||
def _append_data(self, im, meta):
|
||||
if self._written:
|
||||
raise RuntimeError(
|
||||
"Format %s only supports single images." % self.format.name
|
||||
)
|
||||
# Pop unit dimension for grayscale images
|
||||
if im.ndim == 3 and im.shape[-1] == 1:
|
||||
im = im[:, :, 0]
|
||||
self._written = True
|
||||
self._meta.update(meta)
|
||||
img = ndarray_to_pil(
|
||||
im, self.format.plugin_id, self._meta.pop("prefer_uint8", True)
|
||||
)
|
||||
if "bits" in self._meta:
|
||||
img = img.quantize() # Make it a P image, so bits arg is used
|
||||
img.save(self._fp, format=self.format.plugin_id, **self._meta)
|
||||
save_pillow_close(img)
|
||||
|
||||
def set_meta_data(self, meta):
|
||||
self._meta.update(meta)
|
||||
|
||||
|
||||
class PNGFormat(PillowFormat):
|
||||
"""A PNG format based on Pillow.
|
||||
|
||||
This format supports grayscale, RGB and RGBA images.
|
||||
|
||||
Parameters for reading
|
||||
----------------------
|
||||
ignoregamma : bool
|
||||
Avoid gamma correction. Default True.
|
||||
pilmode : str
|
||||
From the Pillow documentation:
|
||||
|
||||
* 'L' (8-bit pixels, grayscale)
|
||||
* 'P' (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* 'RGB' (3x8-bit pixels, true color)
|
||||
* 'RGBA' (4x8-bit pixels, true color with transparency mask)
|
||||
* 'CMYK' (4x8-bit pixels, color separation)
|
||||
* 'YCbCr' (3x8-bit pixels, color video format)
|
||||
* 'I' (32-bit signed integer pixels)
|
||||
* 'F' (32-bit floating point pixels)
|
||||
|
||||
PIL also provides limited support for a few special modes, including
|
||||
'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
|
||||
(true color with premultiplied alpha).
|
||||
|
||||
When translating a color image to grayscale (mode 'L', 'I' or 'F'),
|
||||
the library uses the ITU-R 601-2 luma transform::
|
||||
|
||||
L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
as_gray : bool
|
||||
If True, the image is converted using mode 'F'. When `mode` is
|
||||
not None and `as_gray` is True, the image is first converted
|
||||
according to `mode`, and the result is then "flattened" using
|
||||
mode 'F'.
|
||||
|
||||
Parameters for saving
|
||||
---------------------
|
||||
optimize : bool
|
||||
If present and true, instructs the PNG writer to make the output file
|
||||
as small as possible. This includes extra processing in order to find
|
||||
optimal encoder settings.
|
||||
transparency:
|
||||
This option controls what color image to mark as transparent.
|
||||
dpi: tuple of two scalars
|
||||
The desired dpi in each direction.
|
||||
pnginfo: PIL.PngImagePlugin.PngInfo
|
||||
Object containing text tags.
|
||||
compress_level: int
|
||||
ZLIB compression level, a number between 0 and 9: 1 gives best speed,
|
||||
9 gives best compression, 0 gives no compression at all. Default is 9.
|
||||
When ``optimize`` option is True ``compress_level`` has no effect
|
||||
(it is set to 9 regardless of a value passed).
|
||||
compression: int
|
||||
Compatibility with the freeimage PNG format. If given, it overrides
|
||||
compress_level.
|
||||
icc_profile:
|
||||
The ICC Profile to include in the saved file.
|
||||
bits (experimental): int
|
||||
This option controls how many bits to store. If omitted,
|
||||
the PNG writer uses 8 bits (256 colors).
|
||||
quantize:
|
||||
Compatibility with the freeimage PNG format. If given, it overrides
|
||||
bits. In this case, given as a number between 1-256.
|
||||
dictionary (experimental): dict
|
||||
Set the ZLIB encoder dictionary.
|
||||
prefer_uint8: bool
|
||||
Let the PNG writer truncate uint16 image arrays to uint8 if their values fall
|
||||
within the range [0, 255]. Defaults to true for legacy compatibility, however
|
||||
it is recommended to set this to false to avoid unexpected behavior when
|
||||
saving e.g. weakly saturated images.
|
||||
"""
|
||||
|
||||
class Reader(PillowFormat.Reader):
|
||||
def _open(self, pilmode=None, as_gray=False, ignoregamma=True):
|
||||
return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray)
|
||||
|
||||
def _get_data(self, index):
|
||||
im, info = PillowFormat.Reader._get_data(self, index)
|
||||
if not self.request.kwargs.get("ignoregamma", True):
|
||||
# The gamma value in the file represents the gamma factor for the
|
||||
# hardware on the system where the file was created, and is meant
|
||||
# to be able to match the colors with the system on which the
|
||||
# image is shown. See also issue #366
|
||||
try:
|
||||
gamma = float(info["gamma"])
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
else:
|
||||
scale = float(65536 if im.dtype == np.uint16 else 255)
|
||||
gain = 1.0
|
||||
im[:] = ((im / scale) ** gamma) * scale * gain + 0.4999
|
||||
return im, info
|
||||
|
||||
# --
|
||||
|
||||
class Writer(PillowFormat.Writer):
|
||||
def _open(self, compression=None, quantize=None, interlaced=False, **kwargs):
|
||||
|
||||
# Better default for compression
|
||||
kwargs["compress_level"] = kwargs.get("compress_level", 9)
|
||||
|
||||
if compression is not None:
|
||||
if compression < 0 or compression > 9:
|
||||
raise ValueError("Invalid PNG compression level: %r" % compression)
|
||||
kwargs["compress_level"] = compression
|
||||
if quantize is not None:
|
||||
for bits in range(1, 9):
|
||||
if 2 ** bits == quantize:
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
"PNG quantize must be power of two, " "not %r" % quantize
|
||||
)
|
||||
kwargs["bits"] = bits
|
||||
if interlaced:
|
||||
logger.warning("PIL PNG writer cannot produce interlaced images.")
|
||||
|
||||
ok_keys = (
|
||||
"optimize",
|
||||
"transparency",
|
||||
"dpi",
|
||||
"pnginfo",
|
||||
"bits",
|
||||
"compress_level",
|
||||
"icc_profile",
|
||||
"dictionary",
|
||||
"prefer_uint8",
|
||||
)
|
||||
for key in kwargs:
|
||||
if key not in ok_keys:
|
||||
raise TypeError("Invalid arg for PNG writer: %r" % key)
|
||||
|
||||
PillowFormat.Writer._open(self)
|
||||
self._meta.update(kwargs)
|
||||
|
||||
def _append_data(self, im, meta):
|
||||
if str(im.dtype) == "uint16" and (im.ndim == 2 or im.shape[-1] == 1):
|
||||
im = image_as_uint(im, bitdepth=16)
|
||||
else:
|
||||
im = image_as_uint(im, bitdepth=8)
|
||||
PillowFormat.Writer._append_data(self, im, meta)
|
||||
|
||||
|
||||
class JPEGFormat(PillowFormat):
|
||||
"""A JPEG format based on Pillow.
|
||||
|
||||
This format supports grayscale, RGB and RGBA images.
|
||||
|
||||
Parameters for reading
|
||||
----------------------
|
||||
exifrotate : bool
|
||||
Automatically rotate the image according to exif flag. Default True.
|
||||
pilmode : str
|
||||
From the Pillow documentation:
|
||||
|
||||
* 'L' (8-bit pixels, grayscale)
|
||||
* 'P' (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* 'RGB' (3x8-bit pixels, true color)
|
||||
* 'RGBA' (4x8-bit pixels, true color with transparency mask)
|
||||
* 'CMYK' (4x8-bit pixels, color separation)
|
||||
* 'YCbCr' (3x8-bit pixels, color video format)
|
||||
* 'I' (32-bit signed integer pixels)
|
||||
* 'F' (32-bit floating point pixels)
|
||||
|
||||
PIL also provides limited support for a few special modes, including
|
||||
'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
|
||||
(true color with premultiplied alpha).
|
||||
|
||||
When translating a color image to grayscale (mode 'L', 'I' or 'F'),
|
||||
the library uses the ITU-R 601-2 luma transform::
|
||||
|
||||
L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
as_gray : bool
|
||||
If True, the image is converted using mode 'F'. When `mode` is
|
||||
not None and `as_gray` is True, the image is first converted
|
||||
according to `mode`, and the result is then "flattened" using
|
||||
mode 'F'.
|
||||
|
||||
Parameters for saving
|
||||
---------------------
|
||||
quality : scalar
|
||||
The compression factor of the saved image (1..100), higher
|
||||
numbers result in higher quality but larger file size. Default 75.
|
||||
progressive : bool
|
||||
Save as a progressive JPEG file (e.g. for images on the web).
|
||||
Default False.
|
||||
optimize : bool
|
||||
On saving, compute optimal Huffman coding tables (can reduce a few
|
||||
percent of file size). Default False.
|
||||
dpi : tuple of int
|
||||
The pixel density, ``(x,y)``.
|
||||
icc_profile : object
|
||||
If present and true, the image is stored with the provided ICC profile.
|
||||
If this parameter is not provided, the image will be saved with no
|
||||
profile attached.
|
||||
exif : dict
|
||||
If present, the image will be stored with the provided raw EXIF data.
|
||||
subsampling : str
|
||||
Sets the subsampling for the encoder. See Pillow docs for details.
|
||||
qtables : object
|
||||
Set the qtables for the encoder. See Pillow docs for details.
|
||||
"""
|
||||
|
||||
class Reader(PillowFormat.Reader):
|
||||
def _open(self, pilmode=None, as_gray=False, exifrotate=True):
|
||||
return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray)
|
||||
|
||||
def _get_file(self):
|
||||
# Pillow uses seek for JPG, so we cannot directly stream from web
|
||||
if self.request.filename.startswith(
|
||||
("http://", "https://")
|
||||
) or ".zip/" in self.request.filename.replace("\\", "/"):
|
||||
self._we_own_fp = True
|
||||
return open(self.request.get_local_filename(), "rb")
|
||||
else:
|
||||
self._we_own_fp = False
|
||||
return self.request.get_file()
|
||||
|
||||
def _get_data(self, index):
|
||||
im, info = PillowFormat.Reader._get_data(self, index)
|
||||
|
||||
# Handle exif
|
||||
if "exif" in info:
|
||||
from PIL.ExifTags import TAGS
|
||||
|
||||
info["EXIF_MAIN"] = {}
|
||||
for tag, value in self._im._getexif().items():
|
||||
decoded = TAGS.get(tag, tag)
|
||||
info["EXIF_MAIN"][decoded] = value
|
||||
|
||||
im = self._rotate(im, info)
|
||||
return im, info
|
||||
|
||||
def _rotate(self, im, meta):
|
||||
""" Use Orientation information from EXIF meta data to
|
||||
orient the image correctly. Similar code as in FreeImage plugin.
|
||||
"""
|
||||
if self.request.kwargs.get("exifrotate", True):
|
||||
try:
|
||||
ori = meta["EXIF_MAIN"]["Orientation"]
|
||||
except KeyError: # pragma: no cover
|
||||
pass # Orientation not available
|
||||
else: # pragma: no cover - we cannot touch all cases
|
||||
# www.impulseadventure.com/photo/exif-orientation.html
|
||||
if ori in [1, 2]:
|
||||
pass
|
||||
if ori in [3, 4]:
|
||||
im = np.rot90(im, 2)
|
||||
if ori in [5, 6]:
|
||||
im = np.rot90(im, 3)
|
||||
if ori in [7, 8]:
|
||||
im = np.rot90(im)
|
||||
if ori in [2, 4, 5, 7]: # Flipped cases (rare)
|
||||
im = np.fliplr(im)
|
||||
return im
|
||||
|
||||
# --
|
||||
|
||||
class Writer(PillowFormat.Writer):
|
||||
def _open(self, quality=75, progressive=False, optimize=False, **kwargs):
|
||||
|
||||
# Check quality - in Pillow it should be no higher than 95
|
||||
quality = int(quality)
|
||||
if quality < 1 or quality > 100:
|
||||
raise ValueError("JPEG quality should be between 1 and 100.")
|
||||
quality = min(95, max(1, quality))
|
||||
|
||||
kwargs["quality"] = quality
|
||||
kwargs["progressive"] = bool(progressive)
|
||||
kwargs["optimize"] = bool(progressive)
|
||||
|
||||
PillowFormat.Writer._open(self)
|
||||
self._meta.update(kwargs)
|
||||
|
||||
def _append_data(self, im, meta):
|
||||
if im.ndim == 3 and im.shape[-1] == 4:
|
||||
raise IOError("JPEG does not support alpha channel.")
|
||||
im = image_as_uint(im, bitdepth=8)
|
||||
PillowFormat.Writer._append_data(self, im, meta)
|
||||
return
|
||||
|
||||
|
||||
class JPEG2000Format(PillowFormat):
|
||||
"""A JPEG 2000 format based on Pillow.
|
||||
|
||||
This format supports grayscale and RGB images.
|
||||
|
||||
Parameters for reading
|
||||
----------------------
|
||||
pilmode : str
|
||||
From the Pillow documentation:
|
||||
|
||||
* 'L' (8-bit pixels, grayscale)
|
||||
* 'P' (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* 'RGB' (3x8-bit pixels, true color)
|
||||
* 'RGBA' (4x8-bit pixels, true color with transparency mask)
|
||||
* 'CMYK' (4x8-bit pixels, color separation)
|
||||
* 'YCbCr' (3x8-bit pixels, color video format)
|
||||
* 'I' (32-bit signed integer pixels)
|
||||
* 'F' (32-bit floating point pixels)
|
||||
|
||||
PIL also provides limited support for a few special modes, including
|
||||
'LA' ('L' with alpha), 'RGBX' (true color with padding) and 'RGBa'
|
||||
(true color with premultiplied alpha).
|
||||
|
||||
When translating a color image to grayscale (mode 'L', 'I' or 'F'),
|
||||
the library uses the ITU-R 601-2 luma transform::
|
||||
|
||||
L = R * 299/1000 + G * 587/1000 + B * 114/1000
|
||||
as_gray : bool
|
||||
If True, the image is converted using mode 'F'. When `mode` is
|
||||
not None and `as_gray` is True, the image is first converted
|
||||
according to `mode`, and the result is then "flattened" using
|
||||
mode 'F'.
|
||||
|
||||
Parameters for saving
|
||||
---------------------
|
||||
**quality_mode**
|
||||
Either `"rates"` or `"dB"` depending on the units you want to use to
|
||||
specify image quality.
|
||||
|
||||
**quality**
|
||||
Approximate size reduction (if quality mode is `rates`) or a signal to noise ratio
|
||||
in decibels (if quality mode is `dB`).
|
||||
|
||||
.. note::
|
||||
|
||||
To enable JPEG 2000 support, you need to build and install the OpenJPEG
|
||||
library, version 2.0.0 or higher, before building the Python Imaging
|
||||
Library.
|
||||
|
||||
Windows users can install the OpenJPEG binaries available on the
|
||||
OpenJPEG website, but must add them to their PATH in order to use PIL (if
|
||||
you fail to do this, you will get errors about not being able to load the
|
||||
``_imaging`` DLL).
|
||||
|
||||
"""
|
||||
|
||||
class Reader(PillowFormat.Reader):
|
||||
def _open(self, pilmode=None, as_gray=False):
|
||||
return PillowFormat.Reader._open(self, pilmode=pilmode, as_gray=as_gray)
|
||||
|
||||
def _get_file(self):
|
||||
# Pillow uses seek for JPG, so we cannot directly stream from web
|
||||
if self.request.filename.startswith(
|
||||
("http://", "https://")
|
||||
) or ".zip/" in self.request.filename.replace("\\", "/"):
|
||||
self._we_own_fp = True
|
||||
return open(self.request.get_local_filename(), "rb")
|
||||
else:
|
||||
self._we_own_fp = False
|
||||
return self.request.get_file()
|
||||
|
||||
def _get_data(self, index):
|
||||
im, info = PillowFormat.Reader._get_data(self, index)
|
||||
|
||||
# Handle exif
|
||||
if "exif" in info:
|
||||
from PIL.ExifTags import TAGS
|
||||
|
||||
info["EXIF_MAIN"] = {}
|
||||
for tag, value in self._im._getexif().items():
|
||||
decoded = TAGS.get(tag, tag)
|
||||
info["EXIF_MAIN"][decoded] = value
|
||||
|
||||
im = self._rotate(im, info)
|
||||
return im, info
|
||||
|
||||
def _rotate(self, im, meta):
|
||||
""" Use Orientation information from EXIF meta data to
|
||||
orient the image correctly. Similar code as in FreeImage plugin.
|
||||
"""
|
||||
if self.request.kwargs.get("exifrotate", True):
|
||||
try:
|
||||
ori = meta["EXIF_MAIN"]["Orientation"]
|
||||
except KeyError: # pragma: no cover
|
||||
pass # Orientation not available
|
||||
else: # pragma: no cover - we cannot touch all cases
|
||||
# www.impulseadventure.com/photo/exif-orientation.html
|
||||
if ori in [1, 2]:
|
||||
pass
|
||||
if ori in [3, 4]:
|
||||
im = np.rot90(im, 2)
|
||||
if ori in [5, 6]:
|
||||
im = np.rot90(im, 3)
|
||||
if ori in [7, 8]:
|
||||
im = np.rot90(im)
|
||||
if ori in [2, 4, 5, 7]: # Flipped cases (rare)
|
||||
im = np.fliplr(im)
|
||||
return im
|
||||
|
||||
# --
|
||||
|
||||
class Writer(PillowFormat.Writer):
|
||||
def _open(self, quality_mode="rates", quality=5, **kwargs):
|
||||
|
||||
# Check quality - in Pillow it should be no higher than 95
|
||||
if quality_mode not in {"rates", "dB"}:
|
||||
raise ValueError("Quality mode should be either 'rates' or 'dB'")
|
||||
|
||||
quality = float(quality)
|
||||
|
||||
if quality_mode == "rates" and (quality < 1 or quality > 1000):
|
||||
raise ValueError(
|
||||
"The quality value {} seems to be an invalid rate!".format(quality)
|
||||
)
|
||||
elif quality_mode == "dB" and (quality < 15 or quality > 100):
|
||||
raise ValueError(
|
||||
"The quality value {} seems to be an invalid PSNR!".format(quality)
|
||||
)
|
||||
|
||||
kwargs["quality_mode"] = quality_mode
|
||||
kwargs["quality_layers"] = [quality]
|
||||
|
||||
PillowFormat.Writer._open(self)
|
||||
self._meta.update(kwargs)
|
||||
|
||||
def _append_data(self, im, meta):
|
||||
if im.ndim == 3 and im.shape[-1] == 4:
|
||||
raise IOError(
|
||||
"The current implementation of JPEG 2000 does not support alpha channel."
|
||||
)
|
||||
im = image_as_uint(im, bitdepth=8)
|
||||
PillowFormat.Writer._append_data(self, im, meta)
|
||||
return
|
||||
|
||||
|
||||
def save_pillow_close(im):
|
||||
# see issue #216 and #300
|
||||
if hasattr(im, "close"):
|
||||
if hasattr(getattr(im, "fp", None), "close"):
|
||||
im.close()
|
||||
|
||||
|
||||
## Func from skimage
|
||||
|
||||
# This cells contains code from scikit-image, in particular from
|
||||
# http://github.com/scikit-image/scikit-image/blob/master/
|
||||
# skimage/io/_plugins/pil_plugin.py
|
||||
# The scikit-image license applies.
|
||||
|
||||
|
||||
def pil_try_read(im):
|
||||
try:
|
||||
# this will raise an IOError if the file is not readable
|
||||
im.getdata()[0]
|
||||
except IOError as e:
|
||||
site = "http://pillow.readthedocs.io/en/latest/installation.html"
|
||||
site += "#external-libraries"
|
||||
pillow_error_message = str(e)
|
||||
error_message = (
|
||||
'Could not load "%s" \n'
|
||||
'Reason: "%s"\n'
|
||||
"Please see documentation at: %s"
|
||||
% (im.filename, pillow_error_message, site)
|
||||
)
|
||||
raise ValueError(error_message)
|
||||
|
||||
|
||||
def _palette_is_grayscale(pil_image):
|
||||
if pil_image.mode != "P":
|
||||
return False
|
||||
elif pil_image.info.get("transparency", None): # see issue #475
|
||||
return False
|
||||
# get palette as an array with R, G, B columns
|
||||
palette = np.asarray(pil_image.getpalette()).reshape((256, 3))
|
||||
# Not all palette colors are used; unused colors have junk values.
|
||||
start, stop = pil_image.getextrema()
|
||||
valid_palette = palette[start : stop + 1]
|
||||
# Image is grayscale if channel differences (R - G and G - B)
|
||||
# are all zero.
|
||||
return np.allclose(np.diff(valid_palette), 0)
|
||||
|
||||
|
||||
def pil_get_frame(im, is_gray=None, as_gray=None, mode=None, dtype=None):
|
||||
"""
|
||||
is_gray: Whether the image *is* gray (by inspecting its palette).
|
||||
as_gray: Whether the resulting image must be converted to gaey.
|
||||
mode: The mode to convert to.
|
||||
"""
|
||||
|
||||
if is_gray is None:
|
||||
is_gray = _palette_is_grayscale(im)
|
||||
|
||||
frame = im
|
||||
|
||||
# Convert ...
|
||||
if mode is not None:
|
||||
# Mode is explicitly given ...
|
||||
if mode != im.mode:
|
||||
frame = im.convert(mode)
|
||||
elif as_gray:
|
||||
pass # don't do any auto-conversions (but do the explit one above)
|
||||
elif im.mode == "P" and is_gray:
|
||||
# Paletted images that are already gray by their palette
|
||||
# are converted so that the resulting numpy array is 2D.
|
||||
frame = im.convert("L")
|
||||
elif im.mode == "P":
|
||||
# Paletted images are converted to RGB/RGBA. We jump some loops to make
|
||||
# this work well.
|
||||
if im.info.get("transparency", None) is not None:
|
||||
# Let Pillow apply the transparency, see issue #210 and #246
|
||||
frame = im.convert("RGBA")
|
||||
elif im.palette.mode in ("RGB", "RGBA"):
|
||||
# We can do this ourselves. Pillow seems to sometimes screw
|
||||
# this up if a multi-gif has a palette for each frame ...
|
||||
# Create palette array
|
||||
p = np.frombuffer(im.palette.getdata()[1], np.uint8)
|
||||
# Restore the raw mode that was saved to be used to parse the palette
|
||||
if hasattr(im.palette, "rawmode_saved"):
|
||||
im.palette.rawmode = im.palette.rawmode_saved
|
||||
mode = im.palette.rawmode if im.palette.rawmode else im.palette.mode
|
||||
nchannels = len(mode)
|
||||
# Shape it.
|
||||
p.shape = -1, nchannels
|
||||
if p.shape[1] == 3 or (p.shape[1] == 4 and mode[-1] == "X"):
|
||||
p = np.column_stack((p[:, :3], 255 * np.ones(p.shape[0], p.dtype)))
|
||||
# Swap the axes if the mode is in BGR and not RGB
|
||||
if mode.startswith("BGR"):
|
||||
p = p[:, [2, 1, 0]] if p.shape[1] == 3 else p[:, [2, 1, 0, 3]]
|
||||
# Apply palette
|
||||
frame_paletted = np.array(im, np.uint8)
|
||||
try:
|
||||
frame = p[frame_paletted]
|
||||
except Exception:
|
||||
# Ok, let PIL do it. The introduction of the branch that
|
||||
# tests `im.info['transparency']` should make this happen
|
||||
# much less often, but let's keep it, to be safe.
|
||||
frame = im.convert("RGBA")
|
||||
else:
|
||||
# Let Pillow do it. Unlinke skimage, we always convert
|
||||
# to RGBA; palettes can be RGBA.
|
||||
if True: # im.format == 'PNG' and 'transparency' in im.info:
|
||||
frame = im.convert("RGBA")
|
||||
else:
|
||||
frame = im.convert("RGB")
|
||||
elif "A" in im.mode:
|
||||
frame = im.convert("RGBA")
|
||||
elif im.mode == "CMYK":
|
||||
frame = im.convert("RGB")
|
||||
|
||||
# Apply a post-convert if necessary
|
||||
if as_gray:
|
||||
frame = frame.convert("F") # Scipy compat
|
||||
elif not isinstance(frame, np.ndarray) and frame.mode == "1":
|
||||
# Workaround for crash in PIL. When im is 1-bit, the call array(im)
|
||||
# can cause a segfault, or generate garbage. See
|
||||
# https://github.com/scipy/scipy/issues/2138 and
|
||||
# https://github.com/python-pillow/Pillow/issues/350.
|
||||
#
|
||||
# This converts im from a 1-bit image to an 8-bit image.
|
||||
frame = frame.convert("L")
|
||||
|
||||
# Convert to numpy array
|
||||
if im.mode.startswith("I;16"):
|
||||
# e.g. in16 PNG's
|
||||
shape = im.size
|
||||
dtype = ">u2" if im.mode.endswith("B") else "<u2"
|
||||
if "S" in im.mode:
|
||||
dtype = dtype.replace("u", "i")
|
||||
frame = np.frombuffer(frame.tobytes(), dtype).copy()
|
||||
frame.shape = shape[::-1]
|
||||
else:
|
||||
# Use uint16 for PNG's in mode I
|
||||
if im.format == "PNG" and im.mode == "I" and dtype is None:
|
||||
dtype = "uint16"
|
||||
frame = np.array(frame, dtype=dtype)
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def ndarray_to_pil(arr, format_str=None, prefer_uint8=True):
|
||||
|
||||
from PIL import Image
|
||||
|
||||
if arr.ndim == 3:
|
||||
arr = image_as_uint(arr, bitdepth=8)
|
||||
mode = {3: "RGB", 4: "RGBA"}[arr.shape[2]]
|
||||
|
||||
elif format_str in ["png", "PNG"]:
|
||||
mode = "I;16"
|
||||
mode_base = "I"
|
||||
|
||||
if arr.dtype.kind == "f":
|
||||
arr = image_as_uint(arr)
|
||||
|
||||
elif prefer_uint8 and arr.max() < 256 and arr.min() >= 0:
|
||||
arr = arr.astype(np.uint8)
|
||||
mode = mode_base = "L"
|
||||
|
||||
else:
|
||||
arr = image_as_uint(arr, bitdepth=16)
|
||||
|
||||
else:
|
||||
arr = image_as_uint(arr, bitdepth=8)
|
||||
mode = "L"
|
||||
mode_base = "L"
|
||||
|
||||
if mode == "I;16" and int(getattr(Image, "__version__", "0").split(".")[0]) < 6:
|
||||
# Pillow < v6.0.0 has limited support for the "I;16" mode,
|
||||
# requiring us to fall back to this expensive workaround.
|
||||
# tobytes actually creates a copy of the image, which is costly.
|
||||
array_buffer = arr.tobytes()
|
||||
if arr.ndim == 2:
|
||||
im = Image.new(mode_base, arr.T.shape)
|
||||
im.frombytes(array_buffer, "raw", mode)
|
||||
else:
|
||||
image_shape = (arr.shape[1], arr.shape[0])
|
||||
im = Image.frombytes(mode, image_shape, array_buffer)
|
||||
return im
|
||||
else:
|
||||
return Image.fromarray(arr, mode)
|
||||
|
||||
|
||||
## End of code from scikit-image
|
||||
|
||||
|
||||
from .pillowmulti import GIFFormat, TIFFFormat
|
||||
|
||||
IGNORE_FORMATS = "MPEG"
|
||||
|
||||
SPECIAL_FORMATS = dict(
|
||||
PNG=PNGFormat,
|
||||
JPEG=JPEGFormat,
|
||||
GIF=GIFFormat,
|
||||
TIFF=TIFFFormat,
|
||||
JPEG2000=JPEG2000Format,
|
||||
)
|
||||
|
||||
|
||||
def register_pillow_formats():
|
||||
|
||||
for id, summary, ext in pillow_formats:
|
||||
if id in IGNORE_FORMATS:
|
||||
continue
|
||||
FormatCls = SPECIAL_FORMATS.get(id, PillowFormat)
|
||||
summary = FormatCls._description or summary
|
||||
format = FormatCls(id + "-PIL", summary, ext, FormatCls._modes)
|
||||
format._plugin_id = id
|
||||
if FormatCls is PillowFormat or not FormatCls.__doc__:
|
||||
format.__doc__ = pillow_docs[id] + GENERIC_DOCS
|
||||
formats.add_format(format)
|
||||
|
||||
|
||||
register_pillow_formats()
|
||||
Loading…
Add table
Add a link
Reference in a new issue