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]
Beispiel #2
0
 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)
Beispiel #3
0
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)
Beispiel #4
0
 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)
Beispiel #5
0
 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)
Beispiel #6
0
    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
Beispiel #7
0
 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)
Beispiel #8
0
    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))
Beispiel #9
0
 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)
Beispiel #10
0
    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)
Beispiel #11
0
 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)
Beispiel #13
0
 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)
Beispiel #14
0
 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]
Beispiel #16
0
 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)
Beispiel #17
0
 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)
Beispiel #18
0
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)
Beispiel #19
0
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)
Beispiel #20
0
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))
Beispiel #23
0
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))
Beispiel #24
0
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)