Repository: pydicom/pylibjpeg-openjpeg (language: python)

Issue:
Pip install failing - unable to find build_tools
***Background***

I'm using a M1 mac mini dev machine, and have previously been using Anaconda based conda for managing environments.  This only presently supports using mac x86 versions of everything, which while they work, use mac's rosetta to run the x86 code non-natively.

So I'm cleaning all that cruft out, and using native miniconda mac-arm versions of everything.

Everything has worked fine with installing native python and all the modules I use, right up until pylibjpeg.  (Including pydicom, which uses `noarch` version and runs perfectly it seems so far).

***Problem***

Using pip (specifically miniconda's pip) to install pylibjpeg fails when trying to install pylibjpeg-openjpeg:

The problem is pip is pullling down `pylibjpeg-openjpeg-1.1.1.tar.gz`, and then `setup.py` is failing as its unable to find CMakeLists.txt.

A quick check by manually downloading `pylibjpeg-openjpeg-1.1.1.tar.gz` shows that the entire `build_tools` directory is missing:

```
(aqs-tests-2)  pylibjpeg-openjpeg-1.1.1 >> ls
MANIFEST.in                 README.md                   pylibjpeg_openjpeg.egg-info setup.py
PKG-INFO                    openjpeg                    setup.cfg
(aqs-tests-2)  pylibjpeg-openjpeg-1.1.1 >> find . -name build_tools
(aqs-tests-2)  pylibjpeg-openjpeg-1.1.1 >> ls openjpeg/
__init__.py   _openjpeg.pyx _version.py   src           tests         utils.py
```

**Question**
Should I be using a specific (different) tarball instead of what pip's defaulting to (`pylibjpeg-openjpeg-1.1.1.tar.gz`)?

***Pip Install output:***

```
Conda-arm-install-stuff >> pip download pylibjpeg
Collecting pylibjpeg
  Downloading pylibjpeg-1.3.0-py3-none-any.whl (28 kB)
Collecting pylibjpeg-openjpeg
  Downloading pylibjpeg-openjpeg-1.1.1.tar.gz (2.3 MB)
     |████████████████████████████████| 2.3 MB 2.1 MB/s 
    ERROR: Command errored out with exit status 1:
     command: /Users/rjm/miniforge3/envs/aqs-tests-2/bin/python3.8 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_93f9a1031bbc467692cd36833771bcd2/setup.py'"'"'; __file__='"'"'/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_93f9a1031bbc467692cd36833771bcd2/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-pip-egg-info-lfbbjo5z
         cwd: /private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_93f9a1031bbc467692cd36833771bcd2/
    Complete output (11 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_93f9a1031bbc467692cd36833771bcd2/setup.py", line 87, in <module>
        setup_oj()
      File "/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_93f9a1031bbc467692cd36833771bcd2/setup.py", line 60, in setup_oj
        shutil.copy(
      File "/Users/rjm/miniforge3/envs/aqs-tests-2/lib/python3.8/shutil.py", line 418, in copy
        copyfile(src, dst, follow_symlinks=follow_symlinks)
      File "/Users/rjm/miniforge3/envs/aqs-tests-2/lib/python3.8/shutil.py", line 264, in copyfile
        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
    FileNotFoundError: [Errno 2] No such file or directory: 'build_tools/cmake/CMakeLists.txt'
    ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/71/e9/94f3d009e9f9f31bb76075641214bdabdbe557b26ce74ff71ef1c28b2c05/pylibjpeg-openjpeg-1.1.1.tar.gz#sha256=e7adfd8e2c63661b75219d5ae381ac50381054df41ca94d0d01ddfbe362a66cc (from https://pypi.org/simple/pylibjpeg-openjpeg/) (requires-python:>=3.6). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
  Downloading pylibjpeg-openjpeg-1.1.0.tar.gz (2.3 MB)
     |████████████████████████████████| 2.3 MB 5.7 MB/s 
    ERROR: Command errored out with exit status 1:
     command: /Users/rjm/miniforge3/envs/aqs-tests-2/bin/python3.8 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_cafc2c9526f54399948b001f3b4ac833/setup.py'"'"'; __file__='"'"'/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_cafc2c9526f54399948b001f3b4ac833/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-pip-egg-info-6b4peu5w
         cwd: /private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_cafc2c9526f54399948b001f3b4ac833/
    Complete output (11 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_cafc2c9526f54399948b001f3b4ac833/setup.py", line 87, in <module>
        setup_oj()
      File "/private/var/folders/t3/6rfnr3rd6jl7_jg08ppg5h3c0000gq/T/pip-download-3opfmxjo/pylibjpeg-openjpeg_cafc2c9526f54399948b001f3b4ac833/setup.py", line 60, in setup_oj
        shutil.copy(
      File "/Users/rjm/miniforge3/envs/aqs-tests-2/lib/python3.8/shutil.py", line 418, in copy
        copyfile(src, dst, follow_symlinks=follow_symlinks)
      File "/Users/rjm/miniforge3/envs/aqs-tests-2/lib/python3.8/shutil.py", line 264, in copyfile
        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
    FileNotFoundError: [Errno 2] No such file or directory: 'build_tools/cmake/CMakeLists.txt'
    ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/49/1c/43de9deec30e2a641a3a2f11efa6c13185fa2c77a962676e370a282a3814/pylibjpeg-openjpeg-1.1.0.tar.gz#sha256=66514f5d5df6fdfc704296a8420e4b1feb335fad4e6901435e00c37f353fb44e (from https://pypi.org/simple/pylibjpeg-openjpeg/) (requires-python:>=3.7). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
Collecting pylibjpeg
  Downloading pylibjpeg-1.2.0-py3-none-any.whl (27 kB)
  Downloading pylibjpeg-1.1.1-py3-none-any.whl (29 kB)
  Downloading pylibjpeg-1.1.0-py3-none-any.whl (29 kB)
  Downloading pylibjpeg-1.0.0-py2.py3-none-any.whl (31 kB)
```

Require numpy 1.20 or newer
The package currently requires numpy 1.16 or newer, but there was a change in the C API with version 1.20. This is causing a ValueError when importing the openjpeg package when a numpy version < 1.20 is installed:

> ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 from PyObject

See also https://stackoverflow.com/questions/66060487/valueerror-numpy-ndarray-size-changed-may-indicate-binary-incompatibility-exp


Discussion / hints:
...bit more info (not sure if it's related, but thought I'd throw in in):

when I downloaded the current master, and `python setup.py sdist` the tarball file didn't contain the build_tools directory either.

It also had a cmake error:
```
(aqs-tests-2)  pylibjpeg-openjpeg-master >> python setup.py sdist
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 2.8.12 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.


-- The C compiler identification is AppleClang 12.0.5.12050022
-- The CXX compiler identification is AppleClang 12.0.5.12050022
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Check if the system is big endian
-- Searching 16 bit integer
-- Looking for sys/types.h
-- Looking for sys/types.h - found
-- Looking for stdint.h
-- Looking for stdint.h - found
-- Looking for stddef.h
-- Looking for stddef.h - found
-- Check size of unsigned short
-- Check size of unsigned short - done
-- Searching 16 bit integer - Using unsigned short
-- Check if the system is big endian - little endian
CMake Error at CMakeLists.txt:71 (include):
  include could not find load file:

    EnsureFileInclude


CMake Error at CMakeLists.txt:73 (ensure_file_include):
  Unknown CMake command "ensure_file_include".


-- Configuring incomplete, errors occurred!
See also "/Users/rjm/Conda-arm-install-stuff/pull-version/pylibjpeg-openjpeg-master/openjpeg/src/openjpeg/build/CMakeFiles/CMakeOutput.log".
See also "/Users/rjm/Conda-arm-install-stuff/pull-version/pylibjpeg-openjpeg-master/openjpeg/src/openjpeg/build/CMakeFiles/CMakeError.log".
```
Yeah it's not in the `MANIFEST.in` file, I'll fix it up and put out a patch release.
@scaramallion thanks!

...is that also the cause of `python setup.py sdist` to break when I tried to run manually?
Is there any additional information on this? I am attempting to run this as a github action on `ubuntu-latest` and getting the following similar error as above?

``` EnvCommandError

  Command ['/home/runner/work/xxx/xxx/.venv/bin/pip', 'install', '--no-deps', '/home/runner/.cache/pypoetry/artifacts/e6/29/38/5e6b3a7fdf89c0c6f7299dbea38863f5a82944b105c9a19791ebcd73b1/pylibjpeg-openjpeg-1.1.1.tar.gz'] errored with the following return code 1, and output: 
  Processing /home/runner/.cache/pypoetry/artifacts/e6/29/38/5e6b3a7fdf89c0c6f7299dbea38863f5a82944b105c9a19791ebcd73b1/pylibjpeg-openjpeg-1.1.1.tar.gz
    Preparing metadata (setup.py): started
    Preparing metadata (setup.py): finished with status 'error'
    ERROR: Command errored out with exit status 1:
     command: /home/runner/work/xxx/xxx/.venv/bin/python -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-req-build-8a1ozfo7/setup.py'"'"'; __file__='"'"'/tmp/pip-req-build-8a1ozfo7/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-zh1q6ubl
         cwd: /tmp/pip-req-build-8a1ozfo7/
    Complete output (11 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-req-build-8a1ozfo7/setup.py", line 87, in <module>
        setup_oj()
      File "/tmp/pip-req-build-8a1ozfo7/setup.py", line 60, in setup_oj
        shutil.copy(
      File "/opt/hostedtoolcache/Python/3.10.0/x64/lib/python3.10/shutil.py", line 417, in copy
        copyfile(src, dst, follow_symlinks=follow_symlinks)
      File "/opt/hostedtoolcache/Python/3.10.0/x64/lib/python3.10/shutil.py", line 254, in copyfile
        with open(src, 'rb') as fsrc:
    FileNotFoundError: [Errno 2] No such file or directory: 'build_tools/cmake/CMakeLists.txt'
    ----------------------------------------
  WARNING: Discarding file:///home/runner/.cache/pypoetry/artifacts/e6/29/38/5e6b3a7fdf89c0c6f7299dbea38863f5a82944b105c9a19791ebcd73b1/pylibjpeg-openjpeg-1.1.1.tar.gz. Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
  ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
  

  at /opt/hostedtoolcache/Python/3.10.0/x64/lib/python3.10/site-packages/poetry/utils/env.py:1183 in _run
      1179│                 output = subprocess.check_output(
      1180│                     cmd, stderr=subprocess.STDOUT, **kwargs
      1181│                 )
      1182│         except CalledProcessError as e:
    → 1183│             raise EnvCommandError(e, input=input_)
      1184│ 
      1185│         return decode(output)
      1186│ 
      1187│     def execute(self, bin, *args, **kwargs):```
@jamesrusso can you try installing with the tar archive from GitHub instead: https://github.com/pydicom/pylibjpeg-openjpeg/archive/refs/tags/v1.1.1.tar.gz

Because you're using Python 3.10 pip is falling back on the source files to rebuild
Thanks. I was able to get it installed via source in the github action script, but it was a bit messy. 
@scaramallion is there a change this could be fixed and an updated packages uploaded to pypi? This issue breaks pip installation of any package that depends on the library.


Fix patch:
diff --git a/.github/workflows/release-wheels.yml b/.github/workflows/release-wheels.yml
index 7837753..ba1df1a 100644
--- a/.github/workflows/release-wheels.yml
+++ b/.github/workflows/release-wheels.yml
@@ -1,20 +1,126 @@
-name: Build wheels and deploy to PyPI
+name: release-deploy
 
 on:
   release:
     types: [ published ]
 
 jobs:
-  build_wheels:
-    name: Build wheels for ${{ matrix.os }}
+  build-sdist:
+    name: Build source distribution
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+
+    - uses: actions/setup-python@v2
+      name: Install Python
+      with:
+        python-version: '3.10'
+
+    - name: Build sdist
+      run: |
+        python -m pip install .
+        python setup.py sdist
+
+    - name: Store artifacts
+      uses: actions/upload-artifact@v2
+      with:
+        name: wheels
+        path: ./dist
+
+  build-wheels:
+    name: Build wheel for cp${{ matrix.python }}-${{ matrix.platform_id }}-${{ matrix.manylinux_image }}
     runs-on: ${{ matrix.os }}
-    env:
-      CIBW_BUILD: "cp3*-*"
-      CIBW_SKIP: "cp35-*"
     strategy:
+      fail-fast: false
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
-        python-version: [3.9]
+        include:
+          # Windows 32 bit
+          - os: windows-latest
+            python: 37
+            platform_id: win32
+          - os: windows-latest
+            python: 38
+            platform_id: win32
+          - os: windows-latest
+            python: 39
+            platform_id: win32
+          - os: windows-latest
+            python: 310
+            platform_id: win32
+
+          # Windows 64 bit
+          - os: windows-latest
+            python: 37
+            platform_id: win_amd64
+          - os: windows-latest
+            python: 38
+            platform_id: win_amd64
+          - os: windows-latest
+            python: 39
+            platform_id: win_amd64
+          - os: windows-latest
+            python: 310
+            platform_id: win_amd64
+
+          # Linux 64 bit manylinux2010
+          - os: ubuntu-latest
+            python: 37
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2010
+          - os: ubuntu-latest
+            python: 38
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2010
+          - os: ubuntu-latest
+            python: 39
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2010
+
+          # Linux 64 bit manylinux2014
+          - os: ubuntu-latest
+            python: 37
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2014
+          - os: ubuntu-latest
+            python: 38
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2014
+          - os: ubuntu-latest
+            python: 39
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2014
+          - os: ubuntu-latest
+            python: 310
+            platform_id: manylinux_x86_64
+            manylinux_image: manylinux2014
+
+          # MacOS x86_64
+          - os: macos-latest
+            python: 37
+            platform_id: macosx_x86_64
+          - os: macos-latest
+            python: 38
+            platform_id: macosx_x86_64
+          - os: macos-latest
+            python: 39
+            platform_id: macosx_x86_64
+          - os: macos-latest
+            python: 310
+            platform_id: macosx_x86_64
+
+          # MacOS arm64
+          - os: macos-latest
+            python: 38
+            platform_id: macosx_arm64
+          - os: macos-latest
+            python: 39
+            platform_id: macosx_arm64
+          - os: macos-latest
+            python: 310
+            platform_id: macosx_arm64
 
     steps:
     - uses: actions/checkout@v2
@@ -24,34 +130,89 @@ jobs:
     - uses: actions/setup-python@v2
       name: Install Python
       with:
-        python-version: ${{ matrix.python-version }}
+        python-version: '3.9'
 
     - name: Install cibuildwheel
       run: |
-        python -m pip install cibuildwheel==1.10.0
-
-    - name: Build sdist
-      run: |
-         python setup.py sdist
+        python -m pip install -U pip
+        python -m pip install cibuildwheel==2.3.1
 
     - name: Build wheels
+      env:
+        CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
+        CIBW_ARCHS: all
+        CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux_image }}
+        CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.manylinux_image }}
+        CIBW_BUILD_VERBOSITY: 1
       run: |
         python --version
         python -m cibuildwheel --output-dir dist
 
-    - uses: actions/upload-artifact@v2
+    - name: Store artifacts
+      uses: actions/upload-artifact@v2
       with:
         name: wheels
         path: ./dist
 
+  test-package:
+    name: Test built package
+    needs: [ build-wheels, build-sdist ]
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ['3.7', '3.8', '3.9', '3.10']
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Download the wheels
+      uses: actions/download-artifact@v2
+      with:
+        name: wheels
+        path: dist/
+
+    - name: Install from package wheels and test
+      run: |
+        python -m venv env/test
+        source env/test/bin/activate
+        python -m pip install -U pip
+        python -m pip install pytest pydicom pylibjpeg
+        python -m pip uninstall -y pylibjpeg-openjpeg
+        python -m pip install git+https://github.com/pydicom/pylibjpeg-data
+        python -m pip install -U --find-links dist/ pylibjpeg-openjpeg
+        python -c "import pytest; pytest.main(['--pyargs', 'openjpeg.tests'])"
+        deactivate
+
+    - name: Install from package tarball and test
+      run: |
+        python -m venv env/testsrc
+        source env/testsrc/bin/activate
+        python -m pip install -U pip
+        python -m pip install pytest pydicom pylibjpeg
+        python -m pip uninstall -y pylibjpeg-openjpeg
+        python -m pip install git+https://github.com/pydicom/pylibjpeg-data
+        python -m pip install -U dist/pylibjpeg-openjpeg-*.tar.gz
+        python -c "import pytest; pytest.main(['--pyargs', 'openjpeg.tests'])"
+        deactivate
+
+
   # The pypi upload fails with non-linux containers, so grab the uploaded
   #     artifacts and run using those
   # See: https://github.com/pypa/gh-action-pypi-publish/discussions/15
   deploy:
     name: Upload wheels to PyPI
-    needs:
-    - build_wheels
+    needs: [ test-package ]
     runs-on: ubuntu-latest
+    timeout-minutes: 10
 
     steps:
     - name: Download the wheels
diff --git a/MANIFEST.in b/MANIFEST.in
index 47c5918..bb7848e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,1 +1,11 @@
-recursive-include openjpeg/src *
+recursive-include openjpeg/src/interface *
+
+# openjpeg source file includes
+recursive-include openjpeg/src/openjpeg/src/lib/openjp2 *
+include openjpeg/src/openjpeg/src/lib/CMakeLists.txt
+include openjpeg/src/openjpeg/src/CMakeLists.txt
+recursive-include openjpeg/src/openjpeg/cmake *
+include openjpeg/src/openjpeg/*.md
+include openjpeg/src/openjpeg/LICENSE
+
+recursive-include build_tools *
diff --git a/openjpeg/_version.py b/openjpeg/_version.py
index 8a11bd2..97bee01 100644
--- a/openjpeg/_version.py
+++ b/openjpeg/_version.py
@@ -3,7 +3,7 @@
 import re
 
 
-__version__ = '1.1.1'
+__version__ = '1.2.0'
 
 
 VERSION_PATTERN = r"""
diff --git a/openjpeg/utils.py b/openjpeg/utils.py
index 18e488a..912f2a1 100644
--- a/openjpeg/utils.py
+++ b/openjpeg/utils.py
@@ -133,7 +133,7 @@ def decode(stream, j2k_format=None, reshape=True):
     return arr.reshape(*shape)
 
 
-def decode_pixel_data(stream, ds=None):
+def decode_pixel_data(stream, ds=None, **kwargs):
     """Return the decoded JPEG 2000 data as a :class:`numpy.ndarray`.
 
     Intended for use with *pydicom* ``Dataset`` objects.
@@ -181,32 +181,43 @@ def decode_pixel_data(stream, ds=None):
 
     arr = _openjpeg.decode(stream, j2k_format)
 
-    if not ds:
+    samples_per_pixel = kwargs.get("samples_per_pixel")
+    bits_stored = kwargs.get("bits_stored")
+    pixel_representation = kwargs.get("pixel_representation")
+    no_kwargs = None in (
+        samples_per_pixel, bits_stored, pixel_representation
+    )
+
+    if not ds and no_kwargs:
         return arr
 
+    samples_per_pixel = ds.get("SamplesPerPixel", samples_per_pixel)
+    bits_stored = ds.get("BitsStored", bits_stored)
+    pixel_representation = ds.get("PixelRepresentation", pixel_representation)
+
     meta = get_parameters(stream, j2k_format)
-    if ds.SamplesPerPixel != meta["nr_components"]:
+    if samples_per_pixel != meta["nr_components"]:
         warnings.warn(
-            f"The (0028,0002) Samples per Pixel value '{ds.SamplesPerPixel}' "
+            f"The (0028,0002) Samples per Pixel value '{samples_per_pixel}' "
             f"in the dataset does not match the number of components "
             f"\'{meta['nr_components']}\' found in the JPEG 2000 data. "
             f"It's recommended that you change the  Samples per Pixel value "
             f"to produce the correct output"
         )
 
-    if ds.BitsStored != meta["precision"]:
+    if bits_stored != meta["precision"]:
         warnings.warn(
-            f"The (0028,0101) Bits Stored value '{ds.BitsStored}' in the "
+            f"The (0028,0101) Bits Stored value '{bits_stored}' in the "
             f"dataset does not match the component precision value "
             f"\'{meta['precision']}\' found in the JPEG 2000 data. "
             f"It's recommended that you change the Bits Stored value to "
             f"produce the correct output"
         )
 
-    if bool(ds.PixelRepresentation) != meta["is_signed"]:
+    if bool(pixel_representation) != meta["is_signed"]:
         val = "signed" if meta["is_signed"] else "unsigned"
-        ds_val = "signed" if bool(ds.PixelRepresentation) else "unsigned"
-        ds_val = f"'{ds.PixelRepresentation}' ({ds_val})"
+        ds_val = "signed" if bool(pixel_representation) else "unsigned"
+        ds_val = f"'{pixel_representation}' ({ds_val})"
         warnings.warn(
             f"The (0028,0103) Pixel Representation value {ds_val} in the "
             f"dataset does not match the format of the values found in the "
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..db85ff0
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+    "setuptools == 60.5", "cython == 0.29.26", "wheel == 0.37",
+    "numpy==1.20; python_version=='3.7'",
+    "numpy==1.22; python_version>='3.8'",
+]
diff --git a/requirements.txt b/requirements.txt
index 5dbf5a2..f7b467f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
 cython
-numpy>=1.16.0
+numpy>=1.20.0
diff --git a/setup.py b/setup.py
index 664f222..21bd382 100644
--- a/setup.py
+++ b/setup.py
@@ -2,34 +2,23 @@
 import os
 import sys
 from pathlib import Path
-import platform
 import setuptools
 from setuptools import setup, find_packages
 from setuptools.extension import Extension
 import shutil
 import subprocess
-from distutils.command.build import build as build_orig
 import distutils.sysconfig
 
+try:
+    import numpy
+except ImportError:
+    pass
 
 PACKAGE_DIR = Path(__file__).parent / "openjpeg"
 OPENJPEG_SRC = PACKAGE_DIR / "src" / "openjpeg" / "src" / "lib" / "openjp2"
 INTERFACE_SRC = PACKAGE_DIR / "src" / "interface"
 
 
-# Workaround for needing Cython and numpy
-# Solution from: https://stackoverflow.com/a/54128391/12606901
-class build(build_orig):
-    def finalize_options(self):
-        super().finalize_options()
-        __builtins__.__NUMPY_SETUP__ = False
-        import numpy
-        extension = next(
-            m for m in self.distribution.ext_modules if m == ext
-        )
-        extension.include_dirs.append(numpy.get_include())
-
-
 def get_source_files():
     """Return a list of paths to the source files to be compiled."""
     source_files = [
@@ -98,6 +87,7 @@ def setup_oj():
     include_dirs=[
         OPENJPEG_SRC,
         INTERFACE_SRC,
+        numpy.get_include(),
         distutils.sysconfig.get_python_inc(),
         # Numpy includes get added by the `build` subclass
     ],
@@ -143,18 +133,16 @@ def setup_oj():
         "Intended Audience :: Science/Research",
         "Development Status :: 5 - Production/Stable",
         "Natural Language :: English",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Operating System :: OS Independent",
         "Topic :: Scientific/Engineering :: Medical Science Apps.",
         "Topic :: Software Development :: Libraries",
     ],
-    python_requires = ">=3.6",
-    setup_requires = ["setuptools>=18.0", "cython", "numpy"],
-    install_requires = ["numpy"],
-    cmdclass = {"build": build},
+    python_requires = ">=3.7",
+    install_requires = ["numpy>=1.20"],
     ext_modules = [ext],
     # Plugin registrations
     entry_points = {


Test patch:
diff --git a/.github/workflows/pytest-builds.yml b/.github/workflows/pytest-builds.yml
index 28439ca..fc82663 100644
--- a/.github/workflows/pytest-builds.yml
+++ b/.github/workflows/pytest-builds.yml
@@ -1,4 +1,4 @@
-name: build
+name: unit-tests
 
 on:
   push:
@@ -8,11 +8,11 @@ on:
 jobs:
   windows:
     runs-on: windows-latest
-    timeout-minutes: 10
+    timeout-minutes: 30
     strategy:
       fail-fast: false
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: ['3.7', '3.8', '3.9', '3.10']
         arch: ['x64', 'x86']
 
     steps:
@@ -29,9 +29,9 @@ jobs:
     - name: Install package and dependencies
       run: |
         python -m pip install -U pip
-        python -m pip install pytest coverage pytest-cov cython numpy wheel
-        python -m pip install .
+        python -m pip install -U pytest coverage pytest-cov
         python -m pip install git+https://github.com/pydicom/pylibjpeg-data
+        python -m pip install .
 
     - name: Run pytest
       run: |
@@ -44,11 +44,11 @@ jobs:
 
   osx:
     runs-on: macos-latest
-    timeout-minutes: 10
+    timeout-minutes: 30
     strategy:
       fail-fast: false
       matrix:
-        python-version: [3.7, 3.8, 3.9]
+        python-version: ['3.7', '3.8', '3.9', '3.10']
 
     steps:
     - uses: actions/checkout@v2
@@ -63,10 +63,9 @@ jobs:
     - name: Install package and dependencies
       run: |
         python -m pip install -U pip
-        python -m pip install wheel
-        python -m pip install .
         python -m pip install pytest coverage pytest-cov
         python -m pip install git+https://github.com/pydicom/pylibjpeg-data
+        python -m pip install .
 
     - name: Run pytest
       run: |
@@ -79,11 +78,11 @@ jobs:
 
   ubuntu:
     runs-on: ubuntu-latest
-    timeout-minutes: 10
+    timeout-minutes: 30
     strategy:
       fail-fast: false
       matrix:
-        python-version: [3.7, 3.8, 3.9]
+        python-version: ['3.7', '3.8', '3.9', '3.10']
 
     steps:
     - uses: actions/checkout@v2
@@ -98,10 +97,9 @@ jobs:
     - name: Install package and dependencies
       run: |
         python -m pip install -U pip
-        python -m pip install wheel
-        python -m pip install .
         python -m pip install pytest coverage pytest-cov
         python -m pip install git+https://github.com/pydicom/pylibjpeg-data
+        python -m pip install .
 
     - name: Run pytest
       run: |
@@ -119,9 +117,5 @@ jobs:
         pytest --cov openjpeg --ignore=openjpeg/src/openjpeg
 
     - name: Send coverage results
-      if: ${{ success() }}
-      run: |
-        bash <(curl --connect-timeout 10 --retry 10 --retry-max-time \
-        0 https://codecov.io/bash) || (sleep 30 && bash <(curl \
-        --connect-timeout 10 --retry 10 --retry-max-time \
-        0 https://codecov.io/bash))
+      if: ${{ success() && matrix.coverage == 'coverage' }}
+      uses: codecov/codecov-action@v1