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