Repository: pytorch/accimage Branch: master Commit: 15ec9d4a9506 Files: 14 Total size: 32.1 KB Directory structure: gitextract_k0q0dap3/ ├── .github/ │ └── workflows/ │ └── accimage.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── accimage.h ├── accimagemodule.c ├── imageops.c ├── jpegloader.c ├── setup.py └── test.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/accimage.yml ================================================ name: Accimage (Ubuntu latest) on: [push] jobs: build: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: ["ubuntu-latest"] python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up conda uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true python-version: ${{ matrix.python-version }} - name: Conda info shell: bash -l {0} run: conda info -a - name: Install deps shell: bash -l {0} run: | conda install \ -c pytorch \ -c conda-forge \ pip \ scipy \ numpy \ intel-ipp \ libjpeg-turbo pip install pytest imageio - name: Install Accimage shell: bash -l {0} run: | CPPFLAGS="-I$CONDA_PREFIX/include" LDFLAGS="-L$CONDA_PREFIX/lib" pip install . --no-deps -vv - name: Test Accimage shell: bash -l {0} run: | pytest test.py ================================================ FILE: .gitignore ================================================ #### joe made this: http://goel.io/joe #####=== Python ===##### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ #####=== JetBrains ===##### # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Rider-specific rules *.sln.iml .ropeproject test_*.jpg ================================================ FILE: .travis.yml ================================================ language: python dist: xenial sudo: true matrix: include: - python: "2.7" - python: "3.5" - python: "3.6" - python: "3.7" before_install: - sudo apt-get update - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no - conda update -q conda # Useful for debugging any issues with conda - conda info -a - | conda create -q -n test-environment \ -c pytorch \ -c conda-forge \ python=$TRAVIS_PYTHON_VERSION \ pip \ scipy \ numpy \ intel-ipp \ libjpeg-turbo - source activate test-environment - pip install pytest imageio install: - env | sort - ls $CONDA_PREFIX/include - ls $CONDA_PREFIX/lib - CPPFLAGS="-I$CONDA_PREFIX/include" LDFLAGS="-L$CONDA_PREFIX/lib" pip install . --no-deps -vv script: - pytest test.py ================================================ FILE: CHANGELOG.md ================================================ # v0.2.0 * Add support to reading images from a `bytes` buffer, e.g. ```python with open("chicago.jpg", "rb") as f: img = accimage.Image(f.read()) ``` (This new functionality is thanks to @brhcriteo!) # v0.1.1 * Bug fix: Horizontal crops prior to v0.1.1 didn't work. * Add pytest tests.py * Add travis building support # v0.1.0 Initial release ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to accimage We want to make contributing to this project as easy and transparent as possible. ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. Make sure your code lints. 6. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. ## License By contributing to accimage, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. ================================================ FILE: LICENSE ================================================ BSD 2-Clause License Copyright (c) 2017, Facebook, Inc Copyright (c) 2016, Georgia Institute of Technology All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # accimage [![Build status](https://github.com/pytorch/accimage/actions/workflows/accimage.yml/badge.svg)](https://github.com/pytorch/accimage/actions/workflows/accimage.yml) [![Anaconda badge](https://anaconda.org/conda-forge/accimage/badges/version.svg)](https://anaconda.org/conda-forge/accimage) An accelerated Image loader and preprocessor leveraging [Intel IPP](https://software.intel.com/en-us/intel-ipp). accimage mimics the PIL API and can be used as a backend for [`torchvision`](https://github.com/pytorch/vision). Operations implemented: - `Image.resize((width, height))` - `Image.crop((left, upper, right, lower))` - `Image.transpose(PIL.Image.FLIP_LEFT_RIGHT)` Enable the torchvision accimage backend with: ```python torchvision.set_image_backend('accimage') ``` ## Installation accimage is available on conda-forge, simply run the following to install ``` $ conda install -c conda-forge accimage ``` ================================================ FILE: accimage.h ================================================ #ifndef ACCIMAGE_H #define ACCIMAGE_H #include typedef struct { PyObject_HEAD unsigned char* buffer; int channels; int height; int width; int row_stride; int y_offset; int x_offset; } ImageObject; void image_copy_deinterleave(ImageObject* self, unsigned char* output_buffer); void image_copy_deinterleave_float(ImageObject* self, float* output_buffer); void image_from_buffer(ImageObject* self, void* buf, size_t size); void image_from_jpeg(ImageObject* self, const char* path); void image_resize(ImageObject* self, int new_height, int new_width, int antialiasing); void image_flip_left_right(ImageObject* self); #endif ================================================ FILE: accimagemodule.c ================================================ #include #include #include "accimage.h" static struct PyMemberDef Image_members[] = { { "channels", T_INT, offsetof(ImageObject, channels), READONLY, "number of channels" }, { "height", T_INT, offsetof(ImageObject, height), READONLY, "image height in pixels" }, { "width", T_INT, offsetof(ImageObject, width), READONLY, "image width in pixels" }, { NULL } /* Sentinel */ }; static PyObject* Image_getsize(ImageObject* self, void* closure) { return Py_BuildValue("ii", self->width, self->height); } static PyGetSetDef Image_getseters[] = { { "size", (getter) Image_getsize, (setter) NULL, "Image width x height", NULL }, { NULL } /* Sentinel */ }; static PyObject* Image_resize(ImageObject* self, PyObject* args, PyObject* kwds) { static char* argnames[] = { "size", "interpolation", NULL }; PyObject* size = NULL; int interpolation = 0; int antialiasing = 1; int new_height, new_width; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i", argnames, &size, &interpolation)) { return NULL; } if (!PyArg_ParseTuple(size, "ii", &new_width, &new_height)) { return NULL; } if (new_height <= 0) { return PyErr_Format(PyExc_ValueError, "positive height expected; got %d", new_height); } if (new_width <= 0) { return PyErr_Format(PyExc_ValueError, "positive width expected; got %d", new_width); } // TODO: consider interpolation parameter image_resize(self, new_height, new_width, antialiasing); if (PyErr_Occurred()) { return NULL; } else { Py_INCREF(self); return (PyObject*) self; } } static PyObject* Image_crop(ImageObject* self, PyObject* args, PyObject* kwds) { static char* argnames[] = { "box", NULL }; PyObject* box_object; int left, upper, right, lower; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", argnames, &box_object)) { return NULL; } if (!PyArg_ParseTuple(box_object, "iiii", &left, &upper, &right, &lower)) { return NULL; } if (left < 0) { return PyErr_Format(PyExc_ValueError, "non-negative left offset expected; got %d", left); } if (upper < 0) { return PyErr_Format(PyExc_ValueError, "non-negative upper offset expected; got %d", upper); } if (right > self->width) { return PyErr_Format(PyExc_ValueError, "right coordinate (%d) extends beyond image width (%d)", right, self->width); } if (lower > self->height) { return PyErr_Format(PyExc_ValueError, "lower coordinate (%d) extends beyond image height (%d)", lower, self->height); } if (right <= left) { return PyErr_Format(PyExc_ValueError, "right coordinate (%d) does not exceed left coordinate (%d)", right, left); } if (lower <= upper) { return PyErr_Format(PyExc_ValueError, "lower coordinate (%d) does not exceed upper coordinate (%d)", lower, upper); } self->y_offset += upper; self->x_offset += left; self->height = lower - upper; self->width = right - left; Py_INCREF(self); return (PyObject*) self; } static PyObject* Image_transpose(ImageObject* self, PyObject* args, PyObject* kwds) { static char* argnames[] = { "method", NULL }; int method; if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", argnames, &method)) return NULL; switch (method) { case 0: /* PIL.Image.FLIP_LEFT_RIGHT */ image_flip_left_right(self); break; case 1: PyErr_SetString(PyExc_ValueError, "unsupported method: PIL.Image.FLIP_TOP_BOTTOM"); return NULL; case 2: PyErr_SetString(PyExc_ValueError, "unsupported method: PIL.Image.ROTATE_90"); return NULL; case 3: PyErr_SetString(PyExc_ValueError, "unsupported method: PIL.Image.ROTATE_180"); return NULL; case 4: PyErr_SetString(PyExc_ValueError, "unsupported method: PIL.Image.ROTATE_270"); return NULL; case 5: PyErr_SetString(PyExc_ValueError, "unsupported method: PIL.Image.TRANSPOSE"); return NULL; default: return PyErr_Format(PyExc_ValueError, "unknown method (%d)", method); } if (PyErr_Occurred()) { return NULL; } else { Py_INCREF(self); return (PyObject*) self; } } static PyObject* Image_copyto(ImageObject* self, PyObject* args, PyObject* kwds) { static char* argnames[] = { "buffer", NULL }; static const int FLAGS = PyBUF_CONTIG | PyBUF_FORMAT; PyObject* buffer_object; Py_buffer buffer; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", argnames, &buffer_object)) { return NULL; } if (PyObject_GetBuffer(buffer_object, &buffer, FLAGS) < 0) { return NULL; } int expected_size = self->channels * self->height * self->width; if (strcmp(buffer.format, "f") == 0) { expected_size *= sizeof(float); } if (buffer.len < expected_size) { PyErr_Format(PyExc_IndexError, "buffer size (%lld) is smaller than image size (%d)", (long long) buffer.len, expected_size); goto cleanup; } if (buffer.format == NULL || strcmp(buffer.format, "B") == 0) { image_copy_deinterleave(self, (unsigned char*) buffer.buf); } else if (strcmp(buffer.format, "f") == 0) { image_copy_deinterleave_float(self, (float*) buffer.buf); } else { PyErr_SetString(PyExc_TypeError, "buffer of unsigned byte or float elements expected"); goto cleanup; } cleanup: PyBuffer_Release(&buffer); if (PyErr_Occurred()) { return NULL; } else { Py_RETURN_NONE; } } static PyMethodDef Image_methods[] = { { "resize", (PyCFunction) Image_resize, METH_VARARGS | METH_KEYWORDS, "Scale image to new size." }, { "crop", (PyCFunction) Image_crop, METH_VARARGS | METH_KEYWORDS, "Crop image to new size." }, { "transpose", (PyCFunction) Image_transpose, METH_VARARGS | METH_KEYWORDS, "Transpose/flip/rotate image." }, { "copyto", (PyCFunction) Image_copyto, METH_VARARGS | METH_KEYWORDS, "Copy data to a buffer." }, { NULL } /* Sentinel */ }; static PyTypeObject Image_Type = { /* The ob_type field must be initialized in the module init function * to be portable to Windows without using C++. */ PyVarObject_HEAD_INIT(NULL, 0) "accimage.Image", /*tp_name*/ sizeof(ImageObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ 0, /* see initaccimage */ /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Image_methods, /*tp_methods*/ Image_members, /*tp_members*/ Image_getseters, /*tp_getset*/ 0, /* see initaccimage */ /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /* see initaccimage */ /*tp_init*/ 0, /*tp_alloc*/ 0, /* see initaccimage */ /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; static int Image_init(ImageObject *self, PyObject *args, PyObject *kwds) { const char *path; if (PyArg_ParseTuple(args, "s", &path)) { image_from_jpeg(self, path); } else { Py_buffer buffer; PyErr_Clear(); if (PyArg_ParseTuple(args, "y*", &buffer)) { void* buf = buffer.buf; Py_ssize_t size = buffer.len; image_from_buffer(self, buf, size); PyBuffer_Release(&buffer); } } return PyErr_Occurred() ? -1 : 0; } static void Image_dealloc(ImageObject *self) { if (self->buffer != NULL) { free(self->buffer); self->buffer = NULL; } } #if PY_MAJOR_VERSION == 2 PyMODINIT_FUNC initaccimage(void) { #else PyMODINIT_FUNC PyInit_accimage(void) { #endif PyObject* m; /* * Due to cross platform compiler issues the slots must be filled here. * It's required for portability to Windows without requiring C++. */ Image_Type.tp_base = &PyBaseObject_Type; Image_Type.tp_init = (initproc) Image_init; Image_Type.tp_dealloc = (destructor) Image_dealloc; Image_Type.tp_new = PyType_GenericNew; #if PY_MAJOR_VERSION == 2 m = Py_InitModule("accimage", NULL); #else static struct PyModuleDef module_def = { PyModuleDef_HEAD_INIT, "accimage", NULL, -1, NULL }; m = PyModule_Create(&module_def); #endif if (m == NULL) goto err; if (PyType_Ready(&Image_Type) < 0) goto err; PyModule_AddObject(m, "Image", (PyObject*) &Image_Type); #if PY_MAJOR_VERSION == 2 return; #else return m; #endif err: #if PY_MAJOR_VERSION == 2 return; #else return NULL; #endif } ================================================ FILE: imageops.c ================================================ #include "accimage.h" #include #include void image_copy_deinterleave(ImageObject* self, unsigned char* output_buffer) { unsigned char* channel_buffers[3] = { output_buffer, output_buffer + self->height * self->width, output_buffer + 2 * self->height * self->width }; IppiSize roi = { self->width, self->height }; IppStatus ipp_status = ippiCopy_8u_C3P3R( self->buffer + (self->y_offset * self->row_stride + self->x_offset) * self->channels, self->row_stride * self->channels, channel_buffers, self->width, roi); if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiCopy_8u_C3P3R failed with status %d", ipp_status); } } void image_copy_deinterleave_float(ImageObject* self, float* output_buffer) { unsigned char* tmp_buffer = NULL; IppiSize roi = { self->width, self->height }; tmp_buffer = malloc(self->height * self->width * self->channels); if (!tmp_buffer) { PyErr_NoMemory(); goto cleanup; } image_copy_deinterleave(self, tmp_buffer); if (PyErr_Occurred()) { goto cleanup; } IppStatus ipp_status = ippiConvert_8u32f_C3R( tmp_buffer, self->width * self->channels, output_buffer, self->width * self->channels * sizeof(float), roi); if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiConvert_8u32f_C3R failed with status %d", ipp_status); } Ipp32f value[3] = {255.0f, 255.0f, 255.0f}; ipp_status = ippiDivC_32f_C3IR( value, output_buffer, self->width * self->channels * sizeof(float), roi); if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiDivC_32f_C3IR failed with status %d", ipp_status); } cleanup: free(tmp_buffer); } void image_resize(ImageObject* self, int new_height, int new_width, int antialiasing) { IppStatus ipp_status; unsigned char* new_buffer = NULL; IppiSize old_size = { self->width, self->height }; IppiSize new_size = { new_width, new_height }; IppiPoint new_offset = { 0, 0 }; int specification_size = 0, initialization_buffer_size = 0, scratch_buffer_size = 0; IppiResizeSpec_32f* specification = NULL; Ipp8u* scratch_buffer = NULL; Ipp8u* initialization_buffer = NULL; new_buffer = malloc(new_height * new_width * self->channels); if (new_buffer == NULL) { PyErr_NoMemory(); goto cleanup; } ipp_status = ippiResizeGetSize_8u(old_size, new_size, ippLinear, antialiasing, &specification_size, &initialization_buffer_size); if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiResizeGetSize_8u failed with status %d", ipp_status); goto cleanup; } initialization_buffer = malloc(initialization_buffer_size); if (initialization_buffer == NULL) { PyErr_NoMemory(); goto cleanup; } specification = malloc(specification_size); if (specification == NULL) { PyErr_NoMemory(); goto cleanup; } if (antialiasing) { ipp_status = ippiResizeAntialiasingLinearInit( old_size, new_size, specification, initialization_buffer); } else { ipp_status = ippiResizeLinearInit_8u(old_size, new_size, specification); } if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiResizeLinearInit_8u failed with status %d", ipp_status); goto cleanup; } ipp_status = ippiResizeGetBufferSize_8u(specification, new_size, self->channels, &scratch_buffer_size); if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiResizeGetBufferSize_8u failed with status %d", ipp_status); goto cleanup; } scratch_buffer = malloc(scratch_buffer_size); if (scratch_buffer == NULL) { PyErr_NoMemory(); goto cleanup; } if (antialiasing) { ipp_status = ippiResizeAntialiasing_8u_C3R( self->buffer + (self->y_offset * self->row_stride + self->x_offset) * self->channels, self->row_stride * self->channels, new_buffer, new_width * self->channels, new_offset, new_size, ippBorderRepl, NULL, specification, scratch_buffer); } else { ipp_status = ippiResizeLinear_8u_C3R( self->buffer + (self->y_offset * self->row_stride + self->x_offset) * self->channels, self->row_stride * self->channels, new_buffer, new_width * self->channels, new_offset, new_size, ippBorderRepl, NULL, specification, scratch_buffer); } if (ipp_status != ippStsNoErr) { PyErr_Format(PyExc_SystemError, "ippiResizeLinear_8u_C3R failed with status %d", ipp_status); goto cleanup; } free(self->buffer); self->buffer = new_buffer; new_buffer = NULL; self->height = new_height; self->width = new_width; self->row_stride = new_width; self->x_offset = 0; self->y_offset = 0; cleanup: free(new_buffer); free(specification); free(initialization_buffer); free(scratch_buffer); } void image_flip_left_right(ImageObject* self) { IppiSize roi = { self->width, self->height }; IppStatus ipp_status = ippiMirror_8u_C3IR( self->buffer + (self->y_offset * self->row_stride + self->x_offset) * self->channels, self->row_stride * self->channels, roi, ippAxsVertical); if (ipp_status != ippStsNoErr) PyErr_Format(PyExc_SystemError, "ippiMirror_8u_C3IR failed with status %d", ipp_status); } ================================================ FILE: jpegloader.c ================================================ #include "accimage.h" #include #include #ifndef LIBJPEG_TURBO_VERSION #error libjpeg-turbo required (not IJG libjpeg) #endif struct accimage_jpeg_error_mgr { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; }; static void accimage_jpeg_error_exit(j_common_ptr cinfo) { struct accimage_jpeg_error_mgr* accimage_err = (struct accimage_jpeg_error_mgr*) cinfo->err; longjmp(accimage_err->setjmp_buffer, 1); } void image_from_file(ImageObject* self, FILE* file) { struct jpeg_decompress_struct state = { 0 }; struct accimage_jpeg_error_mgr jpeg_error; unsigned char* buffer = NULL; state.err = jpeg_std_error(&jpeg_error.pub); jpeg_error.pub.error_exit = accimage_jpeg_error_exit; if (setjmp(jpeg_error.setjmp_buffer)) { char error_message[JMSG_LENGTH_MAX]; (*state.err->format_message)((j_common_ptr) &state, error_message); PyErr_Format(PyExc_IOError, "JPEG decoding failed: %s", error_message); goto cleanup; } jpeg_create_decompress(&state); jpeg_stdio_src(&state, file); jpeg_read_header(&state, TRUE); state.dct_method = JDCT_ISLOW; state.output_components = 3; state.out_color_components = 3; state.out_color_space = JCS_RGB; jpeg_start_decompress(&state); buffer = malloc(state.output_components * state.output_width * state.output_height); if (buffer == NULL) { PyErr_NoMemory(); goto cleanup; } while (state.output_scanline < state.output_height) { unsigned char* row = buffer + state.output_scanline * state.output_width * state.output_components; jpeg_read_scanlines(&state, &row, 1); } jpeg_finish_decompress(&state); /* Success. Commit object state */ self->buffer = buffer; buffer = NULL; self->channels = 3; self->height = state.output_height; self->width = state.output_width; self->row_stride = state.output_width; self->y_offset = 0; self->x_offset = 0; cleanup: jpeg_destroy_decompress(&state); free(buffer); if (file != NULL) { fclose(file); } } void image_from_buffer(ImageObject* self, void* buf, size_t size) { FILE* source = fmemopen(buf, size, "rb"); if (source == NULL) { PyErr_Format(PyExc_IOError, "failed to call fmemopen on buffer"); return; } image_from_file(self, source); } void image_from_jpeg(ImageObject* self, const char* path) { FILE* file = file = fopen(path, "rb"); if (file == NULL) { PyErr_Format(PyExc_IOError, "failed to open file %s", path); return; } image_from_file(self, file); } ================================================ FILE: setup.py ================================================ from distutils.core import setup, Extension accimage = Extension( "accimage", include_dirs=["/usr/local/opt/jpeg-turbo/include", "/opt/intel/ipp/include"], libraries=["jpeg", "ippi", "ipps"], library_dirs=["/usr/local/opt/jpeg-turbo/lib", "/opt/intel/ipp/lib"], sources=["accimagemodule.c", "jpegloader.c", "imageops.c"], ) setup( name="accimage", version="0.2.0", description="Accelerated image loader and preprocessor for Torch", author="Marat Dukhan", author_email="maratek@gmail.com", ext_modules=[accimage], ) ================================================ FILE: test.py ================================================ import accimage import numpy as np import imageio import os ACCIMAGE_SAVE = os.environ.get('ACCIMAGE_SAVE', '') if len(ACCIMAGE_SAVE) and ACCIMAGE_SAVE.lower() not in {'0', 'false', 'no'}: SAVE_IMAGES = True else: SAVE_IMAGES = False def image_to_np(image): """ Returns: np.ndarray: Image converted to array with shape (width, height, channels) """ image_np = np.empty([image.channels, image.height, image.width], dtype=np.uint8) image.copyto(image_np) image_np = np.transpose(image_np, (1, 2, 0)) return image_np def save_image(path, image): imageio.imwrite(path, image_to_np(image)) def test_reading_image(): image = accimage.Image("chicago.jpg") if SAVE_IMAGES: save_image('test_reading_image.jpg', image) assert image.width == 1920 assert image.height == 931 def test_reading_image_from_memory(): from_file = accimage.Image("chicago.jpg") bytes = open("chicago.jpg", "rb").read() from_bytes = accimage.Image(bytes) if SAVE_IMAGES: save_image('test_reading_image_from_memory.jpg', from_bytes) assert from_bytes.width == 1920 assert from_bytes.height == 931 np.testing.assert_array_equal(image_to_np(from_file), image_to_np(from_bytes)) def test_resizing(): image = accimage.Image("chicago.jpg") image.resize(size=(200, 200)) if SAVE_IMAGES: save_image('test_resizing.jpg', image) assert image.width == 200 assert image.height == 200 def test_cropping(): image = accimage.Image("chicago.jpg") image.crop(box=(50, 50, 150, 150)) if SAVE_IMAGES: save_image('test_cropping.jpg', image) assert image.width == 100 assert image.height == 100 def test_flipping(): image = accimage.Image("chicago.jpg") original_image_np = image_to_np(image) FLIP_LEFT_RIGHT = 0 image.transpose(FLIP_LEFT_RIGHT) if SAVE_IMAGES: save_image('test_flipping.jpg', image) new_image_np = image_to_np(image) assert image.width == 1920 assert image.height == 931 np.testing.assert_array_equal(new_image_np[:, ::-1, :], original_image_np)