From bc250357cee6d642f1bb62fbd49da3292f3238ad Mon Sep 17 00:00:00 2001 From: scaramallion Date: Sat, 22 Jan 2022 15:54:53 +1100 Subject: [PATCH] Update interface, workflows (#68) --- .github/workflows/pytest-builds.yml | 27 +- .github/workflows/release-deploy.yml | 2 +- README.md | 12 +- docs/plugins.md | 54 +++- pylibjpeg/__init__.py | 6 +- pylibjpeg/_version.py | 14 +- pylibjpeg/pydicom/utils.py | 37 ++- pylibjpeg/tests/__init__.py | 8 +- pylibjpeg/tests/test_decode.py | 90 +++--- pylibjpeg/tests/test_decode_pydicom.py | 166 ++++++----- pylibjpeg/tools/jpegio.py | 23 +- pylibjpeg/tools/s10918/_markers.py | 152 +++++----- pylibjpeg/tools/s10918/_parsers.py | 118 ++++---- pylibjpeg/tools/s10918/_printers.py | 388 ++++++++++++++----------- pylibjpeg/tools/s10918/io.py | 61 ++-- pylibjpeg/tools/s10918/rep.py | 49 ++-- pylibjpeg/tools/tests/test_utils.py | 64 ++-- pylibjpeg/utils.py | 47 ++- requirements.txt | 2 +- setup.py | 62 ++-- 20 files changed, 754 insertions(+), 628 deletions(-) diff --git a/.github/workflows/pytest-builds.yml b/.github/workflows/pytest-builds.yml index ce9ff94..6ea909e 100644 --- a/.github/workflows/pytest-builds.yml +++ b/.github/workflows/pytest-builds.yml @@ -1,4 +1,4 @@ -name: build +name: unit-tests on: push: @@ -12,7 +12,7 @@ jobs: 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'] steps: - uses: actions/checkout@v2 @@ -25,11 +25,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 uninstall -y pylibjpeg-openjpeg + python -m pip install -U wheel pytest coverage pytest-cov python -m pip install git+https://github.com/pydicom/pylibjpeg-data - python -m pip install pytest coverage pytest-cov + python -m pip install . - name: Run pytest with no plugins run: | @@ -44,7 +42,6 @@ jobs: run: | pip install git+https://github.com/pydicom/pylibjpeg-libjpeg - pip install git+https://github.com/pydicom/pylibjpeg-openjpeg pytest --cov=pylibjpeg --cov-append - name: Rerun pytest with -oj plugin @@ -53,15 +50,17 @@ jobs: pip install git+https://github.com/pydicom/pylibjpeg-openjpeg pytest --cov=pylibjpeg --cov-append - - name: Rerun pytest with -oj and -lj plugins + - name: Rerun pytest with -rle plugin run: | - pip install git+https://github.com/pydicom/pylibjpeg-libjpeg + pip uninstall -y pylibjpeg-libjpeg pylibjpeg-openjpeg + pip install git+https://github.com/pydicom/pylibjpeg-rle + pytest --cov=pylibjpeg --cov-append + + - name: Rerun pytest with all plugins + run: | + pip install .[all] pytest --cov=pylibjpeg --cov-append - 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)) + uses: codecov/codecov-action@v1 diff --git a/.github/workflows/release-deploy.yml b/.github/workflows/release-deploy.yml index 32c9769..768e9e6 100644 --- a/.github/workflows/release-deploy.yml +++ b/.github/workflows/release-deploy.yml @@ -1,4 +1,4 @@ -name: Build package and deploy to PyPI +name: release-deploy on: release: diff --git a/README.md b/README.md index 4b7e515..7f6558b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## pylibjpeg -A Python 3.6+ framework for decoding JPEG images and decoding/encoding RLE datasets, with a focus on providing support for [pydicom](https://github.com/pydicom/pydicom). +A Python 3.7+ framework for decoding JPEG images and decoding/encoding RLE datasets, with a focus on providing support for [pydicom](https://github.com/pydicom/pydicom). ### Installation @@ -17,10 +17,16 @@ pip install pylibjpeg ##### Installing extra requirements -The package can be installed with extra requirements `openjpeg` or `rle` to enable support for JPEG-2000 and Run-Length Encoding (RLE), respectively: +The package can be installed with extra requirements to enable support for JPEG (with `libjpeg`), JPEG 2000 (with `openjpeg`) and Run-Length Encoding (RLE) (with `rle`), respectively: ``` -pip install pylibjpeg[openjpeg,rle] +pip install pylibjpeg[libjpeg,openjpeg,rle] +``` + +Or alternatively with just `all`: + +``` +pip install pylibjpeg[all] ``` #### Installing the development version diff --git a/docs/plugins.md b/docs/plugins.md index 8ba4ced..2f0f2e8 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -24,19 +24,43 @@ setup( #### Decoder function signature -The pixel data decoding function will be passed two required parameters: +The pixel data decoding function will be passed one required parameter: * *src*: a single encoded image frame as [bytes](https://docs.python.org/3/library/stdtypes.html#bytes) + +And at least one of: * *ds*: a *pydicom* [Dataset](https://pydicom.github.io/pydicom/stable/reference/generated/pydicom.dataset.Dataset.html) object containing the (0028,eeee) elements corresponding to the pixel data +* *kwargs*: a dict with at least the following keys: + * `"transfer_syntax_uid": pydicom.uid.UID` - the *Transfer Syntax UID* of + the encoded data. + * `'rows': int` - the number of rows of pixels in the *src*. + * `'columns': int` - the number of columns of pixels in the + *src*. + * `'samples_per_pixel': int` - the number of samples used per + pixel, e.g. `1` for grayscale images or `3` for RGB. + * `'bits_allocated': int` - the number of bits used to contain + each pixel in *src*, should be 8, 16, 32 or 64. + * `'bits_stored': int` - the number of bits actually used by + each pixel in *src*. + * `'bits_stored': int` - the number of bits actually used by + each pixel in *src*, e.g. 12-bit pixel data (range 0 to 4095) will be + contained by 16-bits (range 0 to 65535). + * `'pixel_representation': int` - the type of data in *src*, + `0` for unsigned integers, `1` for 2's complement (signed) + integers. + * `'photometric_interpretation': str` - the color space + of the encoded data, such as `'YBR_FULL'`. + +Other decoder-specific optional keyword parameters may also be present. The function should return the decoded pixel data as a one-dimensional numpy [ndarray](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html) of little-endian ordered `'uint8'`, with the data ordered from left-to-right, top-to-bottom (i.e. the first byte corresponds to the upper left pixel and the last byte corresponds to the lower-right pixel) and a planar configuration that matches the requirements of the transfer syntax: ```python def my_pixel_data_decoder( - src: bytes, ds: pydicom.dataset.Dataset, **kwargs + src: bytes, ds: Optional[pydicom.dataset.Dataset] = None, **kwargs: Any ) -> numpy.ndarray: - """Return the encoded `src` as an unshaped numpy ndarray of uint8. + """Return the encoded *src* as an unshaped numpy ndarray of uint8. .. versionchanged:: 1.3 @@ -46,11 +70,27 @@ def my_pixel_data_decoder( ---------- src : bytes A single frame of the encoded *Pixel Data*. - ds : pydicom.dataset.Dataset + ds : pydicom.dataset.Dataset, optional A dataset containing the group ``0x0028`` elements corresponding to - the *Pixel Data*. - kwargs - Optional keyword parameters for the decoder. + the *Pixel Data*. If not used then *kwargs* must be supplied. + kwargs : Dict[str, Any] + A dict containing relevant image pixel module elements: + + * "rows": int - the number of rows of pixels in *src*, maximum 65535. + * "columns": int - the number of columns of pixels in *src*, maximum + 65535. + * "number_of_frames": int - the number of frames in *src*. + * "samples_per_pixel": int - the number of samples per pixel in *src*, + should be 1 or 3. + * "bits_allocated": int - the number of bits used to contain each + pixel, should be a multiple of 8. + * "bits_stored": int - the number of bits actually used per pixel. + * "pixel_representation": int - the type of data being decoded, 0 for + unsigned, 1 for 2's complement (signed) + * "photometric_interpretation": the color space of the *encoded* pixel + data, such as "YBR_FULL". + + And optional keyword parameters for the decoder. Returns ------- diff --git a/pylibjpeg/__init__.py b/pylibjpeg/__init__.py index 44eb221..647568c 100644 --- a/pylibjpeg/__init__.py +++ b/pylibjpeg/__init__.py @@ -8,17 +8,17 @@ # Setup default logging -_logger = logging.getLogger('pylibjpeg') +_logger = logging.getLogger("pylibjpeg") _logger.addHandler(logging.NullHandler()) _logger.debug("pylibjpeg v{}".format(__version__)) def debug_logger(): """Setup the logging for debugging.""" - logger = logging.getLogger('pylibjpeg') + logger = logging.getLogger("pylibjpeg") logger.handlers = [] handler = logging.StreamHandler() logger.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(levelname).1s: %(message)s') + formatter = logging.Formatter("%(levelname).1s: %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) diff --git a/pylibjpeg/_version.py b/pylibjpeg/_version.py index 1eca84c..2f5899a 100644 --- a/pylibjpeg/_version.py +++ b/pylibjpeg/_version.py @@ -3,7 +3,7 @@ import re -__version__ = '1.4.0' +__version__ = "1.4.0" VERSION_PATTERN = r""" @@ -41,11 +41,13 @@ def is_canonical(version): """Return True if `version` is a PEP440 conformant version.""" match = re.match( - r'^([1-9]\d*!)?(0|[1-9]\d*)' - r'(\.(0|[1-9]\d*))' - r'*((a|b|rc)(0|[1-9]\d*))' - r'?(\.post(0|[1-9]\d*))' - r'?(\.dev(0|[1-9]\d*))?$', version) + r"^([1-9]\d*!)?(0|[1-9]\d*)" + r"(\.(0|[1-9]\d*))" + r"*((a|b|rc)(0|[1-9]\d*))" + r"?(\.post(0|[1-9]\d*))" + r"?(\.dev(0|[1-9]\d*))?$", + version, + ) return match is not None diff --git a/pylibjpeg/pydicom/utils.py b/pylibjpeg/pydicom/utils.py index 4cf3f1c..df625cf 100644 --- a/pylibjpeg/pydicom/utils.py +++ b/pylibjpeg/pydicom/utils.py @@ -30,9 +30,7 @@ def generate_frames(ds): try: import pydicom except ImportError: - raise RuntimeError( - "'generate_frames' requires the pydicom package" - ) + raise RuntimeError("'generate_frames' requires the pydicom package") from pydicom.encaps import generate_pixel_data_frame from pydicom.pixel_data_handlers.util import pixel_dtype @@ -41,7 +39,7 @@ def generate_frames(ds): decode = decoders[ds.file_meta.TransferSyntaxUID] p_interp = ds.PhotometricInterpretation - nr_frames = getattr(ds, 'NumberOfFrames', 1) + nr_frames = getattr(ds, "NumberOfFrames", 1) for frame in generate_pixel_data_frame(ds.PixelData, nr_frames): arr = decode(frame, ds.group_dataset(0x0028)).view(pixel_dtype(ds)) yield reshape_frame(ds, arr) @@ -121,16 +119,16 @@ def reshape_frame(ds, arr): """ # Transfer Syntax UIDs that are always Planar Configuration 0 conf_zero = [ - '1.2.840.10008.1.2.4.50', - '1.2.840.10008.1.2.4.57', - '1.2.840.10008.1.2.4.70', - '1.2.840.10008.1.2.4.90', - '1.2.840.10008.1.2.4.91' + "1.2.840.10008.1.2.4.50", + "1.2.840.10008.1.2.4.57", + "1.2.840.10008.1.2.4.70", + "1.2.840.10008.1.2.4.90", + "1.2.840.10008.1.2.4.91", ] # Transfer Syntax UIDs that are always Planar Configuration 1 conf_one = [ - '1.2.840.10008.1.2.4.80', - '1.2.840.10008.1.2.4.81', + "1.2.840.10008.1.2.4.80", + "1.2.840.10008.1.2.4.81", ] # Valid values for Planar Configuration are dependent on transfer syntax @@ -147,8 +145,9 @@ def reshape_frame(ds, arr): if planar_configuration not in [0, 1]: raise ValueError( "Unable to reshape the pixel array as a value of {} for " - "(0028,0006) 'Planar Configuration' is invalid." - .format(planar_configuration) + "(0028,0006) 'Planar Configuration' is invalid.".format( + planar_configuration + ) ) if nr_samples == 1: @@ -187,22 +186,22 @@ def get_j2k_parameters(codestream): """ try: # First 2 bytes must be the SOC marker - if not then wrong format - if codestream[0:2] != b'\xff\x4f': + if codestream[0:2] != b"\xff\x4f": return {} # SIZ is required to be the second marker - Figure A-3 in 15444-1 - if codestream[2:4] != b'\xff\x51': + if codestream[2:4] != b"\xff\x51": return {} # See 15444-1 A.5.1 for format of the SIZ box and contents ssiz = ord(codestream[42:43]) parameters = {} if ssiz & 0x80: - parameters['precision'] = (ssiz & 0x7F) + 1 - parameters['is_signed'] = True + parameters["precision"] = (ssiz & 0x7F) + 1 + parameters["is_signed"] = True else: - parameters['precision'] = ssiz + 1 - parameters['is_signed'] = False + parameters["precision"] = ssiz + 1 + parameters["is_signed"] = False return parameters diff --git a/pylibjpeg/tests/__init__.py b/pylibjpeg/tests/__init__.py index 620c53d..68b1485 100644 --- a/pylibjpeg/tests/__init__.py +++ b/pylibjpeg/tests/__init__.py @@ -1,4 +1,3 @@ - import logging import sys @@ -6,9 +5,10 @@ try: import ljdata as _data - globals()['data'] = _data + + globals()["data"] = _data # Add to cache - needed for pytest - sys.modules['pylibjpeg.data'] = _data - _logger.debug('pylibjpeg-data module loaded') + sys.modules["pylibjpeg.data"] = _data + _logger.debug("pylibjpeg-data module loaded") except ImportError: pass diff --git a/pylibjpeg/tests/test_decode.py b/pylibjpeg/tests/test_decode.py index 58b6c03..089fbb2 100644 --- a/pylibjpeg/tests/test_decode.py +++ b/pylibjpeg/tests/test_decode.py @@ -22,16 +22,17 @@ @pytest.mark.skipif(HAS_DECODERS, reason="Decoders available") class TestNoDecoders(object): """Test interactions with no decoders.""" + def test_decode_str(self): """Test passing a str to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") assert isinstance(fpath, str) with pytest.raises(RuntimeError, match=r"No decoders are available"): decode(fpath) def test_decode_pathlike(self): """Test passing a pathlike to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") p = Path(fpath) assert isinstance(p, os.PathLike) with pytest.raises(RuntimeError, match=r"No decoders are available"): @@ -39,16 +40,16 @@ def test_decode_pathlike(self): def test_decode_filelike(self): """Test passing a filelike to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') - with open(fpath, 'rb') as f: + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") + with open(fpath, "rb") as f: msg = r"No decoders are available" with pytest.raises(RuntimeError, match=msg): decode(f) def test_decode_bytes(self): """Test passing bytes to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') - with open(fpath, 'rb') as f: + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") + with open(fpath, "rb") as f: data = f.read() assert isinstance(data, bytes) @@ -59,21 +60,22 @@ def test_decode_bytes(self): def test_unknown_decoder_type(self): """Test unknown decoder type.""" with pytest.raises(ValueError, match=r"Unknown decoder_type 'TEST'"): - get_decoders(decoder_type='TEST') + get_decoders(decoder_type="TEST") @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG decoders available") class TestJPEGDecoders(object): """Test decoding.""" + def test_decode_str(self): """Test passing a str to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") assert isinstance(fpath, str) arr = decode(fpath) def test_decode_pathlike(self): """Test passing a pathlike to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") p = Path(fpath) assert isinstance(p, os.PathLike) arr = decode(p) @@ -82,11 +84,11 @@ def test_decode_filelike(self): """Test passing a filelike to decode.""" bs = BytesIO() - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') - with open(fpath, 'rb') as f: + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") + with open(fpath, "rb") as f: arr = decode(f) - with open(fpath, 'rb') as f: + with open(fpath, "rb") as f: bs.write(f.read()) bs.seek(0) @@ -94,8 +96,8 @@ def test_decode_filelike(self): def test_decode_bytes(self): """Test passing bytes to decode.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') - with open(fpath, 'rb') as f: + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") + with open(fpath, "rb") as f: data = f.read() assert isinstance(data, bytes) @@ -104,38 +106,39 @@ def test_decode_bytes(self): def test_decode_failure(self): """Test failure to decode.""" with pytest.raises(ValueError, match=r"Unable to decode"): - decode(b'\x00\x00') + decode(b"\x00\x00") def test_specify_decoder(self): """Test specifying the decoder.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") assert isinstance(fpath, str) - arr = decode(fpath, decoder='libjpeg') + arr = decode(fpath, decoder="libjpeg") @pytest.mark.skipif("openjpeg" in get_decoders(), reason="Have openjpeg") def test_specify_unknown_decoder(self): """Test specifying an unknown decoder.""" - fpath = os.path.join(JPEG_DIRECTORY, '10918', 'p1', 'A1.JPG') + fpath = os.path.join(JPEG_DIRECTORY, "10918", "p1", "A1.JPG") assert isinstance(fpath, str) with pytest.raises(ValueError, match=r"The 'openjpeg' decoder"): - decode(fpath, decoder='openjpeg') + decode(fpath, decoder="openjpeg") @pytest.mark.skipif(not RUN_JPEGLS, reason="No JPEG-LS decoders available") class TestJPEGLSDecoders(object): """Test decoding JPEG-LS files.""" + def setup(self): - self.basedir = os.path.join(JPEG_DIRECTORY, '14495', 'JLS') + self.basedir = os.path.join(JPEG_DIRECTORY, "14495", "JLS") def test_decode_str(self): """Test passing a str to decode.""" - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') + fpath = os.path.join(self.basedir, "T8C0E0.JLS") assert isinstance(fpath, str) arr = decode(fpath) def test_decode_pathlike(self): """Test passing a pathlike to decode.""" - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') + fpath = os.path.join(self.basedir, "T8C0E0.JLS") p = Path(fpath) assert isinstance(p, os.PathLike) arr = decode(p) @@ -144,11 +147,11 @@ def test_decode_filelike(self): """Test passing a filelike to decode.""" bs = BytesIO() - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') - with open(fpath, 'rb') as f: + fpath = os.path.join(self.basedir, "T8C0E0.JLS") + with open(fpath, "rb") as f: arr = decode(f) - with open(fpath, 'rb') as f: + with open(fpath, "rb") as f: bs.write(f.read()) bs.seek(0) @@ -156,8 +159,8 @@ def test_decode_filelike(self): def test_decode_bytes(self): """Test passing bytes to decode.""" - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') - with open(fpath, 'rb') as f: + fpath = os.path.join(self.basedir, "T8C0E0.JLS") + with open(fpath, "rb") as f: data = f.read() assert isinstance(data, bytes) @@ -165,32 +168,33 @@ def test_decode_bytes(self): def test_specify_decoder(self): """Test specifying the decoder.""" - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') - arr = decode(fpath, decoder='libjpeg') + fpath = os.path.join(self.basedir, "T8C0E0.JLS") + arr = decode(fpath, decoder="libjpeg") @pytest.mark.skipif("openjpeg" in get_decoders(), reason="Have openjpeg") def test_specify_unknown_decoder(self): """Test specifying an unknown decoder.""" - fpath = os.path.join(self.basedir, 'T8C0E0.JLS') + fpath = os.path.join(self.basedir, "T8C0E0.JLS") with pytest.raises(ValueError, match=r"The 'openjpeg' decoder"): - decode(fpath, decoder='openjpeg') + decode(fpath, decoder="openjpeg") @pytest.mark.skipif(not RUN_JPEG2K, reason="No JPEG 2000 decoders available") class TestJPEG2KDecoders(object): """Test decoding JPEG 2000 files.""" + def setup(self): - self.basedir = os.path.join(JPEG_DIRECTORY, '15444', '2KLS') + self.basedir = os.path.join(JPEG_DIRECTORY, "15444", "2KLS") def test_decode_str(self): """Test passing a str to decode.""" - fpath = os.path.join(self.basedir, '693.j2k') + fpath = os.path.join(self.basedir, "693.j2k") assert isinstance(fpath, str) arr = decode(fpath) def test_decode_pathlike(self): """Test passing a pathlike to decode.""" - fpath = os.path.join(self.basedir, '693.j2k') + fpath = os.path.join(self.basedir, "693.j2k") p = Path(fpath) assert isinstance(p, os.PathLike) arr = decode(p) @@ -199,11 +203,11 @@ def test_decode_filelike(self): """Test passing a filelike to decode.""" bs = BytesIO() - fpath = os.path.join(self.basedir, '693.j2k') - with open(fpath, 'rb') as f: + fpath = os.path.join(self.basedir, "693.j2k") + with open(fpath, "rb") as f: arr = decode(f) - with open(fpath, 'rb') as f: + with open(fpath, "rb") as f: bs.write(f.read()) bs.seek(0) @@ -211,8 +215,8 @@ def test_decode_filelike(self): def test_decode_bytes(self): """Test passing bytes to decode.""" - fpath = os.path.join(self.basedir, '693.j2k') - with open(fpath, 'rb') as f: + fpath = os.path.join(self.basedir, "693.j2k") + with open(fpath, "rb") as f: data = f.read() assert isinstance(data, bytes) @@ -220,12 +224,12 @@ def test_decode_bytes(self): def test_specify_decoder(self): """Test specifying the decoder.""" - fpath = os.path.join(self.basedir, '693.j2k') - arr = decode(fpath, decoder='openjpeg') + fpath = os.path.join(self.basedir, "693.j2k") + arr = decode(fpath, decoder="openjpeg") @pytest.mark.skipif("libjpeg" in get_decoders(), reason="Have libjpeg") def test_specify_unknown_decoder(self): """Test specifying an unknown decoder.""" - fpath = os.path.join(self.basedir, '693.j2k') + fpath = os.path.join(self.basedir, "693.j2k") with pytest.raises(ValueError, match=r"The 'libjpeg' decoder"): - decode(fpath, decoder='libjpeg') + decode(fpath, decoder="libjpeg") diff --git a/pylibjpeg/tests/test_decode_pydicom.py b/pylibjpeg/tests/test_decode_pydicom.py index 8a1f7af..2206713 100644 --- a/pylibjpeg/tests/test_decode_pydicom.py +++ b/pylibjpeg/tests/test_decode_pydicom.py @@ -9,6 +9,7 @@ try: import pydicom import pydicom.config + HAS_PYDICOM = True except ImportError as exc: HAS_PYDICOM = False @@ -20,9 +21,9 @@ decoders = get_pixel_data_decoders() HAS_PLUGINS = bool(decoders) -HAS_JPEG_PLUGIN = '1.2.840.10008.1.2.4.50' in decoders -HAS_JPEG_LS_PLUGIN = '1.2.840.10008.1.2.4.80' in decoders -HAS_JPEG_2K_PLUGIN = '1.2.840.10008.1.2.4.90' in decoders +HAS_JPEG_PLUGIN = "1.2.840.10008.1.2.4.50" in decoders +HAS_JPEG_LS_PLUGIN = "1.2.840.10008.1.2.4.80" in decoders +HAS_JPEG_2K_PLUGIN = "1.2.840.10008.1.2.4.90" in decoders RUN_JPEG = HAS_JPEG_PLUGIN and HAS_PYDICOM RUN_JPEGLS = HAS_JPEG_LS_PLUGIN and HAS_PYDICOM @@ -34,10 +35,11 @@ @pytest.mark.skipif(not HAS_PYDICOM or HAS_PLUGINS, reason="Plugins available") class TestNoPlugins: """Test interactions with no plugins.""" + def test_pixel_array(self): # Should basically just not mess up the usual pydicom behaviour - index = get_indexed_datasets('1.2.840.10008.1.2.4.50') - ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] + index = get_indexed_datasets("1.2.840.10008.1.2.4.50") + ds = index["JPEGBaseline_1s_1f_u_08_08.dcm"]["ds"] msg = ( r"Unable to convert the Pixel Data as the 'pylibjpeg-libjpeg' " r"plugin is not installed" @@ -49,34 +51,35 @@ def test_pixel_array(self): @pytest.mark.skipif(not HAS_PYDICOM, reason="No pydicom") class TestPlugins: """Test interaction with plugins.""" + @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG plugin") def test_pixel_array(self): # Should basically just not mess up the usual pydicom behaviour - index = get_indexed_datasets('1.2.840.10008.1.2.4.50') - ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] + index = get_indexed_datasets("1.2.840.10008.1.2.4.50") + ds = index["JPEGBaseline_1s_1f_u_08_08.dcm"]["ds"] arr = ds.pixel_array assert arr.flags.writeable - assert 'uint8' == arr.dtype + assert "uint8" == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape # Reference values from GDCM handler - assert 76 == arr[ 5, 50] + assert 76 == arr[5, 50] assert 167 == arr[15, 50] assert 149 == arr[25, 50] assert 203 == arr[35, 50] - assert 29 == arr[45, 50] + assert 29 == arr[45, 50] assert 142 == arr[55, 50] - assert 1 == arr[65, 50] - assert 64 == arr[75, 50] + assert 1 == arr[65, 50] + assert 64 == arr[75, 50] assert 192 == arr[85, 50] assert 255 == arr[95, 50] @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG plugin") def test_missing_required(self): """Test missing required element raises.""" - index = get_indexed_datasets('1.2.840.10008.1.2.4.50') - ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] + index = get_indexed_datasets("1.2.840.10008.1.2.4.50") + ds = index["JPEGBaseline_1s_1f_u_08_08.dcm"]["ds"] del ds.SamplesPerPixel msg = r"'FileDataset' object has no attribute 'SamplesPerPixel'" @@ -86,36 +89,37 @@ def test_missing_required(self): @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG plugin") def test_ybr_full_422(self): """Test YBR_FULL_422 data decoded.""" - index = get_indexed_datasets('1.2.840.10008.1.2.4.50') - ds = index['SC_rgb_dcmtk_+eb+cy+np.dcm']['ds'] - assert 'YBR_FULL_422' == ds.PhotometricInterpretation + index = get_indexed_datasets("1.2.840.10008.1.2.4.50") + ds = index["SC_rgb_dcmtk_+eb+cy+np.dcm"]["ds"] + assert "YBR_FULL_422" == ds.PhotometricInterpretation arr = ds.pixel_array @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG plugin") class TestJPEGPlugin: """Test interaction with plugins that support JPEG.""" - uid = '1.2.840.10008.1.2.4.50' + + uid = "1.2.840.10008.1.2.4.50" def test_pixel_array(self): index = get_indexed_datasets(self.uid) - ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] + ds = index["JPEGBaseline_1s_1f_u_08_08.dcm"]["ds"] assert self.uid == ds.file_meta.TransferSyntaxUID arr = ds.pixel_array assert arr.flags.writeable - assert 'uint8' == arr.dtype + assert "uint8" == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape # Reference values from GDCM handler - assert 76 == arr[ 5, 50] + assert 76 == arr[5, 50] assert 167 == arr[15, 50] assert 149 == arr[25, 50] assert 203 == arr[35, 50] - assert 29 == arr[45, 50] + assert 29 == arr[45, 50] assert 142 == arr[55, 50] - assert 1 == arr[65, 50] - assert 64 == arr[75, 50] + assert 1 == arr[65, 50] + assert 64 == arr[75, 50] assert 192 == arr[85, 50] assert 255 == arr[95, 50] @@ -123,20 +127,21 @@ def test_pixel_array(self): @pytest.mark.skipif(not RUN_JPEGLS, reason="No JPEG-LS plugin") class TestJPEGLSPlugin: """Test interaction with plugins that support JPEG-LS.""" - uid = '1.2.840.10008.1.2.4.80' + + uid = "1.2.840.10008.1.2.4.80" def test_pixel_array(self): index = get_indexed_datasets(self.uid) - ds = index['MR_small_jpeg_ls_lossless.dcm']['ds'] + ds = index["MR_small_jpeg_ls_lossless.dcm"]["ds"] assert self.uid == ds.file_meta.TransferSyntaxUID arr = ds.pixel_array assert arr.flags.writeable - assert 'int16' == arr.dtype + assert "int16" == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape # Reference values from GDCM handler - assert [1194, 879, 127, 661, 1943, 1885, 1857, 1746, 1699] == ( + assert [1194, 879, 127, 661, 1943, 1885, 1857, 1746, 1699] == ( arr[55:65, 38].tolist() ) @@ -144,45 +149,47 @@ def test_pixel_array(self): @pytest.mark.skipif(not RUN_JPEG2K, reason="No JPEG 2000 plugin") class TestJPEG2KPlugin: """Test interaction with plugins that support JPEG 2000.""" - uid = '1.2.840.10008.1.2.4.90' + + uid = "1.2.840.10008.1.2.4.90" def test_pixel_array(self): index = get_indexed_datasets(self.uid) - ds = index['US1_J2KR.dcm']['ds'] + ds = index["US1_J2KR.dcm"]["ds"] arr = ds.pixel_array assert arr.flags.writeable - assert 'uint8' == arr.dtype + assert "uint8" == arr.dtype assert (ds.Rows, ds.Columns, ds.SamplesPerPixel) == arr.shape # Values checked against GDCM assert [ - [180, 26, 0], - [172, 15, 0], - [162, 9, 0], - [152, 4, 0], - [145, 0, 0], - [132, 0, 0], - [119, 0, 0], - [106, 0, 0], - [ 87, 0, 0], - [ 37, 0, 0], - [ 0, 0, 0], - [ 50, 0, 0], - [100, 0, 0], - [109, 0, 0], - [122, 0, 0], - [135, 0, 0], - [145, 0, 0], - [155, 5, 0], - [165, 11, 0], - [175, 17, 0]] == arr[175:195, 28, :].tolist() + [180, 26, 0], + [172, 15, 0], + [162, 9, 0], + [152, 4, 0], + [145, 0, 0], + [132, 0, 0], + [119, 0, 0], + [106, 0, 0], + [87, 0, 0], + [37, 0, 0], + [0, 0, 0], + [50, 0, 0], + [100, 0, 0], + [109, 0, 0], + [122, 0, 0], + [135, 0, 0], + [145, 0, 0], + [155, 5, 0], + [165, 11, 0], + [175, 17, 0], + ] == arr[175:195, 28, :].tolist() # FIXME def test_pixel_representation_mismatch(self): """Test mismatch between Pixel Representation and the J2K data.""" index = get_indexed_datasets(self.uid) - ds = index['J2K_pixelrep_mismatch.dcm']['ds'] + ds = index["J2K_pixelrep_mismatch.dcm"]["ds"] msg = ( r"value '1' \(signed\) in the dataset does not match the format " @@ -191,7 +198,7 @@ def test_pixel_representation_mismatch(self): with pytest.warns(UserWarning, match=msg): arr = ds.pixel_array assert arr.flags.writeable - assert 'int16' == arr.dtype + assert "int16" == arr.dtype assert (512, 512) == arr.shape assert -2000 == arr[0, 0] @@ -206,12 +213,13 @@ def test_pixel_representation_mismatch(self): # Deprecated class TestPydicomUtils: """Test the pydicom.utils functions.""" + @pytest.mark.skipif(not RUN_JPEG2K, reason="No JPEG 2000 plugin") def test_generate_frames_single_1s(self): """Test with single frame, 1 sample/px.""" - index = get_indexed_datasets('1.2.840.10008.1.2.4.90') - ds = index['693_J2KR.dcm']['ds'] - assert 1 == getattr(ds, 'NumberOfFrames', 1) + index = get_indexed_datasets("1.2.840.10008.1.2.4.90") + ds = index["693_J2KR.dcm"]["ds"] + assert 1 == getattr(ds, "NumberOfFrames", 1) assert 1 == ds.SamplesPerPixel frame_gen = generate_frames(ds) arr = next(frame_gen) @@ -219,33 +227,32 @@ def test_generate_frames_single_1s(self): next(frame_gen) assert arr.flags.writeable - assert 'int16' == arr.dtype + assert "int16" == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape - assert ( - [1022, 1051, 1165, 1442, 1835, 2096, 2074, 1868, 1685, 1603] == - arr[290, 135:145].tolist() - ) + assert [1022, 1051, 1165, 1442, 1835, 2096, 2074, 1868, 1685, 1603] == arr[ + 290, 135:145 + ].tolist() @pytest.mark.skipif(not RUN_JPEG2K, reason="No JPEG 2000 plugin") def test_generate_frames_1s(self): """Test with multiple frames, 1 sample/px.""" - index = get_indexed_datasets('1.2.840.10008.1.2.4.90') - ds = index['emri_small_jpeg_2k_lossless.dcm']['ds'] + index = get_indexed_datasets("1.2.840.10008.1.2.4.90") + ds = index["emri_small_jpeg_2k_lossless.dcm"]["ds"] assert ds.NumberOfFrames > 1 assert 1 == ds.SamplesPerPixel frames = generate_frames(ds) arr = next(frames) assert arr.flags.writeable - assert 'uint16' == arr.dtype + assert "uint16" == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape assert 163 == arr[12, 23] @pytest.mark.skipif(not RUN_JPEG, reason="No JPEG plugin") def test_generate_frames_3s_0p(self): """Test with multiple frames, 3 sample/px, 0 planar conf.""" - index = get_indexed_datasets('1.2.840.10008.1.2.4.50') - ds = index['color3d_jpeg_baseline.dcm']['ds'] + index = get_indexed_datasets("1.2.840.10008.1.2.4.50") + ds = index["color3d_jpeg_baseline.dcm"]["ds"] assert ds.NumberOfFrames > 1 assert 3 == ds.SamplesPerPixel assert 0 == ds.PlanarConfiguration @@ -253,40 +260,41 @@ def test_generate_frames_3s_0p(self): arr = next(frames) assert arr.flags.writeable - assert 'uint8' == arr.dtype + assert "uint8" == arr.dtype assert (ds.Rows, ds.Columns, 3) == arr.shape - assert [48, 128, 128] == arr[159, 290, :].tolist() + assert [48, 128, 128] == arr[159, 290, :].tolist() # Deprecated class TestGetJ2KParameters: """Tests for get_j2k_parameters.""" + def test_parameters(self): """Test getting the parameters for a JPEG2K codestream.""" - base = b'\xff\x4f\xff\x51' + b'\x00' * 38 + base = b"\xff\x4f\xff\x51" + b"\x00" * 38 # Signed for ii in range(135, 143): params = get_j2k_parameters(base + bytes([ii])) - assert ii - 127 == params['precision'] - assert params['is_signed'] + assert ii - 127 == params["precision"] + assert params["is_signed"] # Unsigned for ii in range(7, 17): params = get_j2k_parameters(base + bytes([ii])) - assert ii + 1 == params['precision'] - assert not params['is_signed'] + assert ii + 1 == params["precision"] + assert not params["is_signed"] def test_not_j2k(self): """Test result when no JPEG2K SOF marker present""" - base = b'\xff\x4e\xff\x51' + b'\x00' * 38 - assert {} == get_j2k_parameters(base + b'\x8F') + base = b"\xff\x4e\xff\x51" + b"\x00" * 38 + assert {} == get_j2k_parameters(base + b"\x8F") def test_no_siz(self): """Test result when no SIZ box present""" - base = b'\xff\x4f\xff\x52' + b'\x00' * 38 - assert {} == get_j2k_parameters(base + b'\x8F') + base = b"\xff\x4f\xff\x52" + b"\x00" * 38 + assert {} == get_j2k_parameters(base + b"\x8F") def test_short_bytestream(self): """Test result when no SIZ box present""" - assert {} == get_j2k_parameters(b'') - assert {} == get_j2k_parameters(b'\xff\x4f\xff\x51' + b'\x00' * 20) + assert {} == get_j2k_parameters(b"") + assert {} == get_j2k_parameters(b"\xff\x4f\xff\x51" + b"\x00" * 20) diff --git a/pylibjpeg/tools/jpegio.py b/pylibjpeg/tools/jpegio.py index ff30e62..9f4e5c5 100644 --- a/pylibjpeg/tools/jpegio.py +++ b/pylibjpeg/tools/jpegio.py @@ -1,23 +1,21 @@ - import logging from .s10918 import parse, JPEG -LOGGER = logging.getLogger('pylibjpeg.tools.jpegio') +LOGGER = logging.getLogger("pylibjpeg.tools.jpegio") PARSERS = { - '10918' : (parse, JPEG), + "10918": (parse, JPEG), } def get_specification(fp): - """ - """ - if fp.read(1) != b'\xff': - raise ValueError('File is not JPEG') + """ """ + if fp.read(1) != b"\xff": + raise ValueError("File is not JPEG") # Skip any initial fill bytes - while fp.read(1) == b'\xff': + while fp.read(1) == b"\xff": pass fp.seek(-2, 1) @@ -25,13 +23,12 @@ def get_specification(fp): # Confirm SOI marker marker = fp.read(2) - if marker == b'\xFF\xD8': + if marker == b"\xFF\xD8": fp.seek(0) - return '10918' + return "10918" raise NotImplementedError( - "Reading a JPEG file with first marker '0x{}' is not supported" - .format(marker) + "Reading a JPEG file with first marker '0x{}' is not supported".format(marker) ) @@ -39,7 +36,7 @@ def jpgread(fpath): """Return a represention of the JPEG file at `fpath`.""" LOGGER.debug("Reading file: {}".format(fpath)) if isinstance(fpath, str): - with open(fpath, 'rb') as fp: + with open(fpath, "rb") as fp: jpg_format = get_specification(fp) parser, jpg_class = PARSERS[jpg_format] meta = parser(fp) diff --git a/pylibjpeg/tools/s10918/_markers.py b/pylibjpeg/tools/s10918/_markers.py index 59c44a2..1ef9fd1 100644 --- a/pylibjpeg/tools/s10918/_markers.py +++ b/pylibjpeg/tools/s10918/_markers.py @@ -5,79 +5,81 @@ MARKERS = {} # JPEG reserved markers for _marker in range(0xFF02, 0xFFBF + 1): - MARKERS[_marker] = ('RES', 'Reserved', None) + MARKERS[_marker] = ("RES", "Reserved", None) -MARKERS.update({ - 0xFF01 : ('TEM', 'For temporary private use in artithmetic coding', None), - # Start of frame markers, non-differential, Huffman coding - 0xFFC0 : ('SOF0', 'Baseline DCT', SOF), - 0xFFC1 : ('SOF1', 'Extended sequential DCT', SOF), - 0xFFC2 : ('SOF2', 'Progressive DCT', SOF), - 0xFFC3 : ('SOF3', 'Lossless (sequential)', SOF), - # Huffman table specification - 0xFFC4 : ('DHT', 'Define Huffman table(s)', DHT), - # Start of frame markers, differential, Huffman coding - 0xFFC5 : ('SOF5', 'Differential sequential DCT', SOF), - 0xFFC6 : ('SOF6', 'Differential progressive DCT', SOF), - 0xFFC7 : ('SOF7', 'Differential lossless (sequential)', SOF), - # Start of frame markers, non-differential, arithmetic coding - 0xFFC8 : ('JPG', 'Reserved for JPEG extensions', None), - 0xFFC9 : ('SOF9', 'Extended sequential DCT', SOF), - 0xFFCA : ('SOF10', 'Progressive DCT', SOF), - 0xFFCB : ('SOF11', 'Lossless (sequential)', SOF), - # Define arithmetic coding conditioning(s) - 0xFFCC : ('DAC', 'Define arithmetic coding conditioning(s)', DAC), - # Start of frame markers, differential, arithmetic coding - 0xFFCD : ('SOF13', 'Differential sequential DCT', SOF), - 0xFFCE : ('SOF14', 'Differential progressive DCT', SOF), - 0xFFCF : ('SOF15', 'Differential lossless (sequential)', SOF), - # Restart interval termination - 0xFFD0 : ('RST0', 'Restart with modulo 8, count "0"', None), - 0xFFD1 : ('RST1', 'Restart with modulo 8, count "1"', None), - 0xFFD2 : ('RST2', 'Restart with modulo 8, count "2"', None), - 0xFFD3 : ('RST3', 'Restart with modulo 8, count "3"', None), - 0xFFD4 : ('RST4', 'Restart with modulo 8, count "4"', None), - 0xFFD5 : ('RST5', 'Restart with modulo 8, count "5"', None), - 0xFFD6 : ('RST6', 'Restart with modulo 8, count "6"', None), - 0xFFD7 : ('RST7', 'Restart with modulo 8, count "7"', None), - # Other markers - 0xFFD8 : ('SOI', 'Start of image', None), - 0xFFD9 : ('EOI', 'End of image', None), - 0xFFDA : ('SOS', 'Start of scan', SOS), - 0xFFDB : ('DQT', 'Define quantization table(s)', DQT), - 0xFFDC : ('DNL', 'Define number of lines', DNL), - 0xFFDD : ('DRI', 'Define restart interval', DRI), - 0xFFDE : ('DHP', 'Define hierarchical progression', SOF), # Identical - 0xFFDF : ('EXP', 'Expand reference component(s)', EXP), - 0xFFE0 : ('APP0', 'Reserved for application segments', APP), - 0xFFE1 : ('APP1', 'Reserved for application segments', APP), - 0xFFE2 : ('APP2', 'Reserved for application segments', APP), - 0xFFE3 : ('APP3', 'Reserved for application segments', APP), - 0xFFE4 : ('APP4', 'Reserved for application segments', APP), - 0xFFE5 : ('APP5', 'Reserved for application segments', APP), - 0xFFE6 : ('APP6', 'Reserved for application segments', APP), - 0xFFE7 : ('APP7', 'Reserved for application segments', APP), - 0xFFE8 : ('APP8', 'Reserved for application segments', APP), - 0xFFE9 : ('APP9', 'Reserved for application segments', APP), - 0xFFEA : ('APP10', 'Reserved for application segments', APP), - 0xFFEB : ('APP11', 'Reserved for application segments', APP), - 0xFFEC : ('APP12', 'Reserved for application segments', APP), - 0xFFED : ('APP13', 'Reserved for application segments', APP), - 0xFFEE : ('APP14', 'Reserved for application segments', APP), - 0xFFEF : ('APP15', 'Reserved for application segments', APP), - 0xFFF0 : ('JPG0', 'Reserved for JPEG extensions', None), - 0xFFF1 : ('JPG1', 'Reserved for JPEG extensions', None), - 0xFFF2 : ('JPG2', 'Reserved for JPEG extensions', None), - 0xFFF3 : ('JPG3', 'Reserved for JPEG extensions', None), - 0xFFF4 : ('JPG4', 'Reserved for JPEG extensions', None), - 0xFFF5 : ('JPG5', 'Reserved for JPEG extensions', None), - 0xFFF6 : ('JPG6', 'Reserved for JPEG extensions', None), - 0xFFF7 : ('JPG7', 'Reserved for JPEG extensions', None), - 0xFFF8 : ('JPG8', 'Reserved for JPEG extensions', None), - 0xFFF9 : ('JPG9', 'Reserved for JPEG extensions', None), - 0xFFFA : ('JPG10', 'Reserved for JPEG extensions', None), - 0xFFFB : ('JPG11', 'Reserved for JPEG extensions', None), - 0xFFFC : ('JPG12', 'Reserved for JPEG extensions', None), - 0xFFFD : ('JPG13', 'Reserved for JPEG extensions', None), - 0xFFFE : ('COM', 'Comment', COM), -}) +MARKERS.update( + { + 0xFF01: ("TEM", "For temporary private use in artithmetic coding", None), + # Start of frame markers, non-differential, Huffman coding + 0xFFC0: ("SOF0", "Baseline DCT", SOF), + 0xFFC1: ("SOF1", "Extended sequential DCT", SOF), + 0xFFC2: ("SOF2", "Progressive DCT", SOF), + 0xFFC3: ("SOF3", "Lossless (sequential)", SOF), + # Huffman table specification + 0xFFC4: ("DHT", "Define Huffman table(s)", DHT), + # Start of frame markers, differential, Huffman coding + 0xFFC5: ("SOF5", "Differential sequential DCT", SOF), + 0xFFC6: ("SOF6", "Differential progressive DCT", SOF), + 0xFFC7: ("SOF7", "Differential lossless (sequential)", SOF), + # Start of frame markers, non-differential, arithmetic coding + 0xFFC8: ("JPG", "Reserved for JPEG extensions", None), + 0xFFC9: ("SOF9", "Extended sequential DCT", SOF), + 0xFFCA: ("SOF10", "Progressive DCT", SOF), + 0xFFCB: ("SOF11", "Lossless (sequential)", SOF), + # Define arithmetic coding conditioning(s) + 0xFFCC: ("DAC", "Define arithmetic coding conditioning(s)", DAC), + # Start of frame markers, differential, arithmetic coding + 0xFFCD: ("SOF13", "Differential sequential DCT", SOF), + 0xFFCE: ("SOF14", "Differential progressive DCT", SOF), + 0xFFCF: ("SOF15", "Differential lossless (sequential)", SOF), + # Restart interval termination + 0xFFD0: ("RST0", 'Restart with modulo 8, count "0"', None), + 0xFFD1: ("RST1", 'Restart with modulo 8, count "1"', None), + 0xFFD2: ("RST2", 'Restart with modulo 8, count "2"', None), + 0xFFD3: ("RST3", 'Restart with modulo 8, count "3"', None), + 0xFFD4: ("RST4", 'Restart with modulo 8, count "4"', None), + 0xFFD5: ("RST5", 'Restart with modulo 8, count "5"', None), + 0xFFD6: ("RST6", 'Restart with modulo 8, count "6"', None), + 0xFFD7: ("RST7", 'Restart with modulo 8, count "7"', None), + # Other markers + 0xFFD8: ("SOI", "Start of image", None), + 0xFFD9: ("EOI", "End of image", None), + 0xFFDA: ("SOS", "Start of scan", SOS), + 0xFFDB: ("DQT", "Define quantization table(s)", DQT), + 0xFFDC: ("DNL", "Define number of lines", DNL), + 0xFFDD: ("DRI", "Define restart interval", DRI), + 0xFFDE: ("DHP", "Define hierarchical progression", SOF), # Identical + 0xFFDF: ("EXP", "Expand reference component(s)", EXP), + 0xFFE0: ("APP0", "Reserved for application segments", APP), + 0xFFE1: ("APP1", "Reserved for application segments", APP), + 0xFFE2: ("APP2", "Reserved for application segments", APP), + 0xFFE3: ("APP3", "Reserved for application segments", APP), + 0xFFE4: ("APP4", "Reserved for application segments", APP), + 0xFFE5: ("APP5", "Reserved for application segments", APP), + 0xFFE6: ("APP6", "Reserved for application segments", APP), + 0xFFE7: ("APP7", "Reserved for application segments", APP), + 0xFFE8: ("APP8", "Reserved for application segments", APP), + 0xFFE9: ("APP9", "Reserved for application segments", APP), + 0xFFEA: ("APP10", "Reserved for application segments", APP), + 0xFFEB: ("APP11", "Reserved for application segments", APP), + 0xFFEC: ("APP12", "Reserved for application segments", APP), + 0xFFED: ("APP13", "Reserved for application segments", APP), + 0xFFEE: ("APP14", "Reserved for application segments", APP), + 0xFFEF: ("APP15", "Reserved for application segments", APP), + 0xFFF0: ("JPG0", "Reserved for JPEG extensions", None), + 0xFFF1: ("JPG1", "Reserved for JPEG extensions", None), + 0xFFF2: ("JPG2", "Reserved for JPEG extensions", None), + 0xFFF3: ("JPG3", "Reserved for JPEG extensions", None), + 0xFFF4: ("JPG4", "Reserved for JPEG extensions", None), + 0xFFF5: ("JPG5", "Reserved for JPEG extensions", None), + 0xFFF6: ("JPG6", "Reserved for JPEG extensions", None), + 0xFFF7: ("JPG7", "Reserved for JPEG extensions", None), + 0xFFF8: ("JPG8", "Reserved for JPEG extensions", None), + 0xFFF9: ("JPG9", "Reserved for JPEG extensions", None), + 0xFFFA: ("JPG10", "Reserved for JPEG extensions", None), + 0xFFFB: ("JPG11", "Reserved for JPEG extensions", None), + 0xFFFC: ("JPG12", "Reserved for JPEG extensions", None), + 0xFFFD: ("JPG13", "Reserved for JPEG extensions", None), + 0xFFFE: ("COM", "Comment", COM), + } +) diff --git a/pylibjpeg/tools/s10918/_parsers.py b/pylibjpeg/tools/s10918/_parsers.py index e39173c..81734f1 100644 --- a/pylibjpeg/tools/s10918/_parsers.py +++ b/pylibjpeg/tools/s10918/_parsers.py @@ -77,9 +77,9 @@ def APP(fp): * ``Lp`` : application data segment length * ``Ap`` : application data """ - length = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] - return {'Lp' : length, 'Ap' : fp.read(length - 2)} + return {"Lp": length, "Ap": fp.read(length - 2)} def COM(fp): @@ -104,10 +104,10 @@ def COM(fp): * ``Lc`` : comment data segment length * ``Cm`` : comment bytes """ - length = unpack('>H', fp.read(2))[0] - comment = unpack('{}s'.format(length - 2), fp.read(length - 2))[0] + length = unpack(">H", fp.read(2))[0] + comment = unpack("{}s".format(length - 2), fp.read(length - 2))[0] - return {'Lc' : length, 'Cm' : comment} + return {"Lc": length, "Cm": comment} def DAC(fp): @@ -134,19 +134,19 @@ def DAC(fp): * ``Tb`` : arithmetic coding conditioning table destination identifier * ``Cs`` : conditioning table value """ - length = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] bytes_to_read = length - 2 tc, tb, cs = [], [], [] while bytes_to_read > 0: _tc, _tb = split_byte(fp.read(1)) - cs.append(unpack('>B', fp.read(1))[0]) + cs.append(unpack(">B", fp.read(1))[0]) tc.append(_tc) tb.append(_tb) bytes_to_read -= 2 - return {'La' : length, 'Tc' : tc, 'Tb' : tb, 'Cs' : cs} + return {"La": length, "Tc": tc, "Tb": tb, "Cs": cs} def DHT(fp): @@ -177,7 +177,7 @@ def DHT(fp): * ``Vij`` : value associated with each Huffman code of length *i*, equivalent to *HUFFVAL* """ - length = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] bytes_to_read = length - 2 tc, th, li = [], [], [] @@ -190,7 +190,7 @@ def DHT(fp): bytes_to_read -= 1 # li (BITS) is the number of codes for each code length, from 1 to 16 - _li = unpack('>16B', fp.read(16)) + _li = unpack(">16B", fp.read(16)) bytes_to_read -= 16 # vij is a list of the 8-bit symbols values (HUFFVAL), each of which @@ -199,13 +199,13 @@ def DHT(fp): for ii in range(16): nr = _li[ii] if nr: - _vij[ii + 1] = unpack('>{}B'.format(nr), fp.read(nr)) + _vij[ii + 1] = unpack(">{}B".format(nr), fp.read(nr)) bytes_to_read -= nr li.append(_li) vij[(_tc, _th)] = _vij - return {'Lh' : length, 'Tc' : tc, 'Th' : th, 'Li' : li, 'Vij' : vij} + return {"Lh": length, "Tc": tc, "Th": th, "Li": li, "Vij": vij} def DNL(fp): @@ -230,10 +230,10 @@ def DNL(fp): * ``Ld`` : DNL segment length * ``NL`` : number of lines in the frame """ - length = unpack('>H', fp.read(2))[0] - nr_lines = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] + nr_lines = unpack(">H", fp.read(2))[0] - return {'Ld' : length, 'NL' : nr_lines} + return {"Ld": length, "NL": nr_lines} def DQT(fp): @@ -261,7 +261,7 @@ def DQT(fp): * ``Qk`` : quantization table element """ # length is 2 + sum(t=1, N) of (65 + 64 * Pq(t)) - length = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] bytes_to_read = length - 2 pq, tq, qk = [], [], [] @@ -280,15 +280,15 @@ def DQT(fp): Q_k = [] for ii in range(64): if precision == 0: - Q_k.append(unpack('>B', fp.read(1))[0]) + Q_k.append(unpack(">B", fp.read(1))[0]) bytes_to_read -= 1 elif precision == 1: - Q_k.append(unpack('>H', fp.read(2))[0]) + Q_k.append(unpack(">H", fp.read(2))[0]) bytes_to_read -= 2 qk.append(Q_k) - return {'Lq' : length, 'Pq' : pq, 'Tq' : tq, 'Qk' : qk} + return {"Lq": length, "Pq": pq, "Tq": tq, "Qk": qk} def DRI(fp): @@ -313,10 +313,7 @@ def DRI(fp): * ``Lr`` : DRI segment length * ``Ri`` : restart interval (number of MCU in the restart interval) """ - return { - 'Lr' : unpack('>H', fp.read(2))[0], - 'Ri' : unpack('>H', fp.read(2))[0] - } + return {"Lr": unpack(">H", fp.read(2))[0], "Ri": unpack(">H", fp.read(2))[0]} def EXP(fp): @@ -342,10 +339,10 @@ def EXP(fp): * ``Eh`` : expand horizontally * ``Ev`` : expand vertically """ - length = unpack('>H', fp.read(2))[0] - eh, ev = split_byte(unpack('>B', fp.read(1))[0]) + length = unpack(">H", fp.read(2))[0] + eh, ev = split_byte(unpack(">B", fp.read(1))[0]) - return {'Le' : length, 'Eh' : eh, 'Ev' : ev} + return {"Le": length, "Eh": eh, "Ev": ev} def SOF(fp): @@ -377,37 +374,35 @@ def SOF(fp): * ``Vi`` : vertical sampling factor * ``Tqi`` : quantization table destination selector """ - (length, - precision, - nr_lines, - samples_per_line, - nr_components) = unpack('>HBHHB', fp.read(8)) + (length, precision, nr_lines, samples_per_line, nr_components) = unpack( + ">HBHHB", fp.read(8) + ) component_id = {} - #horizontal_sampling_factor = [] - #vertical_sampling_factor = [] - #quantisation_selector = [] + # horizontal_sampling_factor = [] + # vertical_sampling_factor = [] + # quantisation_selector = [] for ii in range(nr_components): - _ci = unpack('>B', fp.read(1))[0] + _ci = unpack(">B", fp.read(1))[0] _hor, _vert = split_byte(fp.read(1)) - #horizontal_sampling_factor.append(_hor) - #vertical_sampling_factor.append(_vert) - _tqi = unpack('>B', fp.read(1))[0] - #quantisation_selector.append(_tqi) + # horizontal_sampling_factor.append(_hor) + # vertical_sampling_factor.append(_vert) + _tqi = unpack(">B", fp.read(1))[0] + # quantisation_selector.append(_tqi) component_id[_ci] = { - 'Hi' : _hor, - 'Vi' : _vert, - 'Tqi' : _tqi, + "Hi": _hor, + "Vi": _vert, + "Tqi": _tqi, } return { - 'Lf' : length, - 'P' : precision, - 'Y' : nr_lines, - 'X' : samples_per_line, - 'Nf' : nr_components, - 'Ci' : component_id, + "Lf": length, + "P": precision, + "Y": nr_lines, + "X": samples_per_line, + "Nf": nr_components, + "Ci": component_id, } @@ -446,30 +441,29 @@ def SOS(fp): Shall be set to 0 for sequential DCT. In lossless mode specifies the point transform Pt. """ - (length, nr_components) = unpack('>HB', fp.read(3)) + (length, nr_components) = unpack(">HB", fp.read(3)) csj, tdj, taj, tmj = [], [], [], [] for ii in range(nr_components): - _cs = unpack('>B', fp.read(1))[0] + _cs = unpack(">B", fp.read(1))[0] csj.append(_cs) _td, _ta = split_byte(fp.read(1)) tdj.append(_td) taj.append(_ta) - - (ss, se) = unpack('>BB', fp.read(2)) + (ss, se) = unpack(">BB", fp.read(2)) ah, al = split_byte(fp.read(1)) return { - 'Ls' : length, - 'Ns' : nr_components, - 'Csj' : csj, - 'Tdj' : tdj, - 'Taj' : taj, - 'Ss' : ss, - 'Se' : se, - 'Ah' : ah, - 'Al' : al, + "Ls": length, + "Ns": nr_components, + "Csj": csj, + "Tdj": tdj, + "Taj": taj, + "Ss": ss, + "Se": se, + "Ah": ah, + "Al": al, } @@ -487,5 +481,5 @@ def skip(fp): A file-like positioned at the start of the length byte for the current marker segment. """ - length = unpack('>H', fp.read(2))[0] + length = unpack(">H", fp.read(2))[0] fp.seek(length - 2, 1) diff --git a/pylibjpeg/tools/s10918/_printers.py b/pylibjpeg/tools/s10918/_printers.py index 16e510e..1072313 100644 --- a/pylibjpeg/tools/s10918/_printers.py +++ b/pylibjpeg/tools/s10918/_printers.py @@ -21,19 +21,80 @@ ZIGZAG = [ - 0, 1, 5, 6, 14, 15, 27, 28, - 2, 4, 7, 13, 16, 26, 29, 42, - 3, 8, 12, 17, 25, 30, 41, 43, - 9, 11, 18, 24, 31, 40, 44, 53, - 10, 19, 23, 32, 39, 45, 52, 54, - 20, 22, 33, 38, 46, 51, 55, 60, - 21, 34, 37, 47, 50, 56, 59, 61, - 35, 36, 48, 49, 57, 58, 62, 63 + 0, + 1, + 5, + 6, + 14, + 15, + 27, + 28, + 2, + 4, + 7, + 13, + 16, + 26, + 29, + 42, + 3, + 8, + 12, + 17, + 25, + 30, + 41, + 43, + 9, + 11, + 18, + 24, + 31, + 40, + 44, + 53, + 10, + 19, + 23, + 32, + 39, + 45, + 52, + 54, + 20, + 22, + 33, + 38, + 46, + 51, + 55, + 60, + 21, + 34, + 37, + 47, + 50, + 56, + 59, + 61, + 35, + 36, + 48, + 49, + 57, + 58, + 62, + 63, ] # ASCII codes for CMYK, RGB _COMMON_COMPONENT_IDS = { - 66 : 'B', 67 : 'C', 71 : 'G', 75 : 'K', 77 : 'M', 82 : 'R', - 89 : 'Y', + 66: "B", + 67: "C", + 71: "G", + 75: "K", + 77: "M", + 82: "R", + 89: "Y", } @@ -43,61 +104,63 @@ def _print_app(marker, offset, info): ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lp'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lp"] + 2) + ) ) - app_data = info['Ap'] - if app_data[:5] == b'\x4a\x46\x49\x46\x00': + app_data = info["Ap"] + if app_data[:5] == b"\x4a\x46\x49\x46\x00": # JFIF https://en.wikipendia.org/wiki/JPEG_File_Interchange_Format version = (app_data[5], app_data[6]) # density units, 0: no units, 1: px/inch: 2 px/cm - units = unpack('B', app_data[7:8])[0] + units = unpack("B", app_data[7:8])[0] if units == 0: - units = 'unitless' + units = "unitless" elif units == 1: - units = 'px/inch' + units = "px/inch" elif units == 2: - units = 'px/cm' + units = "px/cm" # horizontal/vertical px density - x = unpack('>H', app_data[8:10])[0] - y = unpack('>H', app_data[10:12])[0] + x = unpack(">H", app_data[8:10])[0] + y = unpack(">H", app_data[10:12])[0] # Thumbnail horizontal/vertical pixel count - width, height = unpack('BB', app_data[12:14]) + width, height = unpack("BB", app_data[12:14]) # Thumbnail data thumbnail = app_data[14:] if width != 0 and height != 0: ss.append( - 'JFIF v{}.{}, {}, ({}, {}), {} by {} px' - .format(*version, units, x, y, width, height) + "JFIF v{}.{}, {}, ({}, {}), {} by {} px".format( + *version, units, x, y, width, height + ) ) else: - ss.append('JFIF v{}.{}, no thumbnail'.format(*version)) + ss.append("JFIF v{}.{}, no thumbnail".format(*version)) if thumbnail: - data = ' '.join(['{:02x}'.format(cc) for cc in thumbnail]) + data = " ".join(["{:02x}".format(cc) for cc in thumbnail]) for ii in range(0, len(data), 60): - ss.append(' {}'.format(data[ii:ii + 60])) + ss.append(" {}".format(data[ii : ii + 60])) - elif app_data[:6] == b'\x45\x78\x69\x66\x00\x00': - ss.append('EXIF:') - data = ' '.join(['{:02x}'.format(cc) for cc in app_data[6:]]) + elif app_data[:6] == b"\x45\x78\x69\x66\x00\x00": + ss.append("EXIF:") + data = " ".join(["{:02x}".format(cc) for cc in app_data[6:]]) for ii in range(0, len(data), 60): - ss.append(' {}'.format(data[ii:ii + 60])) - elif app_data[:6] == b'\x41\x64\x6f\x62\x65\x00': + ss.append(" {}".format(data[ii : ii + 60])) + elif app_data[:6] == b"\x41\x64\x6f\x62\x65\x00": # Adobe - ss.append('Adobe v{}:'.format(app_data[6])) - data = ' '.join(['{:02x}'.format(cc) for cc in app_data[6:]]) + ss.append("Adobe v{}:".format(app_data[6])) + data = " ".join(["{:02x}".format(cc) for cc in app_data[6:]]) for ii in range(0, len(data), 60): - ss.append(' {}'.format(data[ii:ii + 60])) + ss.append(" {}".format(data[ii : ii + 60])) else: # Unknown - ss.append('Unknown APP data') - data = ['{:02x}'.format(cc) for cc in app_data] + ss.append("Unknown APP data") + data = ["{:02x}".format(cc) for cc in app_data] for ii in range(0, len(data), 20): - ss.append(' {}'.format(' '.join(data[ii:ii + 20]))) + ss.append(" {}".format(" ".join(data[ii : ii + 20]))) - return '\n'.join(ss) + return "\n".join(ss) def _print_com(marker, offset, info): @@ -105,11 +168,12 @@ def _print_com(marker, offset, info): _m, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lc'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lc"] + 2) + ) ) - comment = "'" + info['Cm'].decode('utf-8') + "'" + comment = "'" + info["Cm"].decode("utf-8") + "'" ss.append("{}".format(comment[:47])) comment = comment[47:] @@ -121,7 +185,7 @@ def _print_com(marker, offset, info): ss.append(" {}".format(line)) - return '\n'.join(ss) + return "\n".join(ss) def _print_dac(marker, offset, info): @@ -129,16 +193,13 @@ def _print_dac(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format( - ' {} marker at offset {}, length {} ' - .format(marker, offset, info['La'] + 2) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["La"] + 2) ) ) - ss.append( - "Tc={}, Tb={}, Cs={}".format(info['Tc'], info['Tb'], info['Cs']) - ) + ss.append("Tc={}, Tb={}, Cs={}".format(info["Tc"], info["Tb"], info["Cs"])) - return '\n'.join(ss) + return "\n".join(ss) def _print_dhp(marker, offset, info): @@ -146,26 +207,27 @@ def _print_dhp(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lf'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lf"] + 2) + ) ) - ss.append('Sample size (px): {} x {}'.format(info['X'], info['Y'])) - ss.append('Sample precision (bits): {}'.format(info['P'])) - ss.append('Number of component images: {}'.format(info['Nf'])) + ss.append("Sample size (px): {} x {}".format(info["X"], info["Y"])) + ss.append("Sample precision (bits): {}".format(info["P"])) + ss.append("Number of component images: {}".format(info["Nf"])) - for ci, vv in info['Ci'].items(): - h, v, tqi = vv['Hi'], vv['Vi'], vv['Tqi'] + for ci, vv in info["Ci"].items(): + h, v, tqi = vv["Hi"], vv["Vi"], vv["Tqi"] try: ci = _COMMON_COMPONENT_IDS[ci] except KeyError: pass - ss.append(' Component ID: {}'.format(ci)) - ss.append(' Horizontal sampling factor: {}'.format(h)) - ss.append(' Vertical sampling factor: {}'.format(v)) - ss.append(' Quantization table destination: {}'.format(tqi)) + ss.append(" Component ID: {}".format(ci)) + ss.append(" Horizontal sampling factor: {}".format(h)) + ss.append(" Vertical sampling factor: {}".format(v)) + ss.append(" Quantization table destination: {}".format(tqi)) - return '\n'.join(ss) + return "\n".join(ss) def _print_dht(marker, offset, info): @@ -173,31 +235,32 @@ def _print_dht(marker, offset, info): _m, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lh'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lh"] + 2) + ) ) - for tc, th, li in zip(info['Tc'], info['Th'], info['Li']): - vij = info['Vij'][(tc, th)] + for tc, th, li in zip(info["Tc"], info["Th"], info["Li"]): + vij = info["Vij"][(tc, th)] if tc == 0: - ss.append('Lossless/DC Huffman, table ID: {}'.format(th)) + ss.append("Lossless/DC Huffman, table ID: {}".format(th)) elif tc == 1: - ss.append('AC Huffman, table ID: {}'.format(th)) + ss.append("AC Huffman, table ID: {}".format(th)) else: raise NotImplementedError - ss.append(' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16') - nr_values = ' '.join(['{:>02x}'.format(val) for val in li]) - ss.append(' {} : # codes'.format(nr_values)) + ss.append(" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16") + nr_values = " ".join(["{:>02x}".format(val) for val in li]) + ss.append(" {} : # codes".format(nr_values)) for ii, (kk, values) in enumerate(vij.items()): if values is not None: for jj in range(0, len(values), 16): - vals = ['{:>02x}'.format(vv) for vv in values[jj:jj + 16]] - val = ' '.join(vals) - ss.append(' {:<47} : L = {}'.format(val, kk)) + vals = ["{:>02x}".format(vv) for vv in values[jj : jj + 16]] + val = " ".join(vals) + ss.append(" {:<47} : L = {}".format(val, kk)) - return '\n'.join(ss) + return "\n".join(ss) def _print_dqt(marker, offset, info): @@ -205,32 +268,33 @@ def _print_dqt(marker, offset, info): _m, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lq'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lq"] + 2) + ) ) - for pq, tq, qk in zip(info['Pq'], info['Tq'], info['Qk']): - ss.append('Table destination ID: {}'.format(tq)) + for pq, tq, qk in zip(info["Pq"], info["Tq"], info["Qk"]): + ss.append("Table destination ID: {}".format(tq)) if pq == 0: - ss.append('Table precision: {} (8-bit)'.format(pq)) + ss.append("Table precision: {} (8-bit)".format(pq)) else: - ss.append('Table precision: {} (16-bit)'.format(pq)) + ss.append("Table precision: {} (16-bit)".format(pq)) new_qk = [] for index in ZIGZAG: new_qk.append(qk[index]) - ss.append('Quantization table:') + ss.append("Quantization table:") for ii in range(0, 64, 8): if not pq: # 8-bit - table_rows = ['{:>2}'.format(qq) for qq in new_qk[ii:ii + 8]] + table_rows = ["{:>2}".format(qq) for qq in new_qk[ii : ii + 8]] else: # 16-bit - table_rows = ['{:>3}'.format(qq) for qq in new_qk[ii:ii + 8]] + table_rows = ["{:>3}".format(qq) for qq in new_qk[ii : ii + 8]] - ss.append(' {}'.format(' '.join(table_rows))) + ss.append(" {}".format(" ".join(table_rows))) - return '\n'.join(ss) + return "\n".join(ss) def _print_dnl(marker, offset, info): @@ -238,14 +302,13 @@ def _print_dnl(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format( - ' {} marker at offset {}, length {} ' - .format(marker, offset, info['Ld'] + 2) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Ld"] + 2) ) ) - ss.append("NL={}".format(info['NL'])) + ss.append("NL={}".format(info["NL"])) - return '\n'.join(ss) + return "\n".join(ss) def _print_dri(marker, offset, info): @@ -253,20 +316,17 @@ def _print_dri(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lr'] + 2)) - ) - ss.append( - "Ri={}".format(info['Ri']) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lr"] + 2) + ) ) - return '\n'.join(ss) + ss.append("Ri={}".format(info["Ri"])) + return "\n".join(ss) def _print_eoi(marker, offset, info): """String output for an EOI segment.""" - return ( - '\n{:=^63}'.format(' {} marker at offset {} '.format(marker, offset)) - ) + return "\n{:=^63}".format(" {} marker at offset {} ".format(marker, offset)) def _print_exp(marker, offset, info): @@ -274,14 +334,13 @@ def _print_exp(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format( - ' {} marker at offset {}, length {} ' - .format(marker, offset, info['Le'] + 2) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Le"] + 2) ) ) - ss.append("Eh={}, Ev={}".format(info['Eh'], info['Ev'])) + ss.append("Eh={}, Ev={}".format(info["Eh"], info["Ev"])) - return '\n'.join(ss) + return "\n".join(ss) def _print_sof(marker, offset, info): @@ -289,52 +348,53 @@ def _print_sof(marker, offset, info): m_bytes, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Lf'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Lf"] + 2) + ) ) sof_type = { - 0xFFC0: 'Baseline sequential DCT', # SOF0 - 0xFFC1: 'Extended sequential DCT, Huffman coding', # SOF1 - 0xFFC2: 'Progressive DCT, Huffman coding', # SOF2 - 0xFFC3: 'Lossless (sequential), Huffman coding', # SOF3 - 0xFFC5: 'Differential sequential DCT, Huffman coding', # SOF5 - 0xFFC6: 'Differential progressive DCT, Huffman coding', # SOF6 - 0xFFC7: 'Differential lossless (sequential), Huffman coding', # SOF7 - 0xFFC9: 'Extended sequential DCT, arithmetic coding', # SOF9 - 0xFFCA: 'Progressive DCT, arithmetic coding', # SOF10 - 0xFFCB: 'Lossless (sequential), arithmetic coding', # SOF11 - 0xFFCD: 'Differential sequential DCT, arithmetic coding', # SOF13 - 0xFFCE: 'Differential progressive DCT, arithmetic coding', # SOF14 - 0xFFCF: 'Differential lossless (sequential), arithmetic coding', # SOF15 + 0xFFC0: "Baseline sequential DCT", # SOF0 + 0xFFC1: "Extended sequential DCT, Huffman coding", # SOF1 + 0xFFC2: "Progressive DCT, Huffman coding", # SOF2 + 0xFFC3: "Lossless (sequential), Huffman coding", # SOF3 + 0xFFC5: "Differential sequential DCT, Huffman coding", # SOF5 + 0xFFC6: "Differential progressive DCT, Huffman coding", # SOF6 + 0xFFC7: "Differential lossless (sequential), Huffman coding", # SOF7 + 0xFFC9: "Extended sequential DCT, arithmetic coding", # SOF9 + 0xFFCA: "Progressive DCT, arithmetic coding", # SOF10 + 0xFFCB: "Lossless (sequential), arithmetic coding", # SOF11 + 0xFFCD: "Differential sequential DCT, arithmetic coding", # SOF13 + 0xFFCE: "Differential progressive DCT, arithmetic coding", # SOF14 + 0xFFCF: "Differential lossless (sequential), arithmetic coding", # SOF15 } try: - ss.append('{}'.format(sof_type[m_bytes])) + ss.append("{}".format(sof_type[m_bytes])) except KeyError: - ss.append('Unknown SOF type: {}'.format(hex(m_bytes))) + ss.append("Unknown SOF type: {}".format(hex(m_bytes))) - ss.append('Sample size (px): {} x {}'.format(info['X'], info['Y'])) - ss.append('Sample precision (bits): {}'.format(info['P'])) - ss.append('Number of component images: {}'.format(info['Nf'])) + ss.append("Sample size (px): {} x {}".format(info["X"], info["Y"])) + ss.append("Sample precision (bits): {}".format(info["P"])) + ss.append("Number of component images: {}".format(info["Nf"])) - for ci, vv in info['Ci'].items(): - h, v, tqi = vv['Hi'], vv['Vi'], vv['Tqi'] + for ci, vv in info["Ci"].items(): + h, v, tqi = vv["Hi"], vv["Vi"], vv["Tqi"] try: ci = _COMMON_COMPONENT_IDS[ci] except KeyError: pass - ss.append(' Component ID: {}'.format(ci)) - ss.append(' Horizontal sampling factor: {}'.format(h)) - ss.append(' Vertical sampling factor: {}'.format(v)) - ss.append(' Quantization table destination: {}'.format(tqi)) + ss.append(" Component ID: {}".format(ci)) + ss.append(" Horizontal sampling factor: {}".format(h)) + ss.append(" Vertical sampling factor: {}".format(v)) + ss.append(" Quantization table destination: {}".format(tqi)) - return '\n'.join(ss) + return "\n".join(ss) def _print_soi(marker, offset, info): """String output for a SOI segment.""" - return '\n{:=^63}'.format(' {} marker at offset {} '.format(marker, offset)) + return "\n{:=^63}".format(" {} marker at offset {} ".format(marker, offset)) def _print_sos(marker, offset, info): @@ -342,60 +402,50 @@ def _print_sos(marker, offset, info): _m, fill, info = info ss = [] ss.append( - '\n{:-^63}'.format(' {} marker at offset {}, length {} ' - .format(marker, offset, info['Ls'] + 2)) + "\n{:-^63}".format( + " {} marker at offset {}, length {} ".format(marker, offset, info["Ls"] + 2) + ) ) - ss.append("Number of image components: {}".format(info['Ns'])) + ss.append("Number of image components: {}".format(info["Ns"])) - for csk, td, ta in zip(info['Csj'], info['Tdj'], info['Taj']): + for csk, td, ta in zip(info["Csj"], info["Tdj"], info["Taj"]): try: csk = _COMMON_COMPONENT_IDS[csk] except KeyError: pass - ss.append( - ' Component: {}, DC table: {}, AC table: {}'.format(csk, td, ta) - ) + ss.append(" Component: {}, DC table: {}, AC table: {}".format(csk, td, ta)) + ss.append("Spectral selectors start-end: {}-{}".format(info["Ss"], info["Se"])) ss.append( - 'Spectral selectors start-end: {}-{}'.format(info['Ss'], info['Se']) - ) - ss.append( - 'Successive approximation bit high-low: {}-{}' - .format(info['Ah'], info['Al']) + "Successive approximation bit high-low: {}-{}".format(info["Ah"], info["Al"]) ) # Write RST and encoded data lengths - remove = ['Ls', 'Ns', 'Csj', 'Tdj', 'Taj', 'Ss', 'Se', 'Ah', 'Al'] + remove = ["Ls", "Ns", "Csj", "Tdj", "Taj", "Ss", "Se", "Ah", "Al"] keys = [kk for kk in info if kk not in remove] for key in keys: - if key[0] == 'ENC': - ss.append( - '\n{:.^63}'.format(' ENC marker at offset {}'.format(key[1])) - ) - ss.append( - '\n{} bytes of entropy-coded data'.format(len(info[key])) - ) + if key[0] == "ENC": + ss.append("\n{:.^63}".format(" ENC marker at offset {}".format(key[1]))) + ss.append("\n{} bytes of entropy-coded data".format(len(info[key]))) else: (name, offset) = key - ss.append( - '{:<7}{}({})'.format(offset, name, 'ffd{}'.format(name[-1])) - ) + ss.append("{:<7}{}({})".format(offset, name, "ffd{}".format(name[-1]))) - return '\n'.join(ss) + return "\n".join(ss) PRINTERS = { - 'APP' : _print_app, - 'COM' : _print_com, - 'DAC' : _print_dac, - 'DHP' : _print_dhp, - 'DHT' : _print_dht, - 'DQT' : _print_dqt, - 'DNL' : _print_dnl, - 'DRI' : _print_dri, - 'EOI' : _print_eoi, - 'EXP' : _print_exp, - 'SOF' : _print_sof, - 'SOI' : _print_soi, - 'SOS' : _print_sos, + "APP": _print_app, + "COM": _print_com, + "DAC": _print_dac, + "DHP": _print_dhp, + "DHT": _print_dht, + "DQT": _print_dqt, + "DNL": _print_dnl, + "DRI": _print_dri, + "EOI": _print_eoi, + "EXP": _print_exp, + "SOF": _print_sof, + "SOI": _print_soi, + "SOS": _print_sos, } diff --git a/pylibjpeg/tools/s10918/io.py b/pylibjpeg/tools/s10918/io.py index 5c50ee1..f09825f 100644 --- a/pylibjpeg/tools/s10918/io.py +++ b/pylibjpeg/tools/s10918/io.py @@ -7,27 +7,23 @@ from ._markers import MARKERS -LOGGER = logging.getLogger('jpg') +LOGGER = logging.getLogger("jpg") def parse(fp): """Return a JPEG but don't decode yet.""" _fill_bytes = 0 - while fp.read(1) == b'\xff': + while fp.read(1) == b"\xff": _fill_bytes += 1 pass fp.seek(-2, 1) # Confirm SOI marker - if fp.read(2) != b'\xFF\xD8': - raise ValueError('SOI marker not found') + if fp.read(2) != b"\xFF\xD8": + raise ValueError("SOI marker not found") - info = { - ('SOI', fp.tell() - 2) : ( - unpack('>H', b'\xFF\xD8')[0], _fill_bytes, {} - ) - } + info = {("SOI", fp.tell() - 2): (unpack(">H", b"\xFF\xD8")[0], _fill_bytes, {})} END_OF_FILE = False @@ -36,7 +32,7 @@ def parse(fp): # Skip fill next_byte = fp.read(1) - while next_byte == b'\xFF': + while next_byte == b"\xFF": _fill_bytes += 1 next_byte = fp.read(1) @@ -46,20 +42,20 @@ def parse(fp): fp.seek(-2, 1) - _marker = unpack('>H', fp.read(2))[0] + _marker = unpack(">H", fp.read(2))[0] if _marker in MARKERS: name, description, handler = MARKERS[_marker] key = (name, fp.tell() - 2) - if name not in ['SOS', 'EOI']: + if name not in ["SOS", "EOI"]: if handler is None: - length = unpack('>H', fp.read(2))[0] - 2 + length = unpack(">H", fp.read(2))[0] - 2 fp.seek(length, 1) continue - info[key] = (_marker, _fill_bytes , handler(fp)) + info[key] = (_marker, _fill_bytes, handler(fp)) - elif name is 'SOS': + elif name is "SOS": # SOS's info dict contains an extra 'encoded_data' keys # which use RSTN@offset and ENC@offset info[key] = [_marker, _fill_bytes, handler(fp)] @@ -69,12 +65,12 @@ def parse(fp): _enc_start = fp.tell() while True: - _enc_key = ('ENC', _enc_start - 2) + _enc_key = ("ENC", _enc_start - 2) prev_byte = fp.read(1) - if prev_byte == b'': + if prev_byte == b"": END_OF_FILE = True break - elif prev_byte != b'\xFF': + elif prev_byte != b"\xFF": encoded_data.extend(prev_byte) continue @@ -86,33 +82,41 @@ def parse(fp): # Otherwise rewind to the start of the fill bytes and break next_byte = fp.read(1) - if next_byte == b'\x00': + if next_byte == b"\x00": # Skip padding bytes # The previous byte wasn't added so do it now encoded_data.extend(prev_byte) - #encoded_data.extend(next_byte) + # encoded_data.extend(next_byte) continue # To get here next_byte must be non-padding (non 0x00) # so we must be at the end of the encoded data - info[key][2].update({_enc_key : encoded_data}) + info[key][2].update({_enc_key: encoded_data}) encoded_data = bytearray() # The number of 0xFF bytes before the marker # i.e. 0xFF 0xFF 0xFF 0xD9 is 2 fill bytes _sos_fill_bytes = 0 # While we still have 0xFF bytes - while next_byte == b'\xFF': + while next_byte == b"\xFF": _sos_fill_bytes += 1 next_byte = fp.read(1) # Check to see if marker is RST_m - if next_byte in [b'\xD0', b'\xD1', b'\xD2', b'\xD3', - b'\xD4', b'\xD5', b'\xD6', b'\xD7']: - _sos_marker = unpack('>H', b'\xFF' + next_byte)[0] + if next_byte in [ + b"\xD0", + b"\xD1", + b"\xD2", + b"\xD3", + b"\xD4", + b"\xD5", + b"\xD6", + b"\xD7", + ]: + _sos_marker = unpack(">H", b"\xFF" + next_byte)[0] _sos_marker, _, _ = MARKERS[_sos_marker] _sos_key = (_sos_marker, fp.tell() - 2) - info[key][2].update({_sos_key : None}) + info[key][2].update({_sos_key: None}) _enc_start = fp.tell() continue @@ -123,15 +127,14 @@ def parse(fp): fp.seek(-2 - _sos_fill_bytes, 1) break - elif name is 'EOI': + elif name is "EOI": info[key] = (_marker, _fill_bytes, {}) break else: if not END_OF_FILE: print( - 'Unknown marker {} at offset {}' - .format(hex(_marker), fp.tell() - 2) + "Unknown marker {} at offset {}".format(hex(_marker), fp.tell() - 2) ) raise NotImplementedError else: diff --git a/pylibjpeg/tools/s10918/rep.py b/pylibjpeg/tools/s10918/rep.py index 502de88..1ac43c9 100644 --- a/pylibjpeg/tools/s10918/rep.py +++ b/pylibjpeg/tools/s10918/rep.py @@ -1,5 +1,3 @@ - - from ._printers import PRINTERS @@ -52,6 +50,7 @@ class JPEG(object): 29: Lossless, arithmetic, 2 to 16-bit """ + def __init__(self, meta): """Initialise a new JPEG. @@ -65,9 +64,9 @@ def __init__(self, meta): @property def columns(self): """Return the number of columns in the image as an int.""" - keys = self.get_keys('SOF') + keys = self.get_keys("SOF") if keys: - return self.info[keys[0]][2]['X'] + return self.info[keys[0]][2]["X"] raise ValueError( "Unable to get the number of columns in the image as no SOFn " @@ -91,13 +90,13 @@ def _decode(self): if self.is_process1: decoder = decode_baseline - #elif self.is_process2: + # elif self.is_process2: # decoder = decode_extended_8 - #elif self.is_process4: + # elif self.is_process4: # decoder = decode_extended_12 - #elif self.is_process14: + # elif self.is_process14: # decoder = decode_lossless - #elif self.is_process14_sv1: + # elif self.is_process14_sv1: # decoder = decode_lossless try: @@ -129,7 +128,7 @@ def is_baseline(self): Hierarchical baseline processes are: 16, 17, 20, 21, 24, 25. """ - return 'SOF0' in self.markers + return "SOF0" in self.markers @property def is_extended(self): @@ -148,7 +147,7 @@ def is_extended(self): Hierarchical extended processes are: 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 """ - extended_markers = ('SOF1', 'SOF9', 'SOF5', 'SOF13') + extended_markers = ("SOF1", "SOF9", "SOF5", "SOF13") if [mm for mm in extended_markers if mm in self.markers]: return True @@ -164,7 +163,7 @@ def is_hierarchical(self): * Decoders shall process scans with 1, 2, 3 and 4 components * Interleaved and non-interleaved scans """ - return 'DHP' in self.markers + return "DHP" in self.markers @property def is_lossless(self): @@ -183,7 +182,7 @@ def is_lossless(self): Hierarchical lossless processes are: 28, 29 """ - lossless_markers = ('SOF3', 'SOF11') #, 'SOF7', 'SOF15') + lossless_markers = ("SOF3", "SOF11") # , 'SOF7', 'SOF15') if [mm for mm in lossless_markers if mm in self.markers]: return True @@ -230,7 +229,7 @@ def is_process4(self): @property def is_process14(self): """Return True if the JPEG is Process 14, False otherwise.""" - if 'SOF3' not in self.markers: + if "SOF3" not in self.markers: return False if not self.is_hierarchical and self.is_lossless: @@ -249,7 +248,7 @@ def is_process14_sv1(self): True if JPEG is process 14, first-order prediction, selection value 1, False otherwise. """ - if 'SOF3' not in self.markers: + if "SOF3" not in self.markers: return False if self.is_hierarchical or not self.is_lossless: @@ -281,9 +280,9 @@ def markers(self): @property def precision(self): """Return the precision of the sample as an int.""" - keys = self.get_keys('SOF') + keys = self.get_keys("SOF") if keys: - return self.info[keys[0]][2]['P'] + return self.info[keys[0]][2]["P"] raise ValueError( "Unable to get the sample precision of the image as no SOFn " @@ -348,9 +347,9 @@ def process(self): @property def rows(self): """Return the number of rows in the image as an int.""" - keys = self.get_keys('SOF') + keys = self.get_keys("SOF") if keys: - return self.info[keys[0]][2]['Y'] + return self.info[keys[0]][2]["Y"] raise ValueError( "Unable to get the number of rows in the image as no SOFn " @@ -360,9 +359,9 @@ def rows(self): @property def samples(self): """Return the number of components in the JPEG as an int.""" - keys = self.get_keys('SOF') + keys = self.get_keys("SOF") if keys: - return self.info[keys[0]][2]['Nf'] + return self.info[keys[0]][2]["Nf"] raise ValueError( "Unable to get the number of components in the image as no SOFn " @@ -387,12 +386,10 @@ def selection_value(self): If the JPEG is not lossless. """ if not self.is_lossless: - raise ValueError( - "Selection value is only available for lossless JPEG" - ) + raise ValueError("Selection value is only available for lossless JPEG") - sos_markers = [mm for mm in self._keys if 'SOS' in mm] - return self.info[sos_markers[0]][2]['Ss'] + sos_markers = [mm for mm in self._keys if "SOS" in mm] + return self.info[sos_markers[0]][2]["Ss"] def __str__(self): """""" @@ -402,7 +399,7 @@ def __str__(self): printer = PRINTERS[marker[:3]] ss.append(printer(marker, offset, info)) - return '\n'.join(ss) + return "\n".join(ss) @property def uid(self): diff --git a/pylibjpeg/tools/tests/test_utils.py b/pylibjpeg/tools/tests/test_utils.py index 925375d..f28d414 100644 --- a/pylibjpeg/tools/tests/test_utils.py +++ b/pylibjpeg/tools/tests/test_utils.py @@ -7,26 +7,33 @@ class TestGetBit(object): """Tests for utils.get_bit(byte, index).""" + def test_out_of_range_raises(self): """Test than an invalid `index` value raises an exception.""" msg = r"'index' must be between 0 and 7, inclusive" with pytest.raises(ValueError, match=msg): - get_bit(b'\x00', -1) + get_bit(b"\x00", -1) with pytest.raises(ValueError, match=msg): - get_bit(b'\x00', 8) + get_bit(b"\x00", 8) def test_empty_byte_raises(self): """Test that an empty `byte` value raises an exception.""" msg = r"ord\(\) expected a character" with pytest.raises(TypeError, match=msg): - get_bit(b'', 0) + get_bit(b"", 0) def test_index_correct(self): """Test that the `index` returns the correct bit = 1.""" ref_bytes = [ - b'\x01', b'\x02', b'\x04', b'\x08', - b'\x10', b'\x20', b'\x40', b'\x80' + b"\x01", + b"\x02", + b"\x04", + b"\x08", + b"\x10", + b"\x20", + b"\x40", + b"\x80", ] for ii in range(0, 8, -1): assert 1 == get_bit(ref_bytes[ii], ii) @@ -34,8 +41,14 @@ def test_index_correct(self): def test_index_correct_inverse(self): """Test that the `index` returns the correct bit = 0.""" ref_bytes = [ - b'\xfe', b'\xfd', b'\xfb', b'\xf7', - b'\xef', b'\xdf', b'\xbf', b'\x7f' + b"\xfe", + b"\xfd", + b"\xfb", + b"\xf7", + b"\xef", + b"\xdf", + b"\xbf", + b"\x7f", ] for ii in range(0, 8, -1): assert 0 == get_bit(ref_bytes[ii], ii) @@ -43,8 +56,14 @@ def test_index_correct_inverse(self): def test_multiple_bytes(self): """Test that only the bit value from the first byte is returned""" ref_bytes = [ - b'\x01\xff', b'\x02\xff', b'\x04\xff', b'\x08\xff', - b'\x10\xff', b'\x20\xff', b'\x40\xff', b'\x80\xff' + b"\x01\xff", + b"\x02\xff", + b"\x04\xff", + b"\x08\xff", + b"\x10\xff", + b"\x20\xff", + b"\x40\xff", + b"\x80\xff", ] for ii in range(0, 8, -1): assert 1 == get_bit(ref_bytes[ii], ii) @@ -52,8 +71,14 @@ def test_multiple_bytes(self): def test_multiple_bytes_inverse(self): """Test that only the bit value from the first byte is returned""" ref_bytes = [ - b'\xfe\x00', b'\xfd\x00', b'\xfb\x00', b'\xf7\x00', - b'\xef\x00', b'\xdf\x00', b'\xbf\x00', b'\x7f\x00' + b"\xfe\x00", + b"\xfd\x00", + b"\xfb\x00", + b"\xf7\x00", + b"\xef\x00", + b"\xdf\x00", + b"\xbf\x00", + b"\x7f\x00", ] for ii in range(0, 8, -1): assert 0 == get_bit(ref_bytes[ii], ii) @@ -61,22 +86,23 @@ def test_multiple_bytes_inverse(self): class TestSplitByte(object): """Tests for utils.split_byte(byte).""" + def test_empty_byte_raises(self): """Test that an empty `byte` value raises an exception.""" msg = r"ord\(\) expected a character" with pytest.raises(TypeError, match=msg): - split_byte(b'') + split_byte(b"") def test_splitting(self): """Test splitting a byte.""" ref = { - b'\x00' : ( 0, 0), - b'\x0f' : ( 0, 15), - b'\xf0' : (15, 0), - b'\x55' : ( 5, 5), - b'\xaa' : (10, 10), - b'\x18' : ( 1, 8), - b'\x81' : ( 8, 1), + b"\x00": (0, 0), + b"\x0f": (0, 15), + b"\xf0": (15, 0), + b"\x55": (5, 5), + b"\xaa": (10, 10), + b"\x18": (1, 8), + b"\x81": (8, 1), } for byte, out in ref.items(): assert out == split_byte(byte) diff --git a/pylibjpeg/utils.py b/pylibjpeg/utils.py index c2cf3fd..fb425dd 100644 --- a/pylibjpeg/utils.py +++ b/pylibjpeg/utils.py @@ -1,4 +1,3 @@ - import logging import os from pkg_resources import iter_entry_points @@ -41,7 +40,7 @@ def decode(data, decoder=None, kwargs=None): raise RuntimeError("No decoders are available") if isinstance(data, (str, os.PathLike)): - with open(str(data), 'rb') as f: + with open(str(data), "rb") as f: data = f.read() elif isinstance(data, bytes): pass @@ -92,20 +91,20 @@ def get_decoders(decoder_type=None): A dict of ``{'package_name': }``. """ entry_points = { - "JPEG" : "pylibjpeg.jpeg_decoders", - "JPEG XT" : "pylibjpeg.jpeg_xt_decoders", - "JPEG-LS" : "pylibjpeg.jpeg_ls_decoders", - "JPEG 2000" : "pylibjpeg.jpeg_2000_decoders", - "JPEG XR" : "pylibjpeg.jpeg_xr_decoders", - "JPEG XS" : "pylibjpeg.jpeg_xs_decoders", - "JPEG XL" : "pylibjpeg.jpeg_xl_decoders", + "JPEG": "pylibjpeg.jpeg_decoders", + "JPEG XT": "pylibjpeg.jpeg_xt_decoders", + "JPEG-LS": "pylibjpeg.jpeg_ls_decoders", + "JPEG 2000": "pylibjpeg.jpeg_2000_decoders", + "JPEG XR": "pylibjpeg.jpeg_xr_decoders", + "JPEG XS": "pylibjpeg.jpeg_xs_decoders", + "JPEG XL": "pylibjpeg.jpeg_xl_decoders", } if decoder_type is None: decoders = {} for entry_point in entry_points.values(): - decoders.update({ - val.name: val.load() for val in iter_entry_points(entry_point) - }) + decoders.update( + {val.name: val.load() for val in iter_entry_points(entry_point)} + ) return decoders try: @@ -121,7 +120,7 @@ def get_pixel_data_decoders(): """Return a :class:`dict` of ``{UID: callable}``.""" return { val.name: val.load() - for val in iter_entry_points('pylibjpeg.pixel_data_decoders') + for val in iter_entry_points("pylibjpeg.pixel_data_decoders") } @@ -200,20 +199,20 @@ def get_encoders(encoder_type=None): A dict of ``{'package_name': }``. """ entry_points = { - "JPEG" : "pylibjpeg.jpeg_encoders", - "JPEG XT" : "pylibjpeg.jpeg_xt_encoders", - "JPEG-LS" : "pylibjpeg.jpeg_ls_encoders", - "JPEG 2000" : "pylibjpeg.jpeg_2000_encoders", - "JPEG XR" : "pylibjpeg.jpeg_xr_encoders", - "JPEG XS" : "pylibjpeg.jpeg_xs_encoders", - "JPEG XL" : "pylibjpeg.jpeg_xl_encoders", + "JPEG": "pylibjpeg.jpeg_encoders", + "JPEG XT": "pylibjpeg.jpeg_xt_encoders", + "JPEG-LS": "pylibjpeg.jpeg_ls_encoders", + "JPEG 2000": "pylibjpeg.jpeg_2000_encoders", + "JPEG XR": "pylibjpeg.jpeg_xr_encoders", + "JPEG XS": "pylibjpeg.jpeg_xs_encoders", + "JPEG XL": "pylibjpeg.jpeg_xl_encoders", } if encoder_type is None: encoders = {} for entry_point in entry_points.values(): - encoders.update({ - val.name: val.load() for val in iter_entry_points(entry_point) - }) + encoders.update( + {val.name: val.load() for val in iter_entry_points(entry_point)} + ) return encoders try: @@ -232,5 +231,5 @@ def get_pixel_data_encoders(): """ return { val.name: val.load() - for val in iter_entry_points('pylibjpeg.pixel_data_encoders') + for val in iter_entry_points("pylibjpeg.pixel_data_encoders") } diff --git a/requirements.txt b/requirements.txt index 6a96010..8b13789 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pylibjpeg-openjpeg + diff --git a/setup.py b/setup.py index 41798d3..7725891 100644 --- a/setup.py +++ b/setup.py @@ -4,56 +4,56 @@ import sys -PACKAGE_DIR = Path(__file__).parent / 'pylibjpeg' -VERSION_FILE = PACKAGE_DIR / '_version.py' -with open(VERSION_FILE) as fp: - exec(fp.read()) +PACKAGE_DIR = Path(__file__).parent / "pylibjpeg" -with open('README.md', 'r') as fp: - long_description = fp.read() + +with open(PACKAGE_DIR / "_version.py") as f: + exec(f.read()) + +with open("README.md", "r") as f: + long_description = f.read() setup( - name = 'pylibjpeg', - description = ( + name="pylibjpeg", + description=( "A Python framework for decoding JPEG and decoding/encoding DICOM " "RLE data, with a focus on supporting pydicom" ), - long_description = long_description, - long_description_content_type = 'text/markdown', - version = __version__, - author = "scaramallion", - author_email = "scaramallion@users.noreply.github.com", - url = "https://github.com/pydicom/pylibjpeg", - license = "MIT", - keywords = ( - "dcm dicom pydicom python medicalimaging radiology radiotherapy " - "oncology imaging jpg jpeg jpg-ls jpeg-ls jpeg2k jpeg2000 rle " - "libjpeg pylibjpeg " + long_description=long_description, + long_description_content_type="text/markdown", + version=__version__, + author="scaramallion", + author_email="scaramallion@users.noreply.github.com", + url="https://github.com/pydicom/pylibjpeg", + license="MIT", + keywords=( + "dcm dicom pydicom python imaging jpg jpeg jpg-ls jpeg-ls jpeg2k " + "jpeg2000 rle libjpeg pylibjpeg " ), - classifiers = [ + classifiers=[ "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", "Intended Audience :: Healthcare Industry", "Intended Audience :: Science/Research", - #"Development Status :: 3 - Alpha", - #"Development Status :: 4 - Beta", "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", ], - packages = find_packages(), - extras_require = { - 'rle': ['pylibjpeg-rle'], - 'openjpeg': ['pylibjpeg-openjpeg'] + packages=find_packages(), + extras_require={ + "rle": ["pylibjpeg-rle"], + "openjpeg": ["pylibjpeg-openjpeg"], + "libjpeg": ["pylibjpeg-libjpeg"], + "all": ["pylibjpeg-libjpeg", "pylibjpeg-openjpeg", "pylibjpeg-rle"], }, - install_requires = ['numpy'], - include_package_data = True, - zip_safe = False, - python_requires = ">=3.6", + install_requires=["numpy"], + include_package_data=True, + zip_safe=False, + python_requires=">=3.7", )