def test_invalid_colourspace_warns(): """Test that using an unknown colourspace gives a warning.""" index = get_indexed_datasets('1.2.840.10008.1.2.4.50') ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] nr_frames = ds.get('NumberOfFrames', 1) frame = next(generate_pixel_data_frame(ds.PixelData, nr_frames)) msg = r"no colour transformation will be applied" ds.PhotometricInterpretation = 'ANY' with pytest.warns(UserWarning, match=msg): arr = decode_pixel_data(np.frombuffer(frame, 'uint8'), ds) arr = reshape_pixel_array(ds, arr) assert arr.flags.writeable assert 'uint8' == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape # Reference values from GDCM handler 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 142 == arr[55, 50] assert 1 == arr[65, 50] assert 64 == arr[75, 50] assert 192 == arr[85, 50] assert 255 == arr[95, 50]
def test_multi_frame_varied_ratio(self): """Test a multi-frame image where each frames is random fragments""" # 3 frames, 1st is 1 fragment, 2nd is 3 fragments, 3rd is 2 fragments bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x0E\x00\x00\x00' \ b'\x32\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x06\x00\x00\x00\x01\x00\x00\x00\x00\x01' \ b'\xFE\xFF\x00\xE0' \ b'\x02\x00\x00\x00\x02\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x06\x00\x00\x00\x03\x00\x00\x00\x00\x02' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x02\x00\x00\x00\x02\x04' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00\x00\x01' assert next(frames) == ( b'\x02\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x02' ) assert next(frames) == b'\x03\x00\x00\x00\x02\x04' pytest.raises(StopIteration, next, frames)
def generate_frames(ds): """Yield decompressed pixel data frames as :class:`numpy.ndarray`. .. deprecated:: 1.2 Use :func:`~pydicom.pixel_data_handlers.pylibjpeg_handler.generate_frames` instead Parameters ---------- ds : pydicom.dataset.Dataset The dataset containing the pixel data. Yields ------ numpy.ndarray A single frame of the decompressed pixel data. """ try: import pydicom except ImportError: 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 decoders = get_pixel_data_decoders() decode = decoders[ds.file_meta.TransferSyntaxUID] p_interp = ds.PhotometricInterpretation 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)
def test_multi_frame_three_to_one(self): """Test a multi-frame image where each frame is three fragments""" # 2 frames, each 3 fragments long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x20\x00\x00\x00' \ b'\x40\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == ( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') assert next(frames) == ( b'\x02\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') assert next(frames) == ( b'\x03\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') pytest.raises(StopIteration, next, frames)
def test_multi_frame_varied_ratio(self): """Test a multi-frame image where each frames is random fragments""" # 3 frames, 1st is 1 fragment, 2nd is 3 fragments, 3rd is 2 fragments bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x0E\x00\x00\x00' \ b'\x32\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x06\x00\x00\x00\x01\x00\x00\x00\x00\x01' \ b'\xFE\xFF\x00\xE0' \ b'\x02\x00\x00\x00\x02\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x06\x00\x00\x00\x03\x00\x00\x00\x00\x02' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x02\x00\x00\x00\x02\x04' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00\x00\x01' assert next(frames) == ( b'\x02\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x02') assert next(frames) == b'\x03\x00\x00\x00\x02\x04' pytest.raises(StopIteration, next, frames)
def slice_dicom(self, dcm: U) -> U: r"""Slices a DICOM object input according to :func:`get_indices`. .. note: Unlike :func:`slice_array`, this function can perform slicing on compressed DICOMs with out needing to decompress all frames. This can provide a substantial performance gain. """ # copy dicom and read key tags dcm = deepcopy(dcm) num_frames: Optional[SupportsInt] = dcm.get("NumberOfFrames", None) num_frames = int(num_frames) if num_frames is not None else None is_compressed: bool = dcm.file_meta.TransferSyntaxUID.is_compressed start, stop, stride = self.get_indices(num_frames) # read data if is_compressed: frame_iterator: Iterator = generate_pixel_data_frame( dcm.PixelData, num_frames) frames = list(islice(frame_iterator, start, stop, stride)) new_pixel_data = encapsulate(frames) else: all_frames: np.ndarray = dcm.pixel_array frames = all_frames[start:stop:stride] new_pixel_data = frames.tobytes() out_frames = len(frames) dcm.NumberOfFrames = out_frames dcm.PixelData = new_pixel_data return dcm
def test_multi_frame_three_to_one(self): """Test a multi-frame image where each frame is three fragments""" # 2 frames, each 3 fragments long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x20\x00\x00\x00' \ b'\x40\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0\x04\x00\x00\x00\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == ( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' ) assert next(frames) == ( b'\x02\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' ) assert next(frames) == ( b'\x03\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' ) pytest.raises(StopIteration, next, frames)
def test_empty_bot_too_few_fragments(self): """Test parsing with too few fragments.""" ds = dcmread(JP2K_10FRAME_NOBOT) assert 10 == ds.NumberOfFrames msg = (r"Unable to parse encapsulated pixel data as the Basic " r"Offset Table is empty and there are fewer fragments then " r"frames; the dataset may be corrupt") with pytest.raises(ValueError, match=msg): next(generate_pixel_data_frame(ds.PixelData, 20))
def test_empty_bot_single_fragment(self): """Test a single-frame image where the frame is one fragments""" # 1 frame, 1 fragment long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x00\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00' pytest.raises(StopIteration, next, frames)
def test_empty_bot_multi_fragments_per_frame(self): """Test multi-frame where multiple frags per frame and no BOT.""" # Regression test for #685 ds = dcmread(JP2K_10FRAME_NOBOT) assert 10 == ds.NumberOfFrames frame_gen = generate_pixel_data_frame(ds.PixelData, ds.NumberOfFrames) for ii in range(10): next(frame_gen) with pytest.raises(StopIteration): next(frame_gen)
def test_empty_bot_single_fragment(self): """Test a single-frame image where the frame is one fragments""" # 1 frame, 1 fragment long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x00\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00' pytest.raises(StopIteration, next, frames)
def test_non_conformant_raises(): """Test that a non-conformant JPEG image raises an exception.""" ds_list = get_indexed_datasets('1.2.840.10008.1.2.4.51') # Image has invalid Se value in the SOS marker segment item = ds_list['JPEG-lossy.dcm'] assert 0xC000 == item['Status'][1] ds = item['ds'] nr_frames = ds.get('NumberOfFrames', 1) frame = next(generate_pixel_data_frame(ds.PixelData, nr_frames)) msg = ( r"libjpeg error code '-1038' returned from GetJPEGParameters\(\): A " r"misplaced marker segment was found - scan start must be zero " r"and scan stop must be 63 for the sequential operating modes") with pytest.raises(RuntimeError, match=msg): get_parameters(frame)
def test_empty_bot_triple_fragment_single_frame(self): """Test a single-frame image where the frame is three fragments""" # 1 frame, 3 fragments long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x00\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream, 1) assert next(frames) == ( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') pytest.raises(StopIteration, next, frames)
def test_bot_triple_fragment_single_frame(self): """Test a single-frame image where the frame is three fragments""" # 1 frame, 3 fragments long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == ( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' ) pytest.raises(StopIteration, next, frames)
def test_decode_bytes(): """Test decode using bytes.""" index = get_indexed_datasets('1.2.840.10008.1.2.4.50') ds = index['JPEGBaseline_1s_1f_u_08_08.dcm']['ds'] nr_frames = ds.get('NumberOfFrames', 1) frame = next(generate_pixel_data_frame(ds.PixelData, nr_frames)) arr = decode(frame) assert arr.flags.writeable assert 'uint8' == arr.dtype assert (ds.Rows, ds.Columns) == arr.shape # Reference values from GDCM handler 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 142 == arr[55, 50] assert 1 == arr[65, 50] assert 64 == arr[75, 50] assert 192 == arr[85, 50] assert 255 == arr[95, 50]
def test_multi_frame_one_to_one(self): """Test a multi-frame image where each frame is one fragment""" # 3 frames, each 1 fragment long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x0C\x00\x00\x00' \ b'\x18\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00' assert next(frames) == b'\x02\x00\x00\x00' assert next(frames) == b'\x03\x00\x00\x00' pytest.raises(StopIteration, next, frames)
def test_multi_frame_one_to_one(self): """Test a multi-frame image where each frame is one fragment""" # 3 frames, each 1 fragment long bytestream = b'\xFE\xFF\x00\xE0' \ b'\x0C\x00\x00\x00' \ b'\x00\x00\x00\x00' \ b'\x0C\x00\x00\x00' \ b'\x18\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x01\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x02\x00\x00\x00' \ b'\xFE\xFF\x00\xE0' \ b'\x04\x00\x00\x00' \ b'\x03\x00\x00\x00' frames = generate_pixel_data_frame(bytestream) assert next(frames) == b'\x01\x00\x00\x00' assert next(frames) == b'\x02\x00\x00\x00' assert next(frames) == b'\x03\x00\x00\x00' pytest.raises(StopIteration, next, frames)
def generate_frames(ds): """Yield decompressed pixel data frames as :class:`numpy.ndarray`. Parameters ---------- ds : pydicom.dataset.Dataset The dataset containing the pixel data. Yields ------ numpy.ndarray A single frame of the decompressed pixel data. """ from pydicom.encaps import generate_pixel_data_frame from pydicom.pixel_data_handlers.util import pixel_dtype decoders = get_pixel_data_decoders() decode = decoders[ds.file_meta.TransferSyntaxUID] p_interp = ds.PhotometricInterpretation 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)
def generate_frames(ds: "Dataset", reshape: bool = True) -> "np.ndarray": """Yield a *Pixel Data* frame from `ds` as an :class:`~numpy.ndarray`. .. versionadded:: 2.1 Parameters ---------- ds : pydicom.dataset.Dataset The :class:`Dataset` containing an :dcm:`Image Pixel <part03/sect_C.7.6.3.html>` module and the *Pixel Data* to be converted. reshape : bool, optional If ``True`` (default), then the returned :class:`~numpy.ndarray` will be reshaped to the correct dimensions. If ``False`` then no reshaping will be performed. Yields ------- numpy.ndarray A single frame of (7FE0,0010) *Pixel Data* as an :class:`~numpy.ndarray` with an appropriate dtype for the data. Raises ------ AttributeError If `ds` is missing a required element. RuntimeError If the plugin required to decode the pixel data is not installed. """ tsyntax = ds.file_meta.TransferSyntaxUID # The check of transfer syntax must be first if tsyntax not in _DECODERS: if tsyntax in _OPENJPEG_SYNTAXES: plugin = "pylibjpeg-openjpeg" else: plugin = "pylibjpeg-libjpeg" raise RuntimeError( f"Unable to convert the Pixel Data as the '{plugin}' plugin is " f"not installed") # Check required elements required_elements = [ "BitsAllocated", "Rows", "Columns", "PixelRepresentation", "SamplesPerPixel", "PhotometricInterpretation", "PixelData", ] missing = [elem for elem in required_elements if elem not in ds] if missing: raise AttributeError( "Unable to convert the pixel data as the following required " "elements are missing from the dataset: " + ", ".join(missing)) decoder = _DECODERS[tsyntax] LOGGER.debug(f"Decoding {tsyntax.name} encoded Pixel Data using {decoder}") nr_frames = getattr(ds, "NumberOfFrames", 1) image_px_module = ds.group_dataset(0x0028) dtype = pixel_dtype(ds) for frame in generate_pixel_data_frame(ds.PixelData, nr_frames): arr = decoder(frame, image_px_module) # Re-view as pylibjpeg returns a 1D uint8 ndarray arr = arr.view(dtype) if not reshape: yield arr continue if ds.SamplesPerPixel == 1: yield arr.reshape(ds.Rows, ds.Columns) else: # JPEG, JPEG-LS and JPEG 2000 are all Planar Configuration 0 yield arr.reshape(ds.Rows, ds.Columns, ds.SamplesPerPixel)
def generate_frames(ds): """Return a frame generator for DICOM datasets.""" nr_frames = ds.get('NumberOfFrames', 1) return generate_pixel_data_frame(ds.PixelData, nr_frames)
def generate_frames(self, ds): """Return a generator object with the dataset's pixel data frames.""" nr_frames = ds.get('NumberOfFrames', 1) return generate_pixel_data_frame(ds.PixelData, nr_frames)
def get_pixeldata(ds): """Return a :class:`numpy.ndarray` of the pixel data. Parameters ---------- ds : Dataset The :class:`Dataset` containing an Image Pixel, Floating Point Image Pixel or Double Floating Point Image Pixel module and the *Pixel Data*, *Float Pixel Data* or *Double Float Pixel Data* to be converted. If (0028,0004) *Photometric Interpretation* is `'YBR_FULL_422'` then the pixel data will be resampled to 3 channel data as per Part 3, :dcm:`Annex C.7.6.3.1.2 <part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2>` of the DICOM Standard. Returns ------- np.ndarray The contents of (7FE0,0010) *Pixel Data* as a 1D array. """ tsyntax = ds.file_meta.TransferSyntaxUID # The check of transfer syntax must be first if tsyntax not in SUPPORTED_TRANSFER_SYNTAXES: raise NotImplementedError( "Unable to convert the pixel data as the transfer syntax " "is not supported by the pylibjpeg pixel data handler." ) # Check required elements required_elements = [ 'BitsAllocated', 'Rows', 'Columns', 'PixelRepresentation', 'SamplesPerPixel', 'PhotometricInterpretation', 'PixelData', ] missing = [elem for elem in required_elements if elem not in ds] if missing: raise AttributeError( "Unable to convert the pixel data as the following required " "elements are missing from the dataset: " + ", ".join(missing) ) # Calculate the expected length of the pixel data (in bytes) # Note: this does NOT include the trailing null byte for odd length data expected_len = get_expected_length(ds) if ds.PhotometricInterpretation == 'YBR_FULL_422': # libjpeg has already resampled the pixel data, see PS3.3 C.7.6.3.1.2 expected_len = expected_len // 2 * 3 p_interp = ds.PhotometricInterpretation # How long each frame is in bytes nr_frames = getattr(ds, 'NumberOfFrames', 1) frame_len = expected_len // nr_frames # The decoded data will be placed here arr = np.empty(expected_len, np.uint8) # Generators for the encoded JPG image frame(s) and insertion offsets generate_frames = generate_pixel_data_frame(ds.PixelData, nr_frames) generate_offsets = range(0, expected_len, frame_len) for frame, offset in zip(generate_frames, generate_offsets): # Encoded JPG data to be sent to the decoder frame = np.frombuffer(frame, np.uint8) arr[offset:offset + frame_len] = decode_pixel_data( frame, ds.group_dataset(0x0028) ) return arr.view(pixel_dtype(ds))
def get_pixeldata(ds): """Return a :class:`numpy.ndarray` of the pixel data. Parameters ---------- ds : pydicom.dataset.Dataset The :class:`Dataset` containing an Image Pixel module and the *Pixel Data* to be converted. Returns ------- numpy.ndarray The contents of (7FE0,0010) *Pixel Data* as a 1D array. Raises ------ AttributeError If `ds` is missing a required element. NotImplementedError If `ds` contains pixel data in an unsupported format. ValueError If the actual length of the pixel data doesn't match the expected length. """ tsyntax = ds.file_meta.TransferSyntaxUID # The check of transfer syntax must be first if tsyntax not in SUPPORTED_TRANSFER_SYNTAXES: raise NotImplementedError( "Unable to convert the pixel data as there are no pylibjpeg " "plugins available to decode pixel data encoded using '{}'".format( tsyntax.name)) # Check required elements required_elements = [ 'BitsAllocated', 'Rows', 'Columns', 'PixelRepresentation', 'SamplesPerPixel', 'PhotometricInterpretation', 'PixelData', ] missing = [elem for elem in required_elements if elem not in ds] if missing: raise AttributeError( "Unable to convert the pixel data as the following required " "elements are missing from the dataset: " + ", ".join(missing)) # Calculate the expected length of the pixel data (in bytes) # Note: this does NOT include the trailing null byte for odd length data expected_len = get_expected_length(ds) if ds.PhotometricInterpretation == 'YBR_FULL_422': # JPEG Transfer Syntaxes # Plugin should have already resampled the pixel data # see PS3.3 C.7.6.3.1.2 expected_len = expected_len // 2 * 3 p_interp = ds.PhotometricInterpretation # How long each frame is in bytes nr_frames = getattr(ds, 'NumberOfFrames', 1) frame_len = expected_len // nr_frames # The decoded data will be placed here arr = np.empty(expected_len, np.uint8) decoder = _DECODERS[tsyntax] LOGGER.debug("Decoding {} Pixel Data using {}".format(tsyntax, decoder)) # Generators for the encoded JPEG image frame(s) and insertion offsets generate_frames = generate_pixel_data_frame(ds.PixelData, nr_frames) generate_offsets = range(0, expected_len, frame_len) pixel_module = ds.group_dataset(0x0028) for frame, offset in zip(generate_frames, generate_offsets): # Encoded JPEG data to be sent to the decoder arr[offset:offset + frame_len] = decoder(frame, pixel_module) if tsyntax in [JPEG2000, JPEG2000Lossless] and APPLY_J2K_CORRECTIONS: j2k_parameters = get_j2k_parameters(frame) if j2k_parameters: shift = ds.BitsAllocated - j2k_parameters['precision'] if (shift and not j2k_parameters['is_signed'] and bool(ds.PixelRepresentation)): # Correct for a mismatch between J2K and Pixel Representation # by converting unsigned data to signed (2's complement) pixel_module.PixelRepresentation = 0 # This probably isn't very efficient arr = arr.view(pixel_dtype(pixel_module)) np.left_shift(arr, shift, out=arr) arr = arr.astype(pixel_dtype(ds)) return np.right_shift(arr, shift) return arr.view(pixel_dtype(ds))
def generate_frames(ds: "Dataset", reshape: bool = True) -> Iterable["np.ndarray"]: """Yield a *Pixel Data* frame from `ds` as an :class:`~numpy.ndarray`. .. versionadded:: 2.1 Parameters ---------- ds : pydicom.dataset.Dataset The :class:`Dataset` containing an :dcm:`Image Pixel <part03/sect_C.7.6.3.html>` module and the *Pixel Data* to be converted. reshape : bool, optional If ``True`` (default), then the returned :class:`~numpy.ndarray` will be reshaped to the correct dimensions. If ``False`` then no reshaping will be performed. Yields ------- numpy.ndarray A single frame of (7FE0,0010) *Pixel Data* as an :class:`~numpy.ndarray` with an appropriate dtype for the data. Raises ------ AttributeError If `ds` is missing a required element. RuntimeError If the plugin required to decode the pixel data is not installed. """ tsyntax = ds.file_meta.TransferSyntaxUID # The check of transfer syntax must be first if tsyntax not in _DECODERS: if tsyntax in _OPENJPEG_SYNTAXES: plugin = "pylibjpeg-openjpeg" elif tsyntax in _LIBJPEG_SYNTAXES: plugin = "pylibjpeg-libjpeg" else: plugin = "pylibjpeg-rle" raise RuntimeError( f"Unable to convert the Pixel Data as the '{plugin}' plugin is " f"not installed") # Check required elements required_elements = [ "BitsAllocated", "Rows", "Columns", "PixelRepresentation", "SamplesPerPixel", "PhotometricInterpretation", "PixelData", ] missing = [elem for elem in required_elements if elem not in ds] if missing: raise AttributeError( "Unable to convert the pixel data as the following required " "elements are missing from the dataset: " + ", ".join(missing)) decoder = _DECODERS[tsyntax] LOGGER.debug(f"Decoding {tsyntax.name} encoded Pixel Data using {decoder}") nr_frames = getattr(ds, "NumberOfFrames", 1) pixel_module = ds.group_dataset(0x0028) dtype = pixel_dtype(ds) bits_stored = cast(int, ds.BitsStored) bits_allocated = cast(int, ds.BitsAllocated) for frame in generate_pixel_data_frame(ds.PixelData, nr_frames): arr = decoder(frame, pixel_module) if (tsyntax in [JPEG2000, JPEG2000Lossless] and config.APPLY_J2K_CORRECTIONS): param = get_j2k_parameters(frame) j2k_sign = param.setdefault('is_signed', True) j2k_precision = cast(int, param.setdefault('precision', bits_stored)) shift = bits_allocated - j2k_precision if shift and not j2k_sign and j2k_sign != ds.PixelRepresentation: # Convert unsigned J2K data to 2s complement # Can only get here if parsed J2K codestream OK pixel_module.PixelRepresentation = 0 arr = arr.view(pixel_dtype(pixel_module)) arr = np.left_shift(arr, shift) arr = arr.astype(dtype) arr = np.right_shift(arr, shift) if arr.dtype != dtype: # Re-view as pylibjpeg returns a 1D uint8 ndarray arr = arr.view(dtype) if not reshape: yield arr continue if ds.SamplesPerPixel == 1: yield arr.reshape(ds.Rows, ds.Columns) else: if tsyntax == RLELossless: # RLE Lossless is Planar Configuration 1 arr = arr.reshape(ds.SamplesPerPixel, ds.Rows, ds.Columns) yield arr.transpose(1, 2, 0) else: # JPEG, JPEG-LS and JPEG 2000 are all Planar Configuration 0 yield arr.reshape(ds.Rows, ds.Columns, ds.SamplesPerPixel)