From 14707ed69bcaeb3784b0b9a5229d0d6b0a7247b4 Mon Sep 17 00:00:00 2001 From: kunkunlin1221 Date: Mon, 25 Aug 2025 15:18:01 +0800 Subject: [PATCH 1/5] [C] Update opencv for numpy>2.0 --- capybara/onnxengine/tools.py | 21 ++++++----- pyproject.toml | 64 +++++++++++++++++++++++++++++++++ setup.cfg | 12 +++---- tests/onnxruntime/test_tools.py | 4 +-- 4 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 pyproject.toml diff --git a/capybara/onnxengine/tools.py b/capybara/onnxengine/tools.py index aab00f9..5c16787 100644 --- a/capybara/onnxengine/tools.py +++ b/capybara/onnxengine/tools.py @@ -3,12 +3,11 @@ import onnx import onnxsim -from onnx.helper import (make_graph, make_model, make_opsetid, - tensor_dtype_to_np_dtype) +from onnx.helper import make_graph, make_model, make_opsetid, tensor_dtype_to_np_dtype __all__ = [ - 'get_onnx_input_infos', - 'get_onnx_output_infos', + "get_onnx_input_infos", + "get_onnx_output_infos", ] @@ -17,8 +16,8 @@ def get_onnx_input_infos(model: Union[str, Path, onnx.ModelProto]) -> Dict[str, model = onnx.load(model) return { x.name: { - 'shape': [d.dim_value if d.dim_value != 0 else -1 for d in x.type.tensor_type.shape.dim], - 'dtype': tensor_dtype_to_np_dtype(x.type.tensor_type.elem_type) + "shape": [d.dim_value if d.dim_value != 0 else -1 for d in x.type.tensor_type.shape.dim], + "dtype": tensor_dtype_to_np_dtype(x.type.tensor_type.elem_type), } for x in model.graph.input } @@ -29,8 +28,8 @@ def get_onnx_output_infos(model: Union[str, Path, onnx.ModelProto]) -> Dict[str, model = onnx.load(model) return { x.name: { - 'shape': [d.dim_value if d.dim_value != 0 else -1 for d in x.type.tensor_type.shape.dim], - 'dtype': tensor_dtype_to_np_dtype(x.type.tensor_type.elem_type) + "shape": [d.dim_value if d.dim_value != 0 else -1 for d in x.type.tensor_type.shape.dim], + "dtype": tensor_dtype_to_np_dtype(x.type.tensor_type.elem_type), } for x in model.graph.output } @@ -54,10 +53,10 @@ def make_onnx_dynamic_axes( value_info=None, ) - if not any(opset.domain == '' for opset in onnx_model.opset_import): + if not any(opset.domain == "" for opset in onnx_model.opset_import): onnx_model.opset_import.append(make_opsetid(domain="", version=opset_version)) - new_model = make_model(new_graph, opset_imports=onnx_model.opset_import) + new_model = make_model(new_graph, opset_imports=onnx_model.opset_import, ir_version=onnx_model.ir_version) for x in new_model.graph.input: for name, v in input_dims.items(): @@ -72,7 +71,7 @@ def make_onnx_dynamic_axes( x.type.tensor_type.shape.dim[k].dim_param = d for x in new_model.graph.node: - if x.op_type == 'Reshape': + if x.op_type == "Reshape": raise ValueError("Reshape cannot be trasformed to dynamic axes") new_model, _ = onnxsim.simplify(new_model) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dd9e380 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "capybara_docsaid" +dynamic = ["version"] +description = "An Image Processing and Deep Learning Toolkit." +readme = {file = "README.md", content-type = "text/markdown"} +license = {text = "Apache License 2.0"} +requires-python = ">=3.10,<3.13" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules" +] +dependencies = [ + "dacite", + "psutil", + "requests", + "onnx", + "colored", + "numpy", + "pdf2image", + "ujson", + "pyyaml", + "tqdm", + "pybase64", + "PyTurboJPEG", + "dill", + "networkx", + "natsort", + "flask", + "shapely", + "piexif", + "matplotlib", + "opencv-python>=4.12.0.88", + "onnxsim", + "beautifulsoup4", + "pyheif; platform_system == 'Linux'", + "onnxruntime==1.22.0; platform_system == 'Darwin'", + "onnxruntime_gpu==1.22.0; platform_system == 'Linux'" +] + +[project.urls] +Homepage = "https://docsaid.org/en/docs/capybara/" +Repository = "https://github.com/DocsaidLab/Capybara" +Issues = "https://github.com/DocsaidLab/Capybara/issues" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +exclude = ["docker", "demo", "tests", "benchmarks"] + +[tool.setuptools.dynamic] +version = {attr = "capybara.__version__"} \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 1ba9a23..8604bb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = capybara_docsaid version = attr: capybara.__version__ -description = OpenCV with ONNX Runtime Inference Toolkit. +description = An Image Processing and Deep Learning Toolkit. long_description = file: README.md long_description_content_type = text/markdown license = Apache License 2.0 @@ -16,7 +16,7 @@ classifiers= Programming Language :: Python :: 3.12 Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules -python_requires = >=3.10,<=3.12 +python_requires = >=3.10,<3.13 url = https://github.com/DocsaidLab/Capybara.git [options] @@ -33,7 +33,7 @@ install_requires = requests onnx colored - numpy<2.0.0 + numpy pdf2image ujson pyyaml @@ -47,9 +47,9 @@ install_requires = shapely piexif matplotlib - opencv-python==4.9.0.80 - onnxruntime==1.20.1;platform_system=='Darwin' - onnxruntime_gpu==1.20.1;platform_system=='Linux' + opencv-python>=4.12.0.88 + onnxruntime==1.22.0;platform_system=='Darwin' + onnxruntime_gpu==1.22.0;platform_system=='Linux' onnxsim beautifulsoup4 diff --git a/tests/onnxruntime/test_tools.py b/tests/onnxruntime/test_tools.py index f9c922f..c389e84 100644 --- a/tests/onnxruntime/test_tools.py +++ b/tests/onnxruntime/test_tools.py @@ -34,8 +34,6 @@ def test_make_onnx_dynamic_axes(): output_dims=output_dims, ) xs = {"input": np.random.randn(32, 3, 320, 320).astype("float32")} - engine = ONNXEngineIOBinding( - new_model_path, input_initializer=xs, session_option={"log_severity_level": 1} - ) + engine = ONNXEngineIOBinding(new_model_path, input_initializer=xs, session_option={"log_severity_level": 1}) outs = engine(**xs) assert outs["output"].shape == (32, 64, 80, 80) From 55bf229b73f6fcab8c56f9a5179caaac9815ee11 Mon Sep 17 00:00:00 2001 From: kunkunlin1221 Date: Mon, 25 Aug 2025 15:46:56 +0800 Subject: [PATCH 2/5] [AC] Add mac support and update tests --- capybara/onnxengine/engine.py | 65 ++++++++++----------- capybara/onnxengine/tools.py | 1 + pyproject.toml | 2 +- tests/onnxruntime/test_engine.py | 29 ++++++++- tests/onnxruntime/test_engine_io_binding.py | 6 +- tests/onnxruntime/test_tools.py | 4 +- 6 files changed, 68 insertions(+), 39 deletions(-) diff --git a/capybara/onnxengine/engine.py b/capybara/onnxengine/engine.py index 5d7065c..46af592 100644 --- a/capybara/onnxengine/engine.py +++ b/capybara/onnxengine/engine.py @@ -14,6 +14,7 @@ class Backend(EnumCheckMixin, Enum): cpu = 0 cuda = 1 + coreml = 2 class ONNXEngine: @@ -45,19 +46,14 @@ def __init__( self.device_id = 0 if backend.name == "cpu" else gpu_id # setting provider options - providers, provider_options = self._get_provider_info(backend, provider_option) + providers = self._get_providers(backend, provider_option) # setting session options sess_options = self._get_session_info(session_option) # setting onnxruntime session model_path = str(model_path) if isinstance(model_path, Path) else model_path - self.sess = ort.InferenceSession( - model_path, - sess_options=sess_options, - providers=providers, - provider_options=provider_options, - ) + self.sess = ort.InferenceSession(model_path, sess_options=sess_options, providers=providers) # setting onnxruntime session info self.model_path = model_path @@ -74,10 +70,7 @@ def __call__(self, **xs) -> Dict[str, np.ndarray]: outs = {k: v for k, v in zip(output_names, outs)} return outs - def _get_session_info( - self, - session_option: Dict[str, Any] = {}, - ) -> ort.SessionOptions: + def _get_session_info(self, session_option: Dict[str, Any] = {}) -> ort.SessionOptions: """ Ref: https://onnxruntime.ai/docs/api/python/api_summary.html#sessionoptions """ @@ -91,30 +84,40 @@ def _get_session_info( setattr(sess_opt, k, v) return sess_opt - def _get_provider_info( - self, - backend: Union[str, int, Backend], - provider_option: Dict[str, Any] = {}, - ) -> Backend: + def _get_providers(self, backend: Union[str, int, Backend], provider_option: Dict[str, Any] = {}) -> Backend: """ Ref: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html#configuration-options """ if backend == Backend.cuda: - providers = ["CUDAExecutionProvider"] - provider_option = [ - { - "device_id": self.device_id, - "cudnn_conv_use_max_workspace": "1", - **provider_option, - } + providers = [ + ( + "CUDAExecutionProvider", + { + "device_id": self.device_id, + "cudnn_conv_use_max_workspace": "1", + **provider_option, + }, + ) + ] + elif backend == Backend.coreml: + providers = [ + ( + "CoreMLExecutionProvider", + { + "ModelFormat": "MLProgram", + "MLComputeUnits": "ALL", + "RequireStaticInputShapes": "1", + **provider_option, + }, + ) ] elif backend == Backend.cpu: - providers = ["CPUExecutionProvider"] + providers = [("CPUExecutionProvider", {})] # "CPUExecutionProvider" is different from everything else. - provider_option = None + # provider_option = None else: raise ValueError(f"backend={backend} is not supported.") - return providers, provider_option + return providers def __repr__(self) -> str: import re @@ -132,11 +135,7 @@ def format_nested_dict(dict_data, indent=0): if isinstance(value, dict): info.append(f"{prefix}{key}:") info.append(format_nested_dict(value, indent + 1)) - elif ( - isinstance(value, str) - and value.startswith("{") - and value.endswith("}") - ): + elif isinstance(value, str) and value.startswith("{") and value.endswith("}"): try: nested_dict = eval(value) if isinstance(nested_dict, dict): @@ -153,9 +152,7 @@ def format_nested_dict(dict_data, indent=0): title = "DOCSAID X ONNXRUNTIME" divider_length = 50 divider = f"+{'-' * divider_length}+" - styled_title = colored.stylize( - title, [colored.fg("blue"), colored.attr("bold")] - ) + styled_title = colored.stylize(title, [colored.fg("blue"), colored.attr("bold")]) def center_text(text, width): """Center text within a fixed width, handling ANSI escape codes.""" diff --git a/capybara/onnxengine/tools.py b/capybara/onnxengine/tools.py index 5c16787..b0d71d7 100644 --- a/capybara/onnxengine/tools.py +++ b/capybara/onnxengine/tools.py @@ -8,6 +8,7 @@ __all__ = [ "get_onnx_input_infos", "get_onnx_output_infos", + "make_onnx_dynamic_axes", ] diff --git a/pyproject.toml b/pyproject.toml index dd9e380..f94d20c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ dependencies = [ "opencv-python>=4.12.0.88", "onnxsim", "beautifulsoup4", - "pyheif; platform_system == 'Linux'", + "pyheif", "onnxruntime==1.22.0; platform_system == 'Darwin'", "onnxruntime_gpu==1.22.0; platform_system == 'Linux'" ] diff --git a/tests/onnxruntime/test_engine.py b/tests/onnxruntime/test_engine.py index e07b20b..088dabc 100644 --- a/tests/onnxruntime/test_engine.py +++ b/tests/onnxruntime/test_engine.py @@ -1,9 +1,13 @@ +import platform + import numpy as np +import pytest from capybara import ONNXEngine, get_curdir -def test_ONNXEngine(): +@pytest.mark.skipif(platform.system() != "Linux", reason="Linux only") +def test_ONNXEngine_CUDA(): model_path = get_curdir(__file__).parent / "resources/model_dynamic-axes.onnx" engine = ONNXEngine(model_path, backend="cuda") for i in range(5): @@ -12,3 +16,26 @@ def test_ONNXEngine(): if i: assert not np.allclose(outs["output"], prev_outs["output"]) prev_outs = outs + + +def test_ONNXEngine_CPU(): + model_path = get_curdir(__file__).parent / "resources/model_dynamic-axes.onnx" + engine = ONNXEngine(model_path, backend="cpu") + for i in range(5): + xs = {"input": np.random.randn(32, 3, 224, 224).astype("float32")} + outs = engine(**xs) + if i: + assert not np.allclose(outs["output"], prev_outs["output"]) + prev_outs = outs + + +@pytest.mark.skipif(platform.system() != "Darwin", reason="Mac only") +def test_ONNXEngine_COREML(): + model_path = get_curdir(__file__).parent / "resources/model_dynamic-axes.onnx" + engine = ONNXEngine(model_path, backend="coreml") + for i in range(5): + xs = {"input": np.random.randn(32, 3, 224, 224).astype("float32")} + outs = engine(**xs) + if i: + assert not np.allclose(outs["output"], prev_outs["output"]) + prev_outs = outs diff --git a/tests/onnxruntime/test_engine_io_binding.py b/tests/onnxruntime/test_engine_io_binding.py index 9f39c69..d17a898 100644 --- a/tests/onnxruntime/test_engine_io_binding.py +++ b/tests/onnxruntime/test_engine_io_binding.py @@ -1,9 +1,13 @@ +import platform + import numpy as np +import pytest from capybara import ONNXEngineIOBinding, get_curdir -def test_ONNXEngineIOBinding(): +@pytest.mark.skipif(platform.system() != "Linux", reason="Linux only") +def test_ONNXEngineIOBinding_CUDAonly(): model_path = get_curdir(__file__).parent / "resources/model_dynamic-axes.onnx" input_initializer = {"input": np.random.randn(32, 3, 448, 448).astype("float32")} engine = ONNXEngineIOBinding(model_path, input_initializer) diff --git a/tests/onnxruntime/test_tools.py b/tests/onnxruntime/test_tools.py index c389e84..23f6016 100644 --- a/tests/onnxruntime/test_tools.py +++ b/tests/onnxruntime/test_tools.py @@ -1,7 +1,7 @@ import numpy as np from capybara import ( - ONNXEngineIOBinding, + ONNXEngine, get_onnx_input_infos, get_onnx_output_infos, make_onnx_dynamic_axes, @@ -34,6 +34,6 @@ def test_make_onnx_dynamic_axes(): output_dims=output_dims, ) xs = {"input": np.random.randn(32, 3, 320, 320).astype("float32")} - engine = ONNXEngineIOBinding(new_model_path, input_initializer=xs, session_option={"log_severity_level": 1}) + engine = ONNXEngine(new_model_path, session_option={"log_severity_level": 1}, backend="cpu") outs = engine(**xs) assert outs["output"].shape == (32, 64, 80, 80) From 2c586f1b600b1167cbefa7953502cbc1826f8f96 Mon Sep 17 00:00:00 2001 From: kunkunlin1221 Date: Mon, 25 Aug 2025 16:40:55 +0800 Subject: [PATCH 3/5] [C] modify to use pillow-heif --- capybara/vision/improc.py | 126 ++++++++++++++++-------------------- pyproject.toml | 4 +- setup.cfg | 2 +- tests/vision/test_improc.py | 19 ++++-- 4 files changed, 70 insertions(+), 81 deletions(-) diff --git a/capybara/vision/improc.py b/capybara/vision/improc.py index 8a042a3..39e59db 100644 --- a/capybara/vision/improc.py +++ b/capybara/vision/improc.py @@ -1,4 +1,3 @@ -import platform import warnings from pathlib import Path from typing import Any, List, Union @@ -6,6 +5,7 @@ import cv2 import numpy as np import piexif +import pillow_heif import pybase64 from pdf2image import convert_from_bytes, convert_from_path from turbojpeg import TurboJPEG @@ -14,16 +14,28 @@ from .functionals import imcvtcolor from .geometric import imrotate90 -if platform.system() == 'Linux': - import pyheif -else: - print('Image file with `.heif` or `heic` are only available on Linux system.') - __all__ = [ - 'imread', 'imwrite', 'imencode', 'imdecode', 'img_to_b64', 'img_to_b64str', - 'b64_to_img', 'b64str_to_img', 'b64_to_npy', 'b64str_to_npy', 'npy_to_b64', - 'npy_to_b64str', 'npyread', 'pdf2imgs', 'jpgencode', 'jpgdecode', 'jpgread', - 'pngencode', 'pngdecode', 'is_numpy_img', 'get_orientation_code' + "imread", + "imwrite", + "imencode", + "imdecode", + "img_to_b64", + "img_to_b64str", + "b64_to_img", + "b64str_to_img", + "b64_to_npy", + "b64str_to_npy", + "npy_to_b64", + "npy_to_b64str", + "npyread", + "pdf2imgs", + "jpgencode", + "jpgdecode", + "jpgread", + "pngencode", + "pngdecode", + "is_numpy_img", + "get_orientation_code", ] jpeg = TurboJPEG() @@ -57,7 +69,7 @@ def jpgencode(img: np.ndarray, quality: int = 90) -> Union[bytes, None]: if is_numpy_img(img): try: byte_ = jpeg.encode(img, quality=quality) - except: + except Exception as _: pass return byte_ @@ -66,16 +78,15 @@ def jpgdecode(byte_: bytes) -> Union[np.ndarray, None]: try: bgr_array = jpeg.decode(byte_) code = get_orientation_code(byte_) - bgr_array = imrotate90( - bgr_array, code) if code is not None else bgr_array - except: + bgr_array = imrotate90(bgr_array, code) if code is not None else bgr_array + except Exception as _: bgr_array = None return bgr_array def jpgread(img_file: Union[str, Path]) -> Union[np.ndarray, None]: - with open(str(img_file), 'rb') as f: + with open(str(img_file), "rb") as f: binary_img = f.read() bgr_array = jpgdecode(binary_img) @@ -86,18 +97,17 @@ def pngencode(img: np.ndarray, compression: int = 1) -> Union[bytes, None]: byte_ = None if is_numpy_img(img): try: - byte_ = cv2.imencode('.png', img, params=[int( - cv2.IMWRITE_PNG_COMPRESSION), compression])[1].tobytes() - except: + byte_ = cv2.imencode(".png", img, params=[int(cv2.IMWRITE_PNG_COMPRESSION), compression])[1].tobytes() + except Exception as _: pass return byte_ def pngdecode(byte_: bytes) -> Union[np.ndarray, None]: try: - enc = np.frombuffer(byte_, 'uint8') + enc = np.frombuffer(byte_, "uint8") img = cv2.imdecode(enc, cv2.IMREAD_COLOR) - except: + except Exception as _: img = None return img @@ -113,7 +123,7 @@ def imdecode(byte_: bytes) -> Union[np.ndarray, None]: try: img = jpgdecode(byte_) img = pngdecode(byte_) if img is None else img - except: + except Exception as _: img = None return img @@ -123,23 +133,21 @@ def img_to_b64(img: np.ndarray, IMGTYP: Union[str, int, IMGTYP] = IMGTYP.JPEG) - encode_fn = jpgencode if IMGTYP == IMGTYP.JPEG else pngencode try: b64 = pybase64.b64encode(encode_fn(img)) - except: + except Exception as _: b64 = None return b64 -def npy_to_b64(x: np.ndarray, dtype='float32') -> bytes: +def npy_to_b64(x: np.ndarray, dtype="float32") -> bytes: return pybase64.b64encode(x.astype(dtype).tobytes()) -def npy_to_b64str(x: np.ndarray, dtype='float32', string_encode: str = 'utf-8') -> str: +def npy_to_b64str(x: np.ndarray, dtype="float32", string_encode: str = "utf-8") -> str: return pybase64.b64encode(x.astype(dtype).tobytes()).decode(string_encode) def img_to_b64str( - img: np.ndarray, - IMGTYP: Union[str, int, IMGTYP] = IMGTYP.JPEG, - string_encode: str = 'utf-8' + img: np.ndarray, IMGTYP: Union[str, int, IMGTYP] = IMGTYP.JPEG, string_encode: str = "utf-8" ) -> Union[str, None]: b64 = img_to_b64(img, IMGTYP) return b64.decode(string_encode) if isinstance(b64, bytes) else None @@ -148,16 +156,12 @@ def img_to_b64str( def b64_to_img(b64: bytes) -> Union[np.ndarray, None]: try: img = imdecode(pybase64.b64decode(b64)) - except: + except Exception as _: img = None return img -def b64str_to_img( - b64str: Union[str, None], - string_encode: str = 'utf-8' -) -> Union[np.ndarray, None]: - +def b64str_to_img(b64str: Union[str, None], string_encode: str = "utf-8") -> Union[np.ndarray, None]: if b64str is None: warnings.warn("b64str is None.") return None @@ -168,42 +172,24 @@ def b64str_to_img( return b64_to_img(b64str.encode(string_encode)) -def b64_to_npy(x: bytes, dtype='float32') -> np.ndarray: +def b64_to_npy(x: bytes, dtype="float32") -> np.ndarray: return np.frombuffer(pybase64.b64decode(x), dtype=dtype) -def b64str_to_npy(x: bytes, dtype='float32', string_encode: str = 'utf-8') -> np.ndarray: +def b64str_to_npy(x: bytes, dtype="float32", string_encode: str = "utf-8") -> np.ndarray: return np.frombuffer(pybase64.b64decode(x.encode(string_encode)), dtype=dtype) def npyread(path: Union[str, Path]) -> Union[np.ndarray, None]: try: - with open(str(path), 'rb') as f: + with open(str(path), "rb") as f: img = np.load(f) - except: + except Exception as _: img = None return img -def read_heic_to_numpy(file_path: str): - heif_file = pyheif.read(file_path) - data = heif_file.data - if heif_file.mode == "RGB": - numpy_array = np.frombuffer(data, dtype=np.uint8).reshape( - heif_file.size[1], heif_file.size[0], 3) - elif heif_file.mode == "RGBA": - numpy_array = np.frombuffer(data, dtype=np.uint8).reshape( - heif_file.size[1], heif_file.size[0], 4) - else: - raise ValueError("Unsupported HEIC color mode") - return numpy_array - - -def imread( - path: Union[str, Path], - color_base: str = 'BGR', - verbose: bool = False -) -> Union[np.ndarray, None]: +def imread(path: Union[str, Path], color_base: str = "BGR", verbose: bool = False) -> Union[np.ndarray, None]: """ This function reads an image from a given file path and converts its color base if necessary. @@ -227,13 +213,11 @@ def imread( The image as a numpy ndarray if successful, None otherwise. """ if not Path(path).exists(): - raise FileExistsError(f'{path} can not found.') + raise FileExistsError(f"{path} can not found.") - if Path(path).suffix.lower() == '.heic': - if platform.system() != 'Linux': - raise ValueError('HEIC file is only supported on Linux system.') - img = read_heic_to_numpy(str(path)) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + if Path(path).suffix.lower() == ".heic": + heif_file = pillow_heif.open_heif(str(path), convert_hdr_to_8bit=True, bgr_mode=True) + img = np.asarray(heif_file) else: img = jpgread(path) img = cv2.imread(str(path)) if img is None else img @@ -243,8 +227,8 @@ def imread( warnings.warn("Got a None type image.") return - if color_base != 'BGR': - img = imcvtcolor(img, cvt_mode=f'BGR2{color_base}') + if color_base != "BGR": + img = imcvtcolor(img, cvt_mode=f"BGR2{color_base}") return img @@ -252,8 +236,8 @@ def imread( def imwrite( img: np.ndarray, path: Union[str, Path] = None, - color_base: str = 'BGR', - suffix: str = '.jpg', + color_base: str = "BGR", + suffix: str = ".jpg", ) -> bool: """ Writes an image to a file with optional color base conversion. @@ -274,9 +258,9 @@ def imwrite( bool: True if the write operation is successful, False otherwise. """ color_base = color_base.upper() - if color_base != 'BGR': - img = imcvtcolor(img, cvt_mode=f'{color_base}2BGR') - return cv2.imwrite(str(path) if path else f'tmp{suffix}', img) + if color_base != "BGR": + img = imcvtcolor(img, cvt_mode=f"{color_base}2BGR") + return cv2.imwrite(str(path) if path else f"tmp{suffix}", img) def pdf2imgs(stream: Union[str, Path, bytes]) -> Union[List[np.ndarray], None]: @@ -294,6 +278,6 @@ def pdf2imgs(stream: Union[str, Path, bytes]) -> Union[List[np.ndarray], None]: pil_imgs = convert_from_bytes(stream) else: pil_imgs = convert_from_path(stream) - return [imcvtcolor(np.array(img), cvt_mode='RGB2BGR') for img in pil_imgs] - except: + return [imcvtcolor(np.array(img), cvt_mode="RGB2BGR") for img in pil_imgs] + except Exception as _: return diff --git a/pyproject.toml b/pyproject.toml index f94d20c..7bcfd26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,9 +44,9 @@ dependencies = [ "opencv-python>=4.12.0.88", "onnxsim", "beautifulsoup4", - "pyheif", "onnxruntime==1.22.0; platform_system == 'Darwin'", - "onnxruntime_gpu==1.22.0; platform_system == 'Linux'" + "onnxruntime_gpu==1.22.0; platform_system == 'Linux'", + "pillow-heif" ] [project.urls] diff --git a/setup.cfg b/setup.cfg index 8604bb6..962694d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,6 @@ setup_requires= setuptools wheel install_requires = - pyheif;platform_system=='Linux' dacite psutil requests @@ -52,6 +51,7 @@ install_requires = onnxruntime_gpu==1.22.0;platform_system=='Linux' onnxsim beautifulsoup4 + pillow-heif [options.packages.find] exclude = diff --git a/tests/vision/test_improc.py b/tests/vision/test_improc.py index 2e4155f..32ab521 100644 --- a/tests/vision/test_improc.py +++ b/tests/vision/test_improc.py @@ -10,21 +10,26 @@ def test_imread(): # 測試圖片路徑 - image_path = DIR.parent / 'resources' / 'lena.png' + image_path = DIR.parent / "resources" / "lena.png" # 測試 BGR 格式的圖片讀取 - img_bgr = imread(image_path, color_base='BGR') + img_bgr = imread(image_path, color_base="BGR") assert isinstance(img_bgr, np.ndarray) assert img_bgr.shape[-1] == 3 # BGR圖片的channel數為3 # 測試灰階格式的圖片讀取 - img_gray = imread(image_path, color_base='GRAY') + img_gray = imread(image_path, color_base="GRAY") assert isinstance(img_gray, np.ndarray) assert len(img_gray.shape) == 2 # 灰階圖片的channel數為1 + # 測試heif格式的圖片讀取 + img_heif = imread(DIR.parent / "resources" / "lena.heif", color_base="BGR") + assert isinstance(img_heif, np.ndarray) + assert img_heif.shape[-1] == 3 # BGR圖片的channel數為3 + # 測試不存在的圖片路徑 with pytest.raises(FileExistsError): - imread('non_existent_image.jpg') + imread("non_existent_image.jpg") def test_imwrite(): @@ -32,9 +37,9 @@ def test_imwrite(): img = np.zeros((100, 100, 3), dtype=np.uint8) # 建立一個全黑的BGR圖片 # 測試BGR格式的圖片寫入 - temp_file_path = DIR / 'temp_image.jpg' - assert imwrite(img, path=temp_file_path, color_base='BGR') + temp_file_path = DIR / "temp_image.jpg" + assert imwrite(img, path=temp_file_path, color_base="BGR") assert Path(temp_file_path).exists() # 測試不指定路徑時的圖片寫入 - assert imwrite(img, color_base='BGR') # 將會寫入一個暫時的檔案 + assert imwrite(img, color_base="BGR") # 將會寫入一個暫時的檔案 From 6c870b2f160cc601a2df4112c24e049c1d7a7f5c Mon Sep 17 00:00:00 2001 From: kunkunlin1221 Date: Mon, 25 Aug 2025 16:41:08 +0800 Subject: [PATCH 4/5] [C] Update readme and docker --- README.md | 138 +++++++++++++++++----------------------------- docker/Dockerfile | 8 +-- docker/build.bash | 2 +- 3 files changed, 55 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index fe8ddbe..0cd27b6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # Capybara +**An Integrated Python Package for Image Processing and Deep Learning.** +

@@ -28,113 +30,66 @@ For more detailed information on installation and usage, please refer to the [** The document provides a detailed explanation of this project and answers to frequently asked questions. -## Installation +## Prerequisites -Before starting the installation of Capybara, ensure that your system meets the following requirements: +Before the installation of Capybara, ensure that your system meets the following requirements: ### Python Version -- Python 3.10 or later is required. +3.10+ ### Dependency Packages Please install the necessary system packages according to your operating system: -- **Ubuntu** - - ```bash - sudo apt install libturbojpeg exiftool ffmpeg libheif-dev - ``` - -- **MacOS** - - ```bash - brew install jpeg-turbo exiftool ffmpeg - ``` - - - **Special Notes**: After testing, there are some known issues when using libheif on macOS, including: - - 1. **Generated HEIC files cannot be opened**: On macOS, HEIC files generated by libheif may not open with certain applications. This may be related to image dimensions, particularly when the image width or height is odd, causing compatibility issues. - - 2. **Compilation errors**: When compiling libheif on macOS, you may encounter undefined symbol errors related to ffmpeg decoders. This could be caused by incorrect compilation options or dependency settings. - - 3. **Example programs do not run**: On macOS Sonoma, the example programs of libheif might fail with dynamic link errors, indicating that `libheif.1.dylib` is missing. This might be related to dynamic library path settings. - - Due to these issues, we currently only run libheif on Ubuntu, and macOS support will be addressed in future versions. - -### pdf2image Dependency - -pdf2image is a Python module used to convert PDF documents to images. Make sure the following tools are installed on your system: - -- MacOS: Install poppler - - ```bash - brew install poppler - ``` - -- Linux: Most distributions already include `pdftoppm` and `pdftocairo`. If not, install them using: - - ```bash - sudo apt install poppler-utils - ``` - -### ONNXRuntime GPU Dependencies - -To use ONNXRuntime for GPU-accelerated inference, ensure that you have an appropriate version of CUDA installed. Here's an example: +#### Ubuntu ```bash -sudo apt install cuda-12-4 -# Add to .bashrc -echo 'export PATH=/usr/local/cuda-12.4/bin${PATH:+:${PATH}}' >> ~/.bashrc -echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}' >> ~/.bashrc +sudo apt install libturbojpeg exiftool ffmpeg libheif-dev poppler-utils ``` -### Installation via PyPI +##### GPU Dependencies -1. Install the package from PyPI: - - ```bash - pip install capybara-docsaid - ``` - -2. Verify the installation: - - ```bash - python -c "import capybara; print(capybara.__version__)" - ``` +To use ONNX Runtime with GPU acceleration, ensure that you install a compatible version, which can be found on the official ONNX Runtime CUDA Execution Provider requirements page. -3. If the version number is displayed, the installation was successful. +Here's an example to install cuda-12.8: -### Installation via Git Clone +```bash +wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb +sudo dpkg -i cuda-keyring_1.1-1_all.deb +sudo apt-get update +sudo apt-get -y install cuda-toolkit-12-8 +# Post installation, add cuda path to .bashrc or .zshrc +export shellrc="~/.zshrc" +echo 'export PATH=/usr/local/cuda-12.8/bin${PATH:+:${PATH}}' >> $shellrc +echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.8/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}' >> $shellrc +``` -1. Clone this repository: +For more details, please see [Nvidia CUDA](https://developer.nvidia.com/cuda-toolkit). - ```bash - git clone https://github.com/DocsaidLab/Capybara.git - ``` +#### MacOS -2. Install the wheel package: +```bash +brew install jpeg-turbo exiftool ffmpeg libheif poppler +``` - ```bash - pip install wheel - ``` +## Installation -3. Build the wheel file: +### PyPI - ```bash - cd Capybara - python setup.py bdist_wheel - ``` +```bash +pip install capybara_docsaid +``` -4. Install the built wheel file: +### Git - ```bash - pip install dist/capybara_docsaid-*-py3-none-any.whl - ``` +```bash +pip install git+https://github.com/DocsaidLab/Capybara.git +``` -### Installation via Docker +## Docker -To avoid environment conflicts during deployment or collaborative development, it's recommended to use Docker. Here's a brief guide: +We provide a Docker script for convenient deployment, ensuring a consistent environment. Below are the steps to build the image with Capybara installed. 1. Clone this repository: @@ -149,17 +104,15 @@ To avoid environment conflicts during deployment or collaborative development, i bash docker/build.bash ``` - This will build an image using the [**Dockerfile**](https://github.com/DocsaidLab/Capybara/blob/main/docker/Dockerfile) in the project. The image is based on `nvcr.io/nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04` by default, providing the CUDA environment required for ONNXRuntime inference. + This will build an image using the [**Dockerfile**](docker/Dockerfile) in the project. The image is based on `nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04` by default, providing the CUDA environment required for ONNXRuntime inference. 3. After the build is complete, mount the working directory and run the program: ```bash - docker run -v ${PWD}:/code -it capybara_infer_image your_scripts.py + docker run --gpus all -it --rm capybara_docsaid:latest bash ``` - To enable GPU acceleration, add `--gpus all` when running the command. - -#### gosu Permissions Issues +### gosu Permissions Issues If you encounter issues with file ownership as root when running scripts inside the container, causing permission problems, you can use `gosu` to switch users in the Dockerfile. Specify `USER_ID` and `GROUP_ID` when starting the container to avoid frequent permission adjustments in collaborative development. @@ -210,3 +163,16 @@ python -m pytest -vv tests Once completed, you can check if all modules are functioning properly. If any issues arise, first check the environment settings and package versions. If the problem persists, please report it in the Issue section. + +## Citation + +```bibtex +@misc{lin2025capybara, + author = {Kun-Hsiang Lin*, Ze Yuan*}, + title = {Capybara: An Integrated Python Package for Image Processing and Deep Learning.}, + year = {2025}, + publisher = {GitHub}, + howpublished = {\url{https://github.com/DocsaidLab/Capybara}}, + note = {* equal contribution} +} +``` diff --git a/docker/Dockerfile b/docker/Dockerfile index 3120c77..f267d95 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:experimental -FROM nvcr.io/nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04 +FROM nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04 ENV PYTHONDONTWRITEBYTECODE=1 \ DEBIAN_FRONTEND=noninteractive \ @@ -22,11 +22,7 @@ RUN ln -s /usr/bin/python3 /usr/bin/python && \ COPY . /usr/local/Capybara RUN cd /usr/local/Capybara && python setup.py bdist_wheel && \ - python -m pip install dist/*.whl && rm -rf /usr/local/Capybara - -# opencv-fixer -RUN pip install opencv-fixer -RUN python -c "from opencv_fixer import AutoFix; AutoFix()" + python -m pip install dist/*.whl && rm -rf /usr/local/Capybara # Preload data RUN python -c "import capybara" diff --git a/docker/build.bash b/docker/build.bash index 16dded5..8d0c768 100644 --- a/docker/build.bash +++ b/docker/build.bash @@ -1,3 +1,3 @@ docker build \ -f docker/Dockerfile \ - -t capybara_infer_image . + -t capybara_docsaid . From 1b2ad7ffefc9df26af3f026e395e20069dce37fc Mon Sep 17 00:00:00 2001 From: kunkunlin1221 Date: Mon, 25 Aug 2025 16:54:37 +0800 Subject: [PATCH 5/5] [C] Update docker and fix test error --- README.md | 6 ++++-- docker/Dockerfile | 2 +- docker/pr.dockerfile | 2 +- tests/resources/lena.heic | Bin 0 -> 15207 bytes tests/vision/test_improc.py | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 tests/resources/lena.heic diff --git a/README.md b/README.md index 0cd27b6..4863412 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ pip install capybara_docsaid pip install git+https://github.com/DocsaidLab/Capybara.git ``` -## Docker +## Docker for Deployment We provide a Docker script for convenient deployment, ensuring a consistent environment. Below are the steps to build the image with Capybara installed. @@ -104,7 +104,7 @@ We provide a Docker script for convenient deployment, ensuring a consistent envi bash docker/build.bash ``` - This will build an image using the [**Dockerfile**](docker/Dockerfile) in the project. The image is based on `nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04` by default, providing the CUDA environment required for ONNXRuntime inference. + This will build an image using the [**Dockerfile**](docker/Dockerfile) in the project. The image is based on `nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04` by default, providing the CUDA environment required for ONNXRuntime inference. 3. After the build is complete, mount the working directory and run the program: @@ -112,6 +112,8 @@ We provide a Docker script for convenient deployment, ensuring a consistent envi docker run --gpus all -it --rm capybara_docsaid:latest bash ``` +**PS: If you want to compile cuda or cudnn for developing, please change the base image to `nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04`.** + ### gosu Permissions Issues If you encounter issues with file ownership as root when running scripts inside the container, causing permission problems, you can use `gosu` to switch users in the Dockerfile. Specify `USER_ID` and `GROUP_ID` when starting the container to avoid frequent permission adjustments in collaborative development. diff --git a/docker/Dockerfile b/docker/Dockerfile index f267d95..43570b9 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:experimental -FROM nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04 +FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04 as builder ENV PYTHONDONTWRITEBYTECODE=1 \ DEBIAN_FRONTEND=noninteractive \ diff --git a/docker/pr.dockerfile b/docker/pr.dockerfile index 14e2aa0..841c4cb 100644 --- a/docker/pr.dockerfile +++ b/docker/pr.dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:experimental -FROM nvcr.io/nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04 +FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04 as builder ENV PYTHONDONTWRITEBYTECODE=1 \ DEBIAN_FRONTEND=noninteractive \ diff --git a/tests/resources/lena.heic b/tests/resources/lena.heic new file mode 100644 index 0000000000000000000000000000000000000000..b3389ebda5dcf8e0a3f671c359c9b70712e9fd48 GIT binary patch literal 15207 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 literal 0 HcmV?d00001 diff --git a/tests/vision/test_improc.py b/tests/vision/test_improc.py index 32ab521..4ed3581 100644 --- a/tests/vision/test_improc.py +++ b/tests/vision/test_improc.py @@ -23,7 +23,7 @@ def test_imread(): assert len(img_gray.shape) == 2 # 灰階圖片的channel數為1 # 測試heif格式的圖片讀取 - img_heif = imread(DIR.parent / "resources" / "lena.heif", color_base="BGR") + img_heif = imread(DIR.parent / "resources" / "lena.heic", color_base="BGR") assert isinstance(img_heif, np.ndarray) assert img_heif.shape[-1] == 3 # BGR圖片的channel數為3