From 978d41ea7e8d20c4d30186d4803f42b696fb071d Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Thu, 13 Feb 2020 14:40:17 +0100 Subject: [PATCH 1/8] Add support for png decoding on linux - Pipelines are timing out, increasing to 30 minutes. - Vendors zlib and add's libpng as a dependency. - Ship libpng.so and zlib.so with package on linux --- .circleci/config.yml | 17 ++++- .circleci/config.yml.in | 17 ++++- .gitmodules | 6 ++ .travis.yml | 4 +- CMakeLists.txt | 17 ++++- setup.py | 59 ++++++++++++++++- test/test_image.py | 44 +++++++++++++ third_party/libpng | 1 + third_party/zlib | 1 + torchvision/csrc/cpu/image/readpng_cpu.cpp | 75 ++++++++++++++++++++++ torchvision/csrc/cpu/image/readpng_cpu.h | 6 ++ torchvision/csrc/image.h | 3 + torchvision/csrc/vision.cpp | 6 ++ torchvision/io/image.py | 48 ++++++++++++++ 14 files changed, 293 insertions(+), 11 deletions(-) create mode 100644 .gitmodules create mode 100644 test/test_image.py create mode 160000 third_party/libpng create mode 160000 third_party/zlib create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h create mode 100644 torchvision/csrc/image.h create mode 100644 torchvision/io/image.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 364cdd5f0b9..9668b59915f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,6 +21,9 @@ commands: description: "checkout merge branch" steps: - checkout + - run: + name: initialize submodules + command: git submodule update --init --recursive # - run: # name: Checkout merge branch # command: | @@ -83,7 +86,9 @@ jobs: resource_class: 2xlarge+ steps: - checkout_merge - - run: packaging/build_wheel.sh + - run: + command: packaging/build_wheel.sh + no_output_timeout: 30m - store_artifacts: path: dist - persist_to_workspace: @@ -98,7 +103,10 @@ jobs: resource_class: 2xlarge+ steps: - checkout_merge - - run: packaging/build_conda.sh + - run: + command: packaging/build_conda.sh + no_output_timeout: 30m + - store_artifacts: path: /opt/conda/conda-bld/linux-64 - persist_to_workspace: @@ -167,6 +175,7 @@ jobs: - run: name: Build and run tests + no_output_timeout: 30m command: | set -e @@ -191,6 +200,7 @@ jobs: conda activate base conda install -yq conda-build "conda-package-handling!=1.5.0" bash packaging/build_conda.sh + no_output_timeout: 30m shell: powershell.exe - store_test_results: path: build_results/ @@ -207,6 +217,7 @@ jobs: conda activate base conda install -yq conda-build "conda-package-handling!=1.5.0" bash packaging/build_conda.sh + no_output_timeout: 30m shell: powershell.exe binary_macos_wheel: @@ -219,6 +230,7 @@ jobs: # Cannot easily deduplicate this as source'ing activate # will set environment variables which we need to propagate # to build_wheel.sh + no_output_timeout: 30m command: | curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b @@ -238,6 +250,7 @@ jobs: steps: - checkout_merge - run: + no_output_timeout: 30m command: | curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index de5ec111684..6517daaca37 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -21,6 +21,9 @@ commands: description: "checkout merge branch" steps: - checkout + - run: + name: initialize submodules + command: git submodule update --init --recursive # - run: # name: Checkout merge branch # command: | @@ -83,7 +86,9 @@ jobs: resource_class: 2xlarge+ steps: - checkout_merge - - run: packaging/build_wheel.sh + - run: + command: packaging/build_wheel.sh + no_output_timeout: 30m - store_artifacts: path: dist - persist_to_workspace: @@ -98,7 +103,10 @@ jobs: resource_class: 2xlarge+ steps: - checkout_merge - - run: packaging/build_conda.sh + - run: + command: packaging/build_conda.sh + no_output_timeout: 30m + - store_artifacts: path: /opt/conda/conda-bld/linux-64 - persist_to_workspace: @@ -167,6 +175,7 @@ jobs: - run: name: Build and run tests + no_output_timeout: 30m command: | set -e @@ -191,6 +200,7 @@ jobs: conda activate base conda install -yq conda-build "conda-package-handling!=1.5.0" bash packaging/build_conda.sh + no_output_timeout: 30m shell: powershell.exe - store_test_results: path: build_results/ @@ -207,6 +217,7 @@ jobs: conda activate base conda install -yq conda-build "conda-package-handling!=1.5.0" bash packaging/build_conda.sh + no_output_timeout: 30m shell: powershell.exe binary_macos_wheel: @@ -219,6 +230,7 @@ jobs: # Cannot easily deduplicate this as source'ing activate # will set environment variables which we need to propagate # to build_wheel.sh + no_output_timeout: 30m command: | curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b @@ -238,6 +250,7 @@ jobs: steps: - checkout_merge - run: + no_output_timeout: 30m command: | curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..55b372801aa --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/libpng"] + path = third_party/libpng + url = https://github.com/glennrp/libpng +[submodule "third_party/zlib"] + path = third_party/zlib + url = https://github.com/madler/zlib diff --git a/.travis.yml b/.travis.yml index 1b6ecb7a65b..51b518833ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,8 +67,8 @@ install: cd - script: - - pytest --cov-config .coveragerc --cov torchvision --cov $TV_INSTALL_PATH -k 'not TestVideoReader and not TestVideoTransforms' test - - pytest test/test_hub.py + - travis_wait 60 pytest --cov-config .coveragerc --cov torchvision --cov $TV_INSTALL_PATH -k 'not TestVideoReader and not TestVideoTransforms' test + - travis_wait 60 pytest test/test_hub.py after_success: # Necessary to run coverage combine to rewrite paths from diff --git a/CMakeLists.txt b/CMakeLists.txt index fcedf41e5ab..fd4baaa96e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ if(WITH_CUDA) add_definitions(-D__CUDA_NO_HALF_OPERATORS__) endif() +if(Unix) + add_subdirectory("third_party/libpng") +endif() + find_package(Python3 COMPONENTS Development) find_package(Torch REQUIRED) @@ -21,8 +25,17 @@ endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) +file(GLOB IMAGE_HEADERS torchvision/csrc/image.h) +file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.h torchvision/csrc/cpu/image/*.cpp) + +if(Unix) + add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} {IMAGE_SOURCES}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python "${PNG_LIBRARIES}") +else() + add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) +endif() + set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE diff --git a/setup.py b/setup.py index 71d420573ed..285e9e952cc 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ import subprocess import distutils.command.clean import distutils.spawn +import multiprocessing import glob import shutil @@ -83,9 +84,20 @@ def get_extensions(): main_file = glob.glob(os.path.join(extensions_dir, '*.cpp')) source_cpu = glob.glob(os.path.join(extensions_dir, 'cpu', '*.cpp')) + source_image_cpu = glob.glob(os.path.join(extensions_dir, 'cpu', 'image', '*.cpp')) source_cuda = glob.glob(os.path.join(extensions_dir, 'cuda', '*.cu')) sources = main_file + source_cpu + + libraries = [] + extra_compile_args = {} + third_party_search_directories = [] + + if sys.platform.startswith('linux'): + sources = sources + source_image_cpu + libraries.append('png') + third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) + extension = CppExtension compile_cpp_tests = os.getenv('WITH_CPP_MODELS_TEST', '0') == '1' @@ -142,9 +154,12 @@ def get_extensions(): extension( 'torchvision._C', sources, - include_dirs=include_dirs, + libraries=libraries, + library_dirs=third_party_search_directories, + include_dirs=include_dirs + third_party_search_directories, define_macros=define_macros, extra_compile_args=extra_compile_args, + runtime_library_dirs=['lib'] ) ] if compile_cpp_tests: @@ -197,6 +212,43 @@ def run(self): distutils.command.clean.clean.run(self) +def throw_of_failure(command): + ret = os.system(command) + if ret != 0: + raise Exception("{} failed".format(command)) + + +def build_deps(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + if sys.platform.startswith('linux'): + cpu_count = multiprocessing.cpu_count() + os.chdir("third_party/zlib/") + throw_of_failure('cmake .') + throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) + os.chdir(this_dir) + + zlib_path = os.path.join(this_dir, "third_party/zlib") + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) + os.chdir("third_party/libpng/") + os.system('cmake {} .'.format(libpng_cmake_options)) + throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) + os.chdir(this_dir) + + +def build_ext_with_dependencies(self): + build_deps() + return BuildExtension.with_options(no_python_abi_suffix=True)(self) + + +data_files = [] +if sys.platform.startswith('linux'): + data_files = [ + ('torchvision/lib', [ + 'third_party/zlib/libz.so', + 'third_party/libpng/libpng.so']) + ] + + setup( # Metadata name=package_name, @@ -218,7 +270,8 @@ def run(self): }, ext_modules=get_extensions(), cmdclass={ - 'build_ext': BuildExtension.with_options(no_python_abi_suffix=True), + 'build_ext': build_ext_with_dependencies, 'clean': clean, - } + }, + data_files=data_files ) diff --git a/test/test_image.py b/test/test_image.py new file mode 100644 index 00000000000..2dee7912aa7 --- /dev/null +++ b/test/test_image.py @@ -0,0 +1,44 @@ +import os +import unittest +import sys + +import torch +from PIL import Image +if sys.platform.startswith('linux'): + from torchvision.io.image import read_png, decode_png +import numpy as np + +IMAGE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "fakedata", "imagefolder") + + +def get_images(directory, img_ext): + assert os.path.isdir(directory) + for root, dir, 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): + @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") + def test_read_png(self): + for img_path in get_images(IMAGE_DIR, "png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + img_lpng = read_png(img_path) + self.assertEqual(img_lpng, img_pil) + + @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") + def test_decode_png(self): + for img_path in get_images(IMAGE_DIR, "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() diff --git a/third_party/libpng b/third_party/libpng new file mode 160000 index 00000000000..a40189cf881 --- /dev/null +++ b/third_party/libpng @@ -0,0 +1 @@ +Subproject commit a40189cf881e9f0db80511c382292a5604c3c3d1 diff --git a/third_party/zlib b/third_party/zlib new file mode 160000 index 00000000000..cacf7f1d4e3 --- /dev/null +++ b/third_party/zlib @@ -0,0 +1 @@ +Subproject commit cacf7f1d4e3d44d871b605da3b647f07d718623f diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp new file mode 100644 index 00000000000..c6581f168b1 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.cpp @@ -0,0 +1,75 @@ +#include "readpng_cpu.h" + +#include +#include +#include + +torch::Tensor decodePNG(const torch::Tensor& data) { + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") + auto info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + // Seems redundant with the if statement. done here to avoid leaking memory. + TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") + } + + auto datap = data.accessor().data(); + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(false, "Internal error."); + } + auto is_png = !png_sig_cmp(datap, 0, 8); + TORCH_CHECK(is_png, "Content is not png!") + + struct Reader { + png_const_bytep ptr; + } reader; + reader.ptr = png_const_bytep(datap) + 8; + + auto read_callback = + [](png_structp png_ptr, png_bytep output, png_size_t bytes) { + auto reader = static_cast(png_get_io_ptr(png_ptr)); + std::copy(reader->ptr, reader->ptr + bytes, output); + reader->ptr += bytes; + }; + png_set_sig_bytes(png_ptr, 8); + png_set_read_fn(png_ptr, &reader, read_callback); + png_read_info(png_ptr, info_ptr); + + png_uint_32 width, height; + int bit_depth, color_type; + auto retval = png_get_IHDR( + png_ptr, + info_ptr, + &width, + &height, + &bit_depth, + &color_type, + nullptr, + nullptr, + nullptr); + + if (retval != 1) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(retval == 1, "Could read image metadata from content.") + } + if (color_type != PNG_COLOR_TYPE_RGB) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK( + color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") + } + + auto tensor = + torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); + auto ptr = tensor.accessor().data(); + auto bytes = png_get_rowbytes(png_ptr, info_ptr); + for (decltype(height) i = 0; i < height; ++i) { + png_read_row(png_ptr, ptr, nullptr); + ptr += bytes; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return tensor; +} diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h new file mode 100644 index 00000000000..d2151a43aa9 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/csrc/image.h b/torchvision/csrc/image.h new file mode 100644 index 00000000000..01d2063564d --- /dev/null +++ b/torchvision/csrc/image.h @@ -0,0 +1,3 @@ +#pragma once + +#include "cpu/image/readpng_cpu.h" diff --git a/torchvision/csrc/vision.cpp b/torchvision/csrc/vision.cpp index ed8d4134831..0556e921f7f 100644 --- a/torchvision/csrc/vision.cpp +++ b/torchvision/csrc/vision.cpp @@ -11,6 +11,9 @@ #include "ROIAlign.h" #include "ROIPool.h" #include "empty_tensor_op.h" +#ifdef __linux__ +#include "image.h" +#endif #include "nms.h" // If we are in a Windows environment, we need to define @@ -49,4 +52,7 @@ static auto registry = .op("torchvision::ps_roi_align", &ps_roi_align) .op("torchvision::ps_roi_pool", &ps_roi_pool) .op("torchvision::deform_conv2d", &deform_conv2d) +#ifdef __linux__ + .op("torchvision::decode_png", &decodePNG) +#endif .op("torchvision::_cuda_version", &_cuda_version); diff --git a/torchvision/io/image.py b/torchvision/io/image.py new file mode 100644 index 00000000000..bbf7470b097 --- /dev/null +++ b/torchvision/io/image.py @@ -0,0 +1,48 @@ +import torch +from torch import nn, Tensor +import os + + +def decode_png(input): + # type: (Tensor) -> Tensor + """ + Decodes a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or len(input) == 0: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + output = torch.ops.torchvision.decode_png(input) + return output + + +def read_png(path): + # type: (str) -> Tensor + """ + Reads a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + path (str): path of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_png(data) + From daba9d0ac1b722050ee41615627a5cd5046a26a2 Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Mon, 17 Feb 2020 21:33:17 +0100 Subject: [PATCH 2/8] Fix windows pipeline error --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 285e9e952cc..b1c1493bc8d 100644 --- a/setup.py +++ b/setup.py @@ -93,10 +93,13 @@ def get_extensions(): extra_compile_args = {} third_party_search_directories = [] + + runtime_library_dirs = None if sys.platform.startswith('linux'): sources = sources + source_image_cpu libraries.append('png') third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) + runtime_library_dirs = ['lib'] extension = CppExtension @@ -159,7 +162,7 @@ def get_extensions(): include_dirs=include_dirs + third_party_search_directories, define_macros=define_macros, extra_compile_args=extra_compile_args, - runtime_library_dirs=['lib'] + runtime_library_dirs=runtime_library_dirs ) ] if compile_cpp_tests: @@ -274,4 +277,4 @@ def build_ext_with_dependencies(self): 'clean': clean, }, data_files=data_files -) +) \ No newline at end of file From e7587485fe2c3dd55dd8b868e683dd56c30da71f Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Mon, 17 Feb 2020 22:10:27 +0100 Subject: [PATCH 3/8] Statically linking zlib and libpng --- setup.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index b1c1493bc8d..c57f459f248 100644 --- a/setup.py +++ b/setup.py @@ -93,13 +93,13 @@ def get_extensions(): extra_compile_args = {} third_party_search_directories = [] - - runtime_library_dirs = None + extra_objects = [] if sys.platform.startswith('linux'): sources = sources + source_image_cpu libraries.append('png') third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) runtime_library_dirs = ['lib'] + extra_objects = ['third_party/zlib/libz.a', 'third_party/libpng/libpng.a'] extension = CppExtension @@ -117,7 +117,6 @@ def get_extensions(): define_macros = [] - extra_compile_args = {} if (torch.cuda.is_available() and CUDA_HOME is not None) or os.getenv('FORCE_CUDA', '0') == '1': extension = CUDAExtension sources += source_cuda @@ -162,7 +161,7 @@ def get_extensions(): include_dirs=include_dirs + third_party_search_directories, define_macros=define_macros, extra_compile_args=extra_compile_args, - runtime_library_dirs=runtime_library_dirs + extra_objects=extra_objects ) ] if compile_cpp_tests: @@ -231,9 +230,9 @@ def build_deps(): os.chdir(this_dir) zlib_path = os.path.join(this_dir, "third_party/zlib") - libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) os.chdir("third_party/libpng/") - os.system('cmake {} .'.format(libpng_cmake_options)) + os.system('cmake -DCMAKE_C_FLAGS="-fPIC" {} .'.format(libpng_cmake_options)) throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) os.chdir(this_dir) @@ -243,15 +242,6 @@ def build_ext_with_dependencies(self): return BuildExtension.with_options(no_python_abi_suffix=True)(self) -data_files = [] -if sys.platform.startswith('linux'): - data_files = [ - ('torchvision/lib', [ - 'third_party/zlib/libz.so', - 'third_party/libpng/libpng.so']) - ] - - setup( # Metadata name=package_name, @@ -276,5 +266,4 @@ def build_ext_with_dependencies(self): 'build_ext': build_ext_with_dependencies, 'clean': clean, }, - data_files=data_files ) \ No newline at end of file From 7ee1d028b98a675cda2b911c8b276b32fa2ada01 Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Thu, 20 Feb 2020 07:04:35 +0100 Subject: [PATCH 4/8] Add's support for macos - Installs cmake on macos - Fix conda not found. in macos_wheel - Update $PATH on macOs_wheel for cmake - activate tests for macos --- .circleci/config.yml | 3 ++- .circleci/config.yml.in | 3 ++- packaging/pkg_helpers.bash | 1 + setup.py | 12 +++++++----- test/test_image.py | 8 ++++---- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9668b59915f..d9d7ab2e0bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -235,6 +235,7 @@ jobs: curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b source $HOME/miniconda3/bin/activate + conda install -yq cmake packaging/build_wheel.sh - store_artifacts: path: dist @@ -255,7 +256,7 @@ jobs: curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b source $HOME/miniconda3/bin/activate - conda install -yq conda-build + conda install -yq conda-build cmake packaging/build_conda.sh - store_artifacts: path: /Users/distiller/miniconda3/conda-bld/osx-64 diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index 6517daaca37..8d06d5069cc 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -235,6 +235,7 @@ jobs: curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b source $HOME/miniconda3/bin/activate + conda install -yq cmake packaging/build_wheel.sh - store_artifacts: path: dist @@ -255,7 +256,7 @@ jobs: curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh sh conda.sh -b source $HOME/miniconda3/bin/activate - conda install -yq conda-build + conda install -yq conda-build cmake packaging/build_conda.sh - store_artifacts: path: /Users/distiller/miniconda3/conda-bld/osx-64 diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index d13e77cb637..513df8b3503 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -153,6 +153,7 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" + export PATH="$PATH:/Users/distiller/miniconda3/bin" else case "$PYTHON_VERSION" in 2.7) diff --git a/setup.py b/setup.py index c57f459f248..2ef86e646f9 100644 --- a/setup.py +++ b/setup.py @@ -94,11 +94,10 @@ def get_extensions(): third_party_search_directories = [] extra_objects = [] - if sys.platform.startswith('linux'): + if sys.platform.startswith('linux') or sys.platform.startswith("darwin"): sources = sources + source_image_cpu libraries.append('png') third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) - runtime_library_dirs = ['lib'] extra_objects = ['third_party/zlib/libz.a', 'third_party/libpng/libpng.a'] extension = CppExtension @@ -222,7 +221,7 @@ def throw_of_failure(command): def build_deps(): this_dir = os.path.dirname(os.path.abspath(__file__)) - if sys.platform.startswith('linux'): + if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): cpu_count = multiprocessing.cpu_count() os.chdir("third_party/zlib/") throw_of_failure('cmake .') @@ -230,7 +229,10 @@ def build_deps(): os.chdir(this_dir) zlib_path = os.path.join(this_dir, "third_party/zlib") - libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) + if sys.platform.startswith("linux"): + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) + if sys.platform.startswith("darwin"): + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.dylib".format(zlib_path=zlib_path) os.chdir("third_party/libpng/") os.system('cmake -DCMAKE_C_FLAGS="-fPIC" {} .'.format(libpng_cmake_options)) throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) @@ -266,4 +268,4 @@ def build_ext_with_dependencies(self): 'build_ext': build_ext_with_dependencies, 'clean': clean, }, -) \ No newline at end of file +) diff --git a/test/test_image.py b/test/test_image.py index 2dee7912aa7..4e358018a4b 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -21,24 +21,24 @@ def get_images(directory, img_ext): class ImageTester(unittest.TestCase): - @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") + @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") def test_read_png(self): for img_path in get_images(IMAGE_DIR, "png"): img_pil = torch.from_numpy(np.array(Image.open(img_path))) img_lpng = read_png(img_path) self.assertEqual(img_lpng, img_pil) - @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") + @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") def test_decode_png(self): for img_path in get_images(IMAGE_DIR, "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() From 7d757acdc294c47f5961384bc0b6e1a148d6cb4f Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Sun, 23 Feb 2020 13:59:22 +0100 Subject: [PATCH 5/8] Improve errors handling and fix unit tests --- test/test_image.py | 20 +++++++++++++------- torchvision/io/image.py | 7 +++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/test/test_image.py b/test/test_image.py index 4e358018a4b..b681b615d16 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -4,7 +4,7 @@ import torch from PIL import Image -if sys.platform.startswith('linux'): +if sys.platform.startswith('linux') or sys.platform.startswith("darwin"): from torchvision.io.image import read_png, decode_png import numpy as np @@ -23,21 +23,27 @@ def get_images(directory, img_ext): class ImageTester(unittest.TestCase): @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") def test_read_png(self): - for img_path in get_images(IMAGE_DIR, "png"): + for img_path in get_images(IMAGE_DIR, ".png"): img_pil = torch.from_numpy(np.array(Image.open(img_path))) img_lpng = read_png(img_path) - self.assertEqual(img_lpng, img_pil) + self.assertTrue(torch.equal(img_lpng, img_pil)) @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") def test_decode_png(self): - for img_path in get_images(IMAGE_DIR, "png"): + for img_path in get_images(IMAGE_DIR, ".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.assertTrue(torch.equal(img_lpng, img_pil)) - self.assertEqual(decode_png(torch.empty()), torch.empty()) - self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) + with self.assertRaisesRegex(ValueError, "Expected a non empty 1-dimensional tensor."): + decode_png(torch.empty((100, 1), dtype=torch.uint8)) + + with self.assertRaisesRegex(ValueError, "Expected a torch.uint8 tensor."): + decode_png(torch.empty((100, ), dtype=torch.float16)) + + with self.assertRaisesRegex(ValueError, "Invalid png input."): + decode_png(torch.empty((100), dtype=torch.uint8)) if __name__ == '__main__': diff --git a/torchvision/io/image.py b/torchvision/io/image.py index bbf7470b097..4ae450d2fa2 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -16,12 +16,15 @@ def decode_png(input): Returns: output (Tensor[image_width, image_height, 3]) """ - if not isinstance(input, torch.Tensor) or len(input) == 0: + if not isinstance(input, torch.Tensor) or len(input) == 0 or input.dim() != 1: raise ValueError("Expected a non empty 1-dimensional tensor.") if not input.dtype == torch.uint8: raise ValueError("Expected a torch.uint8 tensor.") - output = torch.ops.torchvision.decode_png(input) + try: + output = torch.ops.torchvision.decode_png(input) + except RuntimeError: + raise ValueError("Invalid png input.") return output From 08a70aff4a1da21f31410d6f2d873bb329a9bcfb Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Wed, 4 Mar 2020 07:52:05 +0100 Subject: [PATCH 6/8] Attempt to fix conda builds --- packaging/torchvision/meta.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index a97bc429e32..f5f92e9daf4 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -7,7 +7,8 @@ source: requirements: build: - - {{ compiler('c') }} # [win] + - {{ compiler('c') }} + - {{ compiler('cxx') }} host: - python From e264be1dcc16f677768fe57d138b1a7ecc89a43b Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Wed, 4 Mar 2020 08:06:12 +0100 Subject: [PATCH 7/8] Revert "Statically linking zlib and libpng" This reverts commit f7bc8a4f38b19d21f4d94db052ce736954ec2146. --- setup.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 2ef86e646f9..8ba171dce2e 100644 --- a/setup.py +++ b/setup.py @@ -93,12 +93,12 @@ def get_extensions(): extra_compile_args = {} third_party_search_directories = [] - extra_objects = [] - if sys.platform.startswith('linux') or sys.platform.startswith("darwin"): + runtime_library_dirs = None + if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): sources = sources + source_image_cpu libraries.append('png') third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) - extra_objects = ['third_party/zlib/libz.a', 'third_party/libpng/libpng.a'] + runtime_library_dirs = ['lib'] extension = CppExtension @@ -116,6 +116,7 @@ def get_extensions(): define_macros = [] + extra_compile_args = {} if (torch.cuda.is_available() and CUDA_HOME is not None) or os.getenv('FORCE_CUDA', '0') == '1': extension = CUDAExtension sources += source_cuda @@ -160,7 +161,7 @@ def get_extensions(): include_dirs=include_dirs + third_party_search_directories, define_macros=define_macros, extra_compile_args=extra_compile_args, - extra_objects=extra_objects + runtime_library_dirs=runtime_library_dirs ) ] if compile_cpp_tests: @@ -230,11 +231,11 @@ def build_deps(): zlib_path = os.path.join(this_dir, "third_party/zlib") if sys.platform.startswith("linux"): - libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) if sys.platform.startswith("darwin"): - libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=ON -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.dylib".format(zlib_path=zlib_path) + libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.dylib".format(zlib_path=zlib_path) os.chdir("third_party/libpng/") - os.system('cmake -DCMAKE_C_FLAGS="-fPIC" {} .'.format(libpng_cmake_options)) + os.system('cmake {} .'.format(libpng_cmake_options)) throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) os.chdir(this_dir) @@ -244,6 +245,22 @@ def build_ext_with_dependencies(self): return BuildExtension.with_options(no_python_abi_suffix=True)(self) +data_files = [] +if sys.platform.startswith('linux'): + data_files = [ + ('torchvision/lib', [ + 'third_party/zlib/libz.so', + 'third_party/libpng/libpng.so']) + ] + +if sys.platform.startswith('darwin'): + data_files = [ + ('torchvision/lib', [ + 'third_party/zlib/libz.dylib', + 'third_party/libpng/libpng.dylib']) + ] + + setup( # Metadata name=package_name, @@ -268,4 +285,5 @@ def build_ext_with_dependencies(self): 'build_ext': build_ext_with_dependencies, 'clean': clean, }, + data_files=data_files ) From 75f4130ff066fd2f4f195ad727245019deed99f7 Mon Sep 17 00:00:00 2001 From: Ryad ZENINE Date: Sun, 8 Mar 2020 14:17:03 +0100 Subject: [PATCH 8/8] drops mac support for now --- setup.py | 14 ++------------ test/test_image.py | 6 +++--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 8ba171dce2e..98cc5c8eb3a 100644 --- a/setup.py +++ b/setup.py @@ -94,7 +94,7 @@ def get_extensions(): third_party_search_directories = [] runtime_library_dirs = None - if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): + if sys.platform.startswith('linux'): sources = sources + source_image_cpu libraries.append('png') third_party_search_directories.append(os.path.join(cwd, "third_party/libpng")) @@ -222,7 +222,7 @@ def throw_of_failure(command): def build_deps(): this_dir = os.path.dirname(os.path.abspath(__file__)) - if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): + if sys.platform.startswith('linux'): cpu_count = multiprocessing.cpu_count() os.chdir("third_party/zlib/") throw_of_failure('cmake .') @@ -232,8 +232,6 @@ def build_deps(): zlib_path = os.path.join(this_dir, "third_party/zlib") if sys.platform.startswith("linux"): libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.so".format(zlib_path=zlib_path) - if sys.platform.startswith("darwin"): - libpng_cmake_options = "-DPNG_BUILD_ZLIB=ON -DPNG_STATIC=OFF -DZLIB_INCLUDE_DIR:PATH={zlib_path} -DZLIB_LIBRARY:FILEPATH={zlib_path}/libz.dylib".format(zlib_path=zlib_path) os.chdir("third_party/libpng/") os.system('cmake {} .'.format(libpng_cmake_options)) throw_of_failure("cmake --build . -- -j {}".format(cpu_count)) @@ -253,14 +251,6 @@ def build_ext_with_dependencies(self): 'third_party/libpng/libpng.so']) ] -if sys.platform.startswith('darwin'): - data_files = [ - ('torchvision/lib', [ - 'third_party/zlib/libz.dylib', - 'third_party/libpng/libpng.dylib']) - ] - - setup( # Metadata name=package_name, diff --git a/test/test_image.py b/test/test_image.py index b681b615d16..7ea5c84ea63 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -4,7 +4,7 @@ import torch from PIL import Image -if sys.platform.startswith('linux') or sys.platform.startswith("darwin"): +if sys.platform.startswith('linux'): from torchvision.io.image import read_png, decode_png import numpy as np @@ -21,14 +21,14 @@ def get_images(directory, img_ext): class ImageTester(unittest.TestCase): - @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") + @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") def test_read_png(self): for img_path in get_images(IMAGE_DIR, ".png"): img_pil = torch.from_numpy(np.array(Image.open(img_path))) img_lpng = read_png(img_path) self.assertTrue(torch.equal(img_lpng, img_pil)) - @unittest.skipUnless(sys.platform.startswith("linux") or sys.platform.startswith("darwin"), "Support only available on linux for now.") + @unittest.skipUnless(sys.platform.startswith("linux"), "Support only available on linux for now.") def test_decode_png(self): for img_path in get_images(IMAGE_DIR, ".png"): img_pil = torch.from_numpy(np.array(Image.open(img_path)))