-
Notifications
You must be signed in to change notification settings - Fork 7.1k
PR: Enable libPNG support #2379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2b78943
23255aa
006ab0c
7b9ec24
f97a9f0
ac6d26e
b14912e
770cea5
0861b80
a42a029
b7a19ea
1afde4d
386fd5b
2b5c469
0341aa5
721e5e3
2186d68
b80fb08
3d153f0
36b0a8f
9d14d9e
852a289
3e86f49
021e767
e734175
58c6524
b9295c1
02fa9d9
6c757d4
c207eab
a1aa2e6
34fc7d6
3ed2044
eaaf658
3edae46
c17202e
741f855
a46f503
4bad033
b419fc1
83eff79
eb2846f
3563ef3
347383f
51f6b48
ad00442
4d82283
32b2207
9a5aefe
a44c3b5
dfcde68
d8d46d6
eea8552
8c7dc31
ee8148a
059fa42
0ed1af6
11d1a7a
3bb65ba
c334d7e
5650e59
7894836
2da51d4
78455ae
a0a383d
f143e2c
95cc941
1c9270a
ee388e5
462ed6c
989db57
6e9ad0e
dd43bcd
e542e48
b5fa45e
3e90556
4e09af0
c2e9bf3
8ac335e
2adb87e
44826a7
1e830ea
37c889b
3cca366
2a6ff9f
b89b349
264cb74
a0ce4ca
bd752aa
5259757
3013247
9d8b1b5
7c3ec51
158eec8
269d8e5
273dc1a
cfc7c75
d32a5f0
051425b
3bc7323
6b87895
af61f94
123fd3f
831749c
558b0cb
8b2f507
b560227
c6d3ebe
39a0334
923ae59
4a1357e
b2fd151
0fc3135
ce92995
2614a9c
bf41911
eb23830
c41800c
b1ff783
84dad44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ dependencies: | |
- pytest-cov | ||
- codecov | ||
- pip | ||
- libpng | ||
- ca-certificates | ||
- pip: | ||
- future | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,3 +21,5 @@ htmlcov | |
*.swo | ||
gen.yml | ||
.mypy_cache | ||
.vscode/ | ||
*.orig |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -10,6 +10,24 @@ setup_wheel_python | |||||
pip_install numpy pyyaml future ninja | ||||||
setup_pip_pytorch_version | ||||||
python setup.py clean | ||||||
|
||||||
# Copy binaries to be included in the wheel distribution | ||||||
if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then | ||||||
python_exec="$(which python)" | ||||||
bin_path=$(dirname $python_exec) | ||||||
env_path=$(dirname $bin_path) | ||||||
if [[ "$(uname)" == Darwin ]]; then | ||||||
# Include LibPNG | ||||||
cp "$env_path/lib/libpng16.dylib" torchvision | ||||||
else | ||||||
# Include libPNG | ||||||
cp "$bin_path/Library/lib/libpng.lib" torchvision | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
fi | ||||||
else | ||||||
# Include LibPNG | ||||||
cp "/usr/lib64/libpng.so" torchvision | ||||||
fi | ||||||
|
||||||
if [[ "$OSTYPE" == "msys" ]]; then | ||||||
IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel | ||||||
else | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
channel_sources: | ||
- defaults | ||
|
||
blas_impl: | ||
- mkl # [x86_64] | ||
c_compiler: | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,7 +3,7 @@ | |||||||||||||||||
import re | ||||||||||||||||||
import sys | ||||||||||||||||||
from setuptools import setup, find_packages | ||||||||||||||||||
from pkg_resources import get_distribution, DistributionNotFound | ||||||||||||||||||
from pkg_resources import parse_version, get_distribution, DistributionNotFound | ||||||||||||||||||
import subprocess | ||||||||||||||||||
import distutils.command.clean | ||||||||||||||||||
import distutils.spawn | ||||||||||||||||||
|
@@ -76,6 +76,65 @@ def write_version_file(): | |||||||||||||||||
requirements.append(pillow_req + pillow_ver) | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
def find_library(name, vision_include): | ||||||||||||||||||
this_dir = os.path.dirname(os.path.abspath(__file__)) | ||||||||||||||||||
build_prefix = os.environ.get('BUILD_PREFIX', None) | ||||||||||||||||||
is_conda_build = build_prefix is not None | ||||||||||||||||||
|
||||||||||||||||||
library_found = False | ||||||||||||||||||
conda_installed = False | ||||||||||||||||||
lib_folder = None | ||||||||||||||||||
include_folder = None | ||||||||||||||||||
library_header = '{0}.h'.format(name) | ||||||||||||||||||
|
||||||||||||||||||
print('Running build on conda-build: {0}'.format(is_conda_build)) | ||||||||||||||||||
if is_conda_build: | ||||||||||||||||||
# Add conda headers/libraries | ||||||||||||||||||
if os.name == 'nt': | ||||||||||||||||||
build_prefix = os.path.join(build_prefix, 'Library') | ||||||||||||||||||
include_folder = os.path.join(build_prefix, 'include') | ||||||||||||||||||
lib_folder = os.path.join(build_prefix, 'lib') | ||||||||||||||||||
library_header_path = os.path.join( | ||||||||||||||||||
include_folder, library_header) | ||||||||||||||||||
library_found = os.path.isfile(library_header_path) | ||||||||||||||||||
conda_installed = library_found | ||||||||||||||||||
else: | ||||||||||||||||||
# Check if using Anaconda to produce wheels | ||||||||||||||||||
conda = distutils.spawn.find_executable('conda') | ||||||||||||||||||
is_conda = conda is not None | ||||||||||||||||||
print('Running build on conda: {0}'.format(is_conda)) | ||||||||||||||||||
if is_conda: | ||||||||||||||||||
python_executable = sys.executable | ||||||||||||||||||
py_folder = os.path.dirname(python_executable) | ||||||||||||||||||
if os.name == 'nt': | ||||||||||||||||||
env_path = os.path.join(py_folder, 'Library') | ||||||||||||||||||
else: | ||||||||||||||||||
env_path = os.path.dirname(py_folder) | ||||||||||||||||||
lib_folder = os.path.join(env_path, 'lib') | ||||||||||||||||||
include_folder = os.path.join(env_path, 'include') | ||||||||||||||||||
library_header_path = os.path.join( | ||||||||||||||||||
include_folder, library_header) | ||||||||||||||||||
library_found = os.path.isfile(library_header_path) | ||||||||||||||||||
conda_installed = library_found | ||||||||||||||||||
|
||||||||||||||||||
if not library_found: | ||||||||||||||||||
if sys.platform == 'linux': | ||||||||||||||||||
library_found = os.path.exists('/usr/include/{0}'.format( | ||||||||||||||||||
library_header)) | ||||||||||||||||||
library_found = library_found or os.path.exists( | ||||||||||||||||||
'/usr/local/include/{0}'.format(library_header)) | ||||||||||||||||||
else: | ||||||||||||||||||
# Lookup in TORCHVISION_INCLUDE or in the package file | ||||||||||||||||||
package_path = [os.path.join(this_dir, 'torchvision')] | ||||||||||||||||||
for folder in vision_include + package_path: | ||||||||||||||||||
candidate_path = os.path.join(folder, library_header) | ||||||||||||||||||
library_found = os.path.exists(candidate_path) | ||||||||||||||||||
if library_found: | ||||||||||||||||||
break | ||||||||||||||||||
|
||||||||||||||||||
return library_found, conda_installed, include_folder, lib_folder | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
def get_extensions(): | ||||||||||||||||||
this_dir = os.path.dirname(os.path.abspath(__file__)) | ||||||||||||||||||
extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') | ||||||||||||||||||
|
@@ -171,6 +230,63 @@ def get_extensions(): | |||||||||||||||||
) | ||||||||||||||||||
) | ||||||||||||||||||
|
||||||||||||||||||
# ------------------- Torchvision extra extensions ------------------------ | ||||||||||||||||||
vision_include = os.environ.get('TORCHVISION_INCLUDE', None) | ||||||||||||||||||
vision_library = os.environ.get('TORCHVISION_LIBRARY', None) | ||||||||||||||||||
vision_include = (vision_include.split(os.pathsep) | ||||||||||||||||||
if vision_include is not None else []) | ||||||||||||||||||
vision_library = (vision_library.split(os.pathsep) | ||||||||||||||||||
if vision_library is not None else []) | ||||||||||||||||||
include_dirs += vision_include | ||||||||||||||||||
library_dirs = vision_library | ||||||||||||||||||
|
||||||||||||||||||
# Image reading extension | ||||||||||||||||||
image_macros = [] | ||||||||||||||||||
image_include = [extensions_dir] | ||||||||||||||||||
image_library = [] | ||||||||||||||||||
image_link_flags = [] | ||||||||||||||||||
|
||||||||||||||||||
# Locating libPNG | ||||||||||||||||||
libpng = distutils.spawn.find_executable('libpng-config') | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried this locally on a Windows box after doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest changing this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
png_found = libpng is not None | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
image_macros += [('PNG_FOUND', str(int(png_found)))] | ||||||||||||||||||
print('PNG found: {0}'.format(png_found)) | ||||||||||||||||||
if png_found: | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
png_version = subprocess.run([libpng, '--version'], | ||||||||||||||||||
stdout=subprocess.PIPE) | ||||||||||||||||||
png_version = png_version.stdout.strip().decode('utf-8') | ||||||||||||||||||
print('libpng version: {0}'.format(png_version)) | ||||||||||||||||||
png_version = parse_version(png_version) | ||||||||||||||||||
if png_version >= parse_version("1.6.0"): | ||||||||||||||||||
print('Building torchvision with PNG image support') | ||||||||||||||||||
png_lib = subprocess.run([libpng, '--libdir'], | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I saw this. Unluckily, on Windows, libpng-config doesn't exist. But there is a CMake configuration. Reading that file, it seems that you'll only need to link against |
||||||||||||||||||
stdout=subprocess.PIPE) | ||||||||||||||||||
png_include = subprocess.run([libpng, '--I_opts'], | ||||||||||||||||||
stdout=subprocess.PIPE) | ||||||||||||||||||
png_include = png_include.stdout.strip().decode('utf-8') | ||||||||||||||||||
_, png_include = png_include.split('-I') | ||||||||||||||||||
image_library += [png_lib.stdout.strip().decode('utf-8')] | ||||||||||||||||||
image_include += [png_include] | ||||||||||||||||||
image_link_flags.append('png' if os.name != 'nt' else 'libpng') | ||||||||||||||||||
else: | ||||||||||||||||||
print('libpng installed version is less than 1.6.0, ' | ||||||||||||||||||
'disabling PNG support') | ||||||||||||||||||
png_found = False | ||||||||||||||||||
|
||||||||||||||||||
image_path = os.path.join(extensions_dir, 'cpu', 'image') | ||||||||||||||||||
image_src = glob.glob(os.path.join(image_path, '*.cpp')) | ||||||||||||||||||
|
||||||||||||||||||
if png_found: | ||||||||||||||||||
ext_modules.append(extension( | ||||||||||||||||||
'torchvision.image', | ||||||||||||||||||
image_src, | ||||||||||||||||||
include_dirs=image_include + include_dirs + [image_path], | ||||||||||||||||||
library_dirs=image_library + library_dirs, | ||||||||||||||||||
define_macros=image_macros, | ||||||||||||||||||
libraries=image_link_flags, | ||||||||||||||||||
extra_compile_args=extra_compile_args | ||||||||||||||||||
)) | ||||||||||||||||||
|
||||||||||||||||||
ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') | ||||||||||||||||||
has_ffmpeg = ffmpeg_exe is not None | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -243,7 +359,9 @@ def run(self): | |||||||||||||||||
|
||||||||||||||||||
# Package info | ||||||||||||||||||
packages=find_packages(exclude=('test',)), | ||||||||||||||||||
|
||||||||||||||||||
package_data={ | ||||||||||||||||||
package_name: ['*.lib', '*.dylib', '*.so'] | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and .dll I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
}, | ||||||||||||||||||
zip_safe=False, | ||||||||||||||||||
install_requires=requirements, | ||||||||||||||||||
extras_require={ | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import os | ||
import unittest | ||
import sys | ||
|
||
import torch | ||
import torchvision | ||
from PIL import Image | ||
from torchvision.io.image import read_png, decode_png | ||
import numpy as np | ||
|
||
IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") | ||
IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") | ||
|
||
|
||
def get_images(directory, img_ext): | ||
assert os.path.isdir(directory) | ||
for root, _, files in os.walk(directory): | ||
for fl in files: | ||
_, ext = os.path.splitext(fl) | ||
if ext == img_ext: | ||
yield os.path.join(root, fl) | ||
|
||
|
||
class ImageTester(unittest.TestCase): | ||
def test_read_png(self): | ||
for img_path in get_images(IMAGE_DIR, "png"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .png |
||
img_pil = torch.from_numpy(np.array(Image.open(img_path))) | ||
img_lpng = read_png(img_path) | ||
self.assertEqual(img_lpng, img_pil) | ||
|
||
def test_decode_png(self): | ||
for img_path in get_images(IMAGE_DIR, "png"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .png |
||
img_pil = torch.from_numpy(np.array(Image.open(img_path))) | ||
size = os.path.getsize(img_path) | ||
img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) | ||
self.assertEqual(img_lpng, img_pil) | ||
|
||
self.assertEqual(decode_png(torch.empty()), torch.empty()) | ||
self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
|
||
#include "image.h" | ||
#include <ATen/ATen.h> | ||
#include <Python.h> | ||
|
||
// If we are in a Windows environment, we need to define | ||
// initialization functions for the _custom_ops extension | ||
#ifdef _WIN32 | ||
#if PY_MAJOR_VERSION < 3 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: It is not no longer needed as PyTorch has dropped Python 2 support. |
||
PyMODINIT_FUNC init_image(void) { | ||
// No need to do anything. | ||
return NULL; | ||
} | ||
#else | ||
PyMODINIT_FUNC PyInit_image(void) { | ||
// No need to do anything. | ||
return NULL; | ||
} | ||
#endif | ||
#endif | ||
|
||
static auto registry = | ||
torch::RegisterOperators().op("image::decode_png", &decodePNG); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
#pragma once | ||
|
||
#include <torch/script.h> | ||
#include <torch/torch.h> | ||
#include "readpng_cpu.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the same is needed for Windows in https://github.com/pytorch/vision/blob/master/.circleci/unittest/windows/scripts/environment.yml ? Not sure why tests are not failing there