# Copyright 2008-2021 pydicom authors. See LICENSE file for details. """Tests for the 'pydicom' encoder plugin.""" from struct import pack, unpack import sys import pytest try: import numpy as np HAVE_NP = True except ImportError: HAVE_NP = False from pydicom import dcmread, Dataset from pydicom.data import get_testdata_file from pydicom.dataset import FileMetaDataset from pydicom.encaps import defragment_data from pydicom.encoders import RLELosslessEncoder from pydicom.encoders.native import ( _encode_frame, _encode_segment, _encode_row ) from pydicom.pixel_data_handlers.rle_handler import ( _rle_decode_frame, _rle_decode_segment ) from pydicom.pixel_data_handlers.rle_handler import rle_encode_frame from pydicom.pixel_data_handlers.util import reshape_pixel_array from pydicom.uid import RLELossless # EXPL: Explicit VR Little Endian # RLE: RLE Lossless # 8/8-bit, 1 sample/pixel, 1 frame EXPL_8_1_1F = get_testdata_file("OBXXXX1A.dcm") RLE_8_1_1F = get_testdata_file("OBXXXX1A_rle.dcm") # 8/8-bit, 3 sample/pixel, 1 frame EXPL_8_3_1F = get_testdata_file("SC_rgb.dcm") # 8/8-bit, 3 sample/pixel, 2 frame EXPL_8_3_2F = get_testdata_file("SC_rgb_2frame.dcm") # 16/16-bit, 1 sample/pixel, 1 frame EXPL_16_1_1F = get_testdata_file("MR_small.dcm") # 16/16-bit, 3 sample/pixel, 1 frame EXPL_16_3_1F = get_testdata_file("SC_rgb_16bit.dcm") # 32/32-bit, 1 sample/pixel, 1 frame EXPL_32_1_1F = get_testdata_file("rtdose_1frame.dcm") # 32/32-bit, 3 sample/pixel, 1 frame EXPL_32_3_1F = get_testdata_file("SC_rgb_32bit.dcm") # Tests for RLE encoding REFERENCE_ENCODE_ROW = [ # Input, output ([], b''), # Replicate run tests # 2 (min) replicate ([0] * 2, b'\xff\x00'), ([0] * 3, b'\xfe\x00'), ([0] * 64, b'\xc1\x00'), ([0] * 127, b'\x82\x00'), # 128 (max) replicate ([0] * 128, b'\x81\x00'), # 128 (max) replicate, 1 (min) literal ([0] * 129, b'\x81\x00\x00\x00'), # 128 (max) replicate, 2 (min) replicate ([0] * 130, b'\x81\x00\xff\x00'), # 128 (max) x 5 replicates ([0] * 128 * 5, b'\x81\x00' * 5), # Literal run tests # 1 (min) literal ([0], b'\x00\x00'), ([0, 1], b'\x01\x00\x01'), ([0, 1, 2], b'\x02\x00\x01\x02'), ([0, 1] * 32, b'\x3f' + b'\x00\x01' * 32), # 127 literal ([0, 1] * 63 + [2], b'\x7e' + b'\x00\x01' * 63 + b'\x02'), # 128 literal (max) ([0, 1] * 64, b'\x7f' + b'\x00\x01' * 64), # 128 (max) literal, 1 (min) literal ([0, 1] * 64 + [2], b'\x7f' + b'\x00\x01' * 64 + b'\x00\x02'), # 128 (max) x 5 literals ([0, 1] * 64 * 5, (b'\x7f' + b'\x00\x01' * 64) * 5), # Combination run tests # 1 (min) literal, 1 (min) replicate ([0, 1, 1], b'\x00\x00\xff\x01'), # 1 (min) literal, 128 (max) replicate ([0] + [1] * 128, b'\x00\x00\x81\x01'), # 128 (max) literal, 2 (min) replicate ([0, 1] * 64 + [2] * 2, b'\x7f' + b'\x00\x01' * 64 + b'\xff\x02'), # 128 (max) literal, 128 (max) replicate ([0, 1] * 64 + [2] * 128, b'\x7f' + b'\x00\x01' * 64 + b'\x81\x02'), # 2 (min) replicate, 1 (min) literal ([0, 0, 1], b'\xff\x00\x00\x01'), # 2 (min) replicate, 128 (max) literal ([0, 0] + [1, 2] * 64, b'\xff\x00\x7f' + b'\x01\x02' * 64), # 128 (max) replicate, 1 (min) literal ([0] * 128 + [1], b'\x81\x00\x00\x01'), # 128 (max) replicate, 128 (max) literal ([0] * 128 + [1, 2] * 64, b'\x81\x00\x7f' + b'\x01\x02' * 64), ] class TestEncodeRow: """Tests for rle_handler._encode_row.""" @pytest.mark.parametrize('src, output', REFERENCE_ENCODE_ROW) def test_encode(self, src, output): """Test encoding an empty row.""" assert output == _encode_row(src) @pytest.mark.skipif(not HAVE_NP, reason="Numpy not available") class TestEncodeFrame: """Tests for rle_handler._encode_frame.""" def setup(self): """Setup the tests.""" # Create a dataset skeleton for use in the cycle tests ds = Dataset() ds.file_meta = FileMetaDataset() ds.file_meta.TransferSyntaxUID = '1.2.840.10008.1.2' ds.Rows = 2 ds.Columns = 4 ds.SamplesPerPixel = 3 ds.PlanarConfiguration = 1 self.ds = ds def test_cycle_8bit_1sample(self): """Test an encode/decode cycle for 8-bit 1 sample/pixel.""" ds = dcmread(EXPL_8_1_1F) ref = ds.pixel_array assert 8 == ds.BitsAllocated assert 1 == ds.SamplesPerPixel kwargs = RLELosslessEncoder.kwargs_from_ds(ds) encoded = _encode_frame(ds.PixelData, **kwargs) decoded = _rle_decode_frame( encoded, ds.Rows, ds.Columns, ds.SamplesPerPixel, ds.BitsAllocated ) arr = np.frombuffer(decoded, '|u1') arr = reshape_pixel_array(ds, arr) assert np.array_equal(ref, arr) def test_cycle_8bit_3sample(self): """Test an encode/decode cycle for 8-bit 3 sample/pixel.""" ds = dcmread(EXPL_8_3_1F) ref = ds.pixel_array assert ds.BitsAllocated == 8 assert ds.SamplesPerPixel == 3 assert ds.PixelRepresentation == 0 kwargs = RLELosslessEncoder.kwargs_from_ds(ds) encoded = _encode_frame(ds.PixelData, **kwargs) decoded = _rle_decode_frame( encoded, ds.Rows, ds.Columns, ds.SamplesPerPixel, ds.BitsAllocated ) # The decoded data is planar configuration 1 ds.PlanarConfiguration = 1 arr = np.frombuffer(decoded, '|u1') arr = reshape_pixel_array(ds, arr) assert np.array_equal(ref, arr) def test_cycle_16bit_1sample(self): """Test an encode/decode cycle for 16-bit 1 sample/pixel.""" ds = dcmread(EXPL_16_1_1F) ref = ds.pixel_array assert 16 == ds.BitsAllocated assert 1 == ds.SamplesPerPixel kwargs = RLELosslessEncoder.kwargs_from_ds(ds) encoded = _encode_frame(ds.PixelData, **kwargs) decoded = _rle_decode_frame( encoded, ds.Rows, ds.Columns, ds.SamplesPerPixel, ds.BitsAllocated ) arr = np.frombuffer(decoded, ' 1 kwargs = RLELosslessEncoder.kwargs_from_ds(ds) msg = ( r"Unable to encode multiple frames at once, please encode one " r"frame at a time" ) with pytest.raises(ValueError, match=msg): rle_encode_frame(ds.pixel_array) def test_functional(self): """Test function works OK.""" ds = dcmread(EXPL_16_3_1F) ref = ds.pixel_array assert ds.BitsAllocated == 16 assert ds.SamplesPerPixel == 3 assert ds.PixelRepresentation == 0 encoded = rle_encode_frame(ref) decoded = _rle_decode_frame( encoded, ds.Rows, ds.Columns, ds.SamplesPerPixel, ds.BitsAllocated ) ds.PlanarConfiguration = 1 arr = np.frombuffer(decoded, '') assert id(arr) != id(ref) assert arr.dtype == '>u2' encoded = rle_encode_frame(arr) decoded = _rle_decode_frame( encoded, ds.Rows, ds.Columns, ds.SamplesPerPixel, ds.BitsAllocated ) ds.PlanarConfiguration = 1 arr = np.frombuffer(decoded, '