コード例 #1
0
ファイル: test_encaps.py プロジェクト: darcymason/pydicom
    def test_encapsulate_single_fragment_per_frame_bot(self):
        """Test encapsulating single fragment per frame with BOT values."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=True)
        test_frames = decode_data_sequence(data)
        for a, b in zip(test_frames, frames):
            assert a == b

        fp = DicomBytesIO(data)
        fp.is_little_endian = True
        offsets = get_frame_offsets(fp)
        assert offsets == [
            0x0000,  # 0
            0x0eee,  # 3822
            0x1df6,  # 7670
            0x2cf8,  # 11512
            0x3bfc,  # 15356
            0x4ade,  # 19166
            0x59a2,  # 22946
            0x6834,  # 26676
            0x76e2,  # 30434
            0x8594  # 34196
        ]
コード例 #2
0
    def test_encapsulate_single_fragment_per_frame_bot(self):
        """Test encapsulating single fragment per frame with BOT values."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=True)
        test_frames = decode_data_sequence(data)
        for a, b in zip(test_frames, frames):
            assert a == b

        fp = DicomBytesIO(data)
        fp.is_little_endian = True
        length, offsets = get_frame_offsets(fp)
        assert offsets == [
            0x0000,  # 0
            0x0eee,  # 3822
            0x1df6,  # 7670
            0x2cf8,  # 11512
            0x3bfc,  # 15356
            0x4ade,  # 19166
            0x59a2,  # 22946
            0x6834,  # 26676
            0x76e2,  # 30434
            0x8594  # 34196
        ]
コード例 #3
0
    def test_encapsulate_single_fragment_per_frame_no_bot(self):
        """Test encapsulating single fragment per frame with no BOT values."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=False)
        test_frames = decode_data_sequence(data)
        for a, b in zip(test_frames, frames):
            assert a == b

        # Original data has no BOT values
        assert data == ds.PixelData
コード例 #4
0
ファイル: test_encaps.py プロジェクト: darcymason/pydicom
    def test_encapsulate_single_fragment_per_frame_no_bot(self):
        """Test encapsulating single fragment per frame with no BOT values."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=False)
        test_frames = decode_data_sequence(data)
        for a, b in zip(test_frames, frames):
            assert a == b

        # Original data has no BOT values
        assert data == ds.PixelData
コード例 #5
0
ファイル: test_encaps.py プロジェクト: kayarre/pydicom
 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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00\x00\x01', b'\x02\x00', b'\x02\x00\x00\x00',
         b'\x03\x00\x00\x00\x00\x02', b'\x03\x00\x00\x00', b'\x02\x04'
     ]
コード例 #6
0
    def setup(self):
        # MONOCHROME2, 64x64, 1 sample/pixel, 16 bits allocated, 12 bits stored
        self.ds = dcmread(EMRI_RLE_10F)
        self.frames = decode_data_sequence(self.ds.PixelData)
        assert len(self.frames) == 10

        self.no_runs = 1
コード例 #7
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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00\x00\x01', b'\x02\x00', b'\x02\x00\x00\x00',
         b'\x03\x00\x00\x00\x00\x02', b'\x03\x00\x00\x00', b'\x02\x04'
     ]
コード例 #8
0
    def setup(self):
        # MONOCHROME2, 64x64, 1 sample/pixel, 16 bits allocated, 12 bits stored
        self.ds = dcmread(EMRI_RLE_10F)
        self.frames = decode_data_sequence(self.ds.PixelData)
        assert len(self.frames) == 10

        self.no_runs = 100
コード例 #9
0
 def __getitem__(self, key):
   if (key) in self._dsequence:
       return self._dsequence[key]
   elif key not in self._dsstore:
       raise ValueError('Entry does not exist')
   else:
       self._dsequence[key] = decode_data_sequence(self._dsstore[key].PixelData)
       return self._dsequence[key]
コード例 #10
0
def parse_comppressed(ds):
    try:
        print(f"pixeldata:{len(ds.PixelData)}")

        if getattr(ds, 'NumberOfFrames', 1) > 1:
            print("multi frame")
            j2k_precision, j2k_sign = None, None
            # multiple compressed frames
            # working case (50):
            # 1. 0002.dcm, some are [-5], [-4], [-6]. 512x512
            # 2. color3d_jpeg_baseline , some frames needs [-1] but some do not need. size unknown?
            frame_count = 0
            for frame in decode_data_sequence(ds.PixelData):
                frame_count += 1
                print(f"frame i:{frame_count}, len:{len(frame)}")
                # a = frame[0]
                # b = frame[1]
                # c = frame[len(frame)-2]
                # d = frame[len(frame)-1]
                # print(f"{a},{b},{c},{d}")
                if frame_count == 1:
                    pixel_data = frame
                # im = _decompress_single_frame(
                #     frame,
                #     transfer_syntax,
                #     ds.PhotometricInterpretation
                # )
                # if 'YBR' in ds.PhotometricInterpretation:
                #     im.draft('YCbCr', (ds.Rows, ds.Columns))
                # pixel_bytes.extend(im.tobytes())

                # if not j2k_precision:
                #     params = get_j2k_parameters(frame)
                #     j2k_precision = params.setdefault("precision", ds.BitsStored)
                #     j2k_sign = params.setdefault("is_signed", None)
            # TODO: what is the rule of -5/-1? But even not using pixel_data[:-1], pixel_data[:-5], still work
            p2 = pixel_data
        else:
            print("single frame")
            # working case but browser can not render DICOM made JPEG Lossless :
            # JPGLosslessP14SV1_1s_1f_8b.dcm, 70. 1024x768
            pixel_data = defragment_data(ds.PixelData)
            p2 = pixel_data
        print(f"pixel_data:{len(pixel_data)}")
        # return None, pixel_data

        # try:
        #     fio = BytesIO(ds.PixelData)  # pixel_data)
        #     image = Image.open(fio)
        # except Exception as e:
        #     print(f"pillow error:{e}")

        # print('pillow done')
        # JPEG57-MR-MONO2-12-shoulder data:718940 -> data:718924
        return None, p2
    except Exception as e:
        print("failed to get compressed data")
        raise e
コード例 #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 = decode_data_sequence(bytestream)
     assert frames == [b'\x01\x00\x00\x00']
コード例 #12
0
ファイル: test_encaps.py プロジェクト: kayarre/pydicom
 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 = decode_data_sequence(bytestream)
     assert frames == [b'\x01\x00\x00\x00']
コード例 #13
0
def get_pixel_data(ds):
    print(f"PixelData:{len(ds.PixelData)}")
    if getattr(ds, 'NumberOfFrames', 1) > 1:
        j2k_precision, j2k_sign = None, None
        # multiple compressed frames
        frame_count = 0
        for frame in decode_data_sequence(ds.PixelData):
            frame_count += 1
            print(f"frame i:{frame_count}, len:{len(frame)}")
            if frame_count == 1:
                pixel_data = frame
    else:
        pixel_data = defragment_data(ds.PixelData)
        print(f"pixel_data:{len(pixel_data)}")
    return pixel_data
コード例 #14
0
def get_pixeldata(ds: "Dataset") -> "numpy.ndarray":
    """Return the *Pixel Data* as a :class:`numpy.ndarray`.

    Returns
    -------
    numpy.ndarray
        A correctly sized (but not shaped) numpy array of the *Pixel Data*.

    Raises
    ------
    ImportError
        If the required packages are not available.
    NotImplementedError
        If the transfer syntax is not supported.
    TypeError
        If the pixel data type is unsupported.
    """
    tsyntax = ds.file_meta.TransferSyntaxUID
    if tsyntax not in SUPPORTED_TRANSFER_SYNTAXES:
        raise NotImplementedError(
            f"The jpeg_ls does not support this transfer syntax {tsyntax.name}"
        )

    if not HAVE_JPEGLS:
        raise ImportError(
            "The jpeg_ls package is required to use pixel_array for this "
            f"transfer syntax {tsyntax.name}, and jpeg_ls could not be "
            "imported")

    pixel_bytes = bytearray()

    nr_frames = getattr(ds, "NumberOfFrames", 1) or 1
    if nr_frames > 1:
        for src in decode_data_sequence(ds.PixelData):
            frame = jpeg_ls.decode(numpy.frombuffer(src, dtype='u1'))
            pixel_bytes.extend(frame.tobytes())
    else:
        src = defragment_data(ds.PixelData)
        frame = jpeg_ls.decode(numpy.frombuffer(src, dtype='u1'))
        pixel_bytes.extend(frame.tobytes())

    arr = numpy.frombuffer(pixel_bytes, pixel_dtype(ds))

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return cast("numpy.ndarray", arr)
コード例 #15
0
ファイル: test_encaps.py プロジェクト: kayarre/pydicom
 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 = decode_data_sequence(bytestream)
     assert frames == [b'\x01\x00\x00\x00',
                       b'\x02\x00\x00\x00',
                       b'\x03\x00\x00\x00']
コード例 #16
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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00'
     ]
コード例 #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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00'
     ]
コード例 #18
0
ファイル: test_encaps.py プロジェクト: kayarre/pydicom
 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 = decode_data_sequence(bytestream)
     assert frames == [b'\x01\x00\x00\x00',
                       b'\x02\x00\x00\x00',
                       b'\x03\x00\x00\x00']
コード例 #19
0
ファイル: test_encaps.py プロジェクト: darcymason/pydicom
    def test_encapsulate_bot(self):
        """Test the Basic Offset Table is correct."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=True)
        assert data[:56] == (
            b'\xfe\xff\x00\xe0'  # Basic offset table item tag
            b'\x28\x00\x00\x00'  # Basic offset table length
            b'\x00\x00\x00\x00'  # First offset
            b'\xee\x0e\x00\x00'
            b'\xf6\x1d\x00\x00'
            b'\xf8\x2c\x00\x00'
            b'\xfc\x3b\x00\x00'
            b'\xde\x4a\x00\x00'
            b'\xa2\x59\x00\x00'
            b'\x34\x68\x00\x00'
            b'\xe2\x76\x00\x00'
            b'\x94\x85\x00\x00'  # Last offset
            b'\xfe\xff\x00\xe0'  # Next item tag
            b'\xe6\x0e\x00\x00'  # Next item length
        )
コード例 #20
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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00',
         b'\x02\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00',
         b'\x03\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00'
     ]
コード例 #21
0
    def test_encapsulate_bot(self):
        """Test the Basic Offset Table is correct."""
        ds = dcmread(JP2K_10FRAME_NOBOT)
        frames = decode_data_sequence(ds.PixelData)
        assert len(frames) == 10

        data = encapsulate(frames, fragments_per_frame=1, has_bot=True)
        assert data[:56] == (
            b'\xfe\xff\x00\xe0'  # Basic offset table item tag
            b'\x28\x00\x00\x00'  # Basic offset table length
            b'\x00\x00\x00\x00'  # First offset
            b'\xee\x0e\x00\x00'
            b'\xf6\x1d\x00\x00'
            b'\xf8\x2c\x00\x00'
            b'\xfc\x3b\x00\x00'
            b'\xde\x4a\x00\x00'
            b'\xa2\x59\x00\x00'
            b'\x34\x68\x00\x00'
            b'\xe2\x76\x00\x00'
            b'\x94\x85\x00\x00'  # Last offset
            b'\xfe\xff\x00\xe0'  # Next item tag
            b'\xe6\x0e\x00\x00'  # Next item length
        )
コード例 #22
0
ファイル: test_encaps.py プロジェクト: kayarre/pydicom
 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 = decode_data_sequence(bytestream)
     assert frames == [
         b'\x01\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00',
         b'\x02\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00',
         b'\x03\x00\x00\x00', b'\x02\x00\x00\x00', b'\x03\x00\x00\x00'
     ]
コード例 #23
0
ファイル: pillow_handler.py プロジェクト: xslxjz/pydicom
def get_pixeldata(ds):
    """Return a :class:`numpy.ndarray` of the *Pixel Data*.

    Parameters
    ----------
    ds : Dataset
        The :class:`Dataset` containing an Image Pixel module and the
        *Pixel Data* to be decompressed and returned.

    Returns
    -------
    numpy.ndarray
       The contents of (7FE0,0010) *Pixel Data* as a 1D array.

    Raises
    ------
    ImportError
        If Pillow is not available.
    NotImplementedError
        If the transfer syntax is not supported
    """
    logger.debug(
        "Trying to use Pillow to read pixel array "
        "(has pillow = %s)", HAVE_PIL)
    transfer_syntax = ds.file_meta.TransferSyntaxUID
    if not HAVE_PIL:
        msg = ("The pillow package is required to use pixel_array for "
               "this transfer syntax {0}, and pillow could not be "
               "imported.".format(transfer_syntax.name))
        raise ImportError(msg)

    if not HAVE_JPEG and transfer_syntax in PillowJPEGTransferSyntaxes:
        msg = ("this transfer syntax {0}, can not be read because "
               "Pillow lacks the jpeg decoder plugin".format(
                   transfer_syntax.name))
        raise NotImplementedError(msg)

    if not HAVE_JPEG2K and transfer_syntax in PillowJPEG2000TransferSyntaxes:
        msg = ("this transfer syntax {0}, can not be read because "
               "Pillow lacks the jpeg 2000 decoder plugin".format(
                   transfer_syntax.name))
        raise NotImplementedError(msg)

    if transfer_syntax not in PillowSupportedTransferSyntaxes:
        msg = ("this transfer syntax {0}, can not be read because "
               "Pillow does not support this syntax".format(
                   transfer_syntax.name))
        raise NotImplementedError(msg)

    if transfer_syntax in PillowJPEGTransferSyntaxes:
        logger.debug("This is a JPEG lossy format")
        if ds.BitsAllocated > 8:
            raise NotImplementedError("JPEG Lossy only supported if "
                                      "Bits Allocated = 8")
    elif transfer_syntax in PillowJPEG2000TransferSyntaxes:
        logger.debug("This is a JPEG 2000 format")
    else:
        logger.debug("This is a another pillow supported format")

    pixel_bytes = bytearray()
    if getattr(ds, 'NumberOfFrames', 1) > 1:
        # multiple compressed frames
        for frame in decode_data_sequence(ds.PixelData):
            decompressed_image = Image.open(io.BytesIO(frame))
            pixel_bytes.extend(decompressed_image.tobytes())
    else:
        # single compressed frame
        pixel_data = defragment_data(ds.PixelData)
        decompressed_image = Image.open(io.BytesIO(pixel_data))
        pixel_bytes.extend(decompressed_image.tobytes())

    logger.debug("Successfully read %s pixel bytes", len(pixel_bytes))

    arr = numpy.frombuffer(pixel_bytes, pixel_dtype(ds))

    if (transfer_syntax in PillowJPEG2000TransferSyntaxes
            and ds.BitsStored == 16):
        # WHY IS THIS EVEN NECESSARY??
        arr &= 0x7FFF

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return arr
コード例 #24
0
ファイル: bench_encaps.py プロジェクト: darcymason/pydicom
 def setup(self):
     """Setup the test"""
     ds = dcmread(JP2K_10FRAME)
     self.test_data = decode_data_sequence(ds.PixelData)
     assert len(self.test_data) == 10
     self.no_runs = 1000
コード例 #25
0
 def setup(self):
     """Setup the test"""
     ds = dcmread(JP2K_10FRAME)
     self.test_data = decode_data_sequence(ds.PixelData)
     assert len(self.test_data) == 10
     self.no_runs = 1000
コード例 #26
0
def get_pixeldata(ds: "Dataset") -> "numpy.ndarray":
    """Return a :class:`numpy.ndarray` of the *Pixel Data*.

    Parameters
    ----------
    ds : Dataset
        The :class:`Dataset` containing an Image Pixel module and the
        *Pixel Data* to be decompressed and returned.

    Returns
    -------
    numpy.ndarray
       The contents of (7FE0,0010) *Pixel Data* as a 1D array.

    Raises
    ------
    ImportError
        If Pillow is not available.
    NotImplementedError
        If the transfer syntax is not supported
    """
    transfer_syntax = ds.file_meta.TransferSyntaxUID

    if not HAVE_PIL:
        raise ImportError(
            f"The pillow package is required to use pixel_array for "
            f"this transfer syntax {transfer_syntax.name}, and pillow could "
            f"not be imported.")

    if not HAVE_JPEG and transfer_syntax in PillowJPEGTransferSyntaxes:
        raise NotImplementedError(
            f"The pixel data with transfer syntax {transfer_syntax.name}, "
            f"cannot be read because Pillow lacks the JPEG plugin")

    if not HAVE_JPEG2K and transfer_syntax in PillowJPEG2000TransferSyntaxes:
        raise NotImplementedError(
            f"The pixel data with transfer syntax {transfer_syntax.name}, "
            f"cannot be read because Pillow lacks the JPEG 2000 plugin")

    if transfer_syntax == JPEGExtended12Bit and ds.BitsAllocated != 8:
        raise NotImplementedError(
            f"{JPEGExtended12Bit} - {JPEGExtended12Bit.name} only supported "
            "by Pillow if Bits Allocated = 8")

    photometric_interpretation = cast(str, ds.PhotometricInterpretation)
    rows = cast(int, ds.Rows)
    columns = cast(int, ds.Columns)
    bits_stored = cast(int, ds.BitsStored)
    bits_allocated = cast(int, ds.BitsAllocated)
    nr_frames = getattr(ds, 'NumberOfFrames', 1) or 1

    pixel_bytes = bytearray()
    if nr_frames > 1:
        j2k_precision, j2k_sign = None, None
        # multiple compressed frames
        for frame in decode_data_sequence(ds.PixelData):
            im = _decompress_single_frame(frame, transfer_syntax,
                                          photometric_interpretation)
            if 'YBR' in photometric_interpretation:
                im.draft('YCbCr', (rows, columns))
            pixel_bytes.extend(im.tobytes())

            if not j2k_precision:
                params = get_j2k_parameters(frame)
                j2k_precision = cast(
                    int, params.setdefault("precision", bits_stored))
                j2k_sign = params.setdefault("is_signed", None)

    else:
        # single compressed frame
        pixel_data = defragment_data(ds.PixelData)
        im = _decompress_single_frame(pixel_data, transfer_syntax,
                                      photometric_interpretation)
        if 'YBR' in photometric_interpretation:
            im.draft('YCbCr', (rows, columns))
        pixel_bytes.extend(im.tobytes())

        params = get_j2k_parameters(pixel_data)
        j2k_precision = cast(int, params.setdefault("precision", bits_stored))
        j2k_sign = params.setdefault("is_signed", None)

    logger.debug(f"Successfully read {len(pixel_bytes)} pixel bytes")

    arr = numpy.frombuffer(pixel_bytes, pixel_dtype(ds))

    if transfer_syntax in PillowJPEG2000TransferSyntaxes:
        # Pillow converts N-bit data to 8- or 16-bit unsigned data,
        # See Pillow src/libImaging/Jpeg2KDecode.c::j2ku_gray_i
        shift = bits_allocated - bits_stored
        if j2k_precision and j2k_precision != bits_stored:
            warnings.warn(
                f"The (0028,0101) 'Bits Stored' value ({bits_stored}-bit) "
                f"doesn't match the JPEG 2000 data ({j2k_precision}-bit). "
                f"It's recommended that you change the 'Bits Stored' value")

        if config.APPLY_J2K_CORRECTIONS and j2k_precision:
            # Corrections based on J2K data
            shift = bits_allocated - j2k_precision
            if not j2k_sign and j2k_sign != ds.PixelRepresentation:
                # Convert unsigned J2K data to 2's complement
                arr = numpy.right_shift(arr, shift)
            else:
                if ds.PixelRepresentation == 1:
                    # Pillow converts signed data to unsigned
                    #   so we need to undo this conversion
                    arr -= 2**(bits_allocated - 1)

                if shift:
                    arr = numpy.right_shift(arr, shift)
        else:
            # Corrections based on dataset elements
            if ds.PixelRepresentation == 1:
                arr -= 2**(bits_allocated - 1)

            if shift:
                arr = numpy.right_shift(arr, shift)

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return cast("numpy.ndarray", arr)
コード例 #27
0
def get_manufacturer_independent_pixel_image2d_array(ds, has_TransferSyntax):

    # '1.2.840.10008.1.2.4.90'  #
    # ds.file_meta.TransferSyntaxUID = '1.2.840.10008.1.2.4.99'  # '1.2.840.10008.1.2.1.99'
    # has_TransferSyntax = True
    # print(f"syntax:{ds.file_meta.TransferSyntaxUID}")

    # RLE 1.2.840.10008.1.2.5:  US-PAL-8-10x-echo.dcm is automatically handled as uncompressed case
    if (has_TransferSyntax and ds.file_meta.TransferSyntaxUID in [
            "1.2.840.10008.1.2.4.50", "1.2.840.10008.1.2.4.51",
            "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70",
            "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.4.81",
            "1.2.840.10008.1.2.4.90", "1.2.840.10008.1.2.4.91"
    ]):
        print("compressed case !!!!!!!!!")

        # return None, ds.PixelData
        # ref: https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/pillow_handler.py
        print(
            "try to get compressed dicom's pixel data manually, can not handle by pydicom in pyodide, lack of some pyodide extension"
        )
        try:
            print(f"pixeldata:{len(ds.PixelData)}")

            # TODO: only get 1st frame for multiple frame case and will improve later
            if getattr(ds, 'NumberOfFrames', 1) > 1:
                print("multi frame")
                j2k_precision, j2k_sign = None, None
                # multiple compressed frames
                # working case (50):
                # 1. 0002.dcm, some are [-5], [-4], [-6]. 512x512
                # 2. color3d_jpeg_baseline , some frames needs [-1] but some do not need. size unknown?
                frame_count = 0
                for frame in decode_data_sequence(ds.PixelData):
                    frame_count += 1
                    # print(f"frame i:{frame_count}, len:{len(frame)}")
                    # a = frame[0]
                    # b = frame[1]
                    # c = frame[len(frame)-2]
                    # d = frame[len(frame)-1]
                    # print(f"{a},{b},{c},{d}")
                    if frame_count == 1:
                        pixel_data = frame
                    # im = _decompress_single_frame(
                    #     frame,
                    #     transfer_syntax,
                    #     ds.PhotometricInterpretation
                    # )
                    # if 'YBR' in ds.PhotometricInterpretation:
                    #     im.draft('YCbCr', (ds.Rows, ds.Columns))
                    # pixel_bytes.extend(im.tobytes())

                    # if not j2k_precision:
                    #     params = get_j2k_parameters(frame)
                    #     j2k_precision = params.setdefault("precision", ds.BitsStored)
                    #     j2k_sign = params.setdefault("is_signed", None)
                # TODO: what is the rule of -5/-1? But even not using pixel_data[:-1], pixel_data[:-5], still work
                p2 = pixel_data
            else:
                print("single frame")
                # working case but browser can not render :
                # - JPGLosslessP14SV1_1s_1f_8b.dcm,  DICOM made JPEG Lossless, 1.2.840.10008.1.2.4.70. 1024x768. local mac is able to view.
                # - JPEG57-MR-MONO2-12-shoulder.dcm from https://barre.dev/medical/samples/, JPEG Lossless, 1.2.840.10008.1.2.4.57.
                #   https://products.groupdocs.app/viewer/jpg can be used to view. local mac seeme not able to view (all black)
                # - JPEG-lossy.dcm 1.2.840.10008.1.2.4.51 from https://github.com/pydicom/pydicom/blob/master/pydicom/data/test_files/JPEG-lossy.dcm,
                #   https://products.groupdocs.app/viewer/jpg can be used to view, local mac seems not able to view (all black)

                pixel_data = defragment_data(ds.PixelData)
                p2 = pixel_data
            print(f"pixel_data:{len(pixel_data)}")
            # return None, pixel_data

            # try:
            #     fio = BytesIO(ds.PixelData)  # pixel_data)
            #     image = Image.open(fio)
            # except Exception as e:
            #     print(f"pillow error:{e}")

            # print('pillow done')
            # JPEG57-MR-MONO2-12-shoulder data:718940 -> data:718924
            return None, p2
        except Exception as e:
            print("failed to get compressed data")
            raise e

    print("incompressed")
    print(
        "start reading dicom pixel_array, uncompressed case uses apply_modality_lut"
    )

    try:
        arr = ds.pixel_array
    except Exception as e:
        if has_TransferSyntax == True:
            raise e
        else:
            # http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_10.html
            print(
                "read data fail may due to no TransferSyntaxUID, set it as most often used and default ImplicitVRLittleEndian and try read dicom again"
            )
            ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
            arr = ds.pixel_array
    print(f"read dicom pixel_array ok, shape:{arr.shape}")
    image2d = apply_modality_lut(arr, ds)
    return image2d, None
コード例 #28
0
ファイル: rle_handler.py プロジェクト: seandoyle/pydicom
def get_pixeldata(ds: "Dataset", rle_segment_order: str = '>') -> "np.ndarray":
    """Return an :class:`numpy.ndarray` of the *Pixel Data*.

    Parameters
    ----------
    ds : dataset.Dataset
        The :class:`Dataset` containing an Image Pixel module and the RLE
        encoded *Pixel Data* to be converted.
    rle_segment_order : str
        The order of segments used by the RLE decoder when dealing with *Bits
        Allocated* > 8. Each RLE segment contains 8-bits of the pixel data,
        and segments are supposed to be ordered from MSB to LSB. A value of
        ``'>'`` means interpret the segments as being in big endian order
        (default) while a value of ``'<'`` means interpret the segments as
        being in little endian order which may be possible if the encoded data
        is non-conformant.

    Returns
    -------
    numpy.ndarray
        The decoded 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.
    """
    file_meta = cast("FileMetaDataset", ds.file_meta)  # type: ignore[has-type]
    transfer_syntax = file_meta.TransferSyntaxUID
    # The check of transfer syntax must be first
    if transfer_syntax not in SUPPORTED_TRANSFER_SYNTAXES:
        raise NotImplementedError(
            "Unable to convert the pixel data as the transfer syntax "
            "is not supported by the RLE pixel data handler.")

    # Check required elements
    required_elements = [
        'PixelData', 'BitsAllocated', 'Rows', 'Columns', 'PixelRepresentation',
        'SamplesPerPixel'
    ]
    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))

    nr_bits = cast(int, ds.BitsAllocated)
    nr_samples = cast(int, ds.SamplesPerPixel)
    nr_frames = cast(int, getattr(ds, 'NumberOfFrames', 1) or 1)
    rows = cast(int, ds.Rows)
    cols = cast(int, ds.Columns)

    # Decompress each frame of the pixel data
    pixel_data = bytearray()
    if nr_frames > 1:
        for rle_frame in decode_data_sequence(ds.PixelData):
            frame = _rle_decode_frame(rle_frame, rows, cols, nr_samples,
                                      nr_bits, rle_segment_order)
            pixel_data.extend(frame)
    else:
        frame = _rle_decode_frame(defragment_data(ds.PixelData), rows, cols,
                                  nr_samples, nr_bits, rle_segment_order)
        pixel_data.extend(frame)

    arr = np.frombuffer(pixel_data, pixel_dtype(ds))

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return arr
コード例 #29
0
def get_pixeldata(ds, rle_segment_order='>'):
    """Return an ndarray of the Pixel Data.

    Parameters
    ----------
    ds : dataset.Dataset
        The DICOM dataset containing an Image Pixel module and the RLE encoded
        Pixel Data to be converted.
    rle_segment_order : str
        The order of segments used by the RLE decoder when dealing with Bits
        Allocated > 8. Each RLE segment contains 8-bits of the pixel data,
        and segments are supposed to be ordered from MSB to LSB. A value of
        '>' means interpret the segments as being in big endian order
        (default) while a value of '<' means interpret the segments as being
        in little endian order which may be possible if the encoded data is
        non-conformant.

    Returns
    -------
    np.ndarray
        The decoded contents of the Pixel Data element (7FE0,0010) as a 1D
        array.

    Raises
    ------
    AttributeError
        If the dataset is missing a required element.
    NotImplementedError
        If the dataset contains pixel data in an unsupported format.
    ValueError
        If the actual length of the pixel data doesn't match the expected
        length.
    """
    transfer_syntax = ds.file_meta.TransferSyntaxUID
    # The check of transfer syntax must be first
    if transfer_syntax not in SUPPORTED_TRANSFER_SYNTAXES:
        raise NotImplementedError(
            "Unable to convert the pixel data as the transfer syntax "
            "is not supported by the RLE pixel data handler.")

    # Check required elements
    required_elements = [
        'PixelData', 'BitsAllocated', 'Rows', 'Columns', 'PixelRepresentation',
        'SamplesPerPixel'
    ]
    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))

    nr_bits = ds.BitsAllocated
    nr_samples = ds.SamplesPerPixel
    nr_frames = getattr(ds, 'NumberOfFrames', 1)
    rows = ds.Rows
    cols = ds.Columns

    # Decompress each frame of the pixel data
    pixel_data = bytearray()
    if nr_frames > 1:
        for rle_frame in decode_data_sequence(ds.PixelData):
            frame = _rle_decode_frame(rle_frame, rows, cols, nr_samples,
                                      nr_bits)
            pixel_data.extend(frame)
    else:
        frame = _rle_decode_frame(defragment_data(ds.PixelData), rows, cols,
                                  nr_samples, nr_bits)

        pixel_data.extend(frame)

    # The segment order should be big endian by default but make it possible
    #   to switch if the RLE is non-conformant
    dtype = pixel_dtype(ds).newbyteorder(rle_segment_order)
    arr = np.frombuffer(pixel_data, dtype)

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return arr
コード例 #30
0
ファイル: pillow_handler.py プロジェクト: wannabe2020/pydicom
def get_pixeldata(ds):
    """Return a :class:`numpy.ndarray` of the *Pixel Data*.

    Parameters
    ----------
    ds : Dataset
        The :class:`Dataset` containing an Image Pixel module and the
        *Pixel Data* to be decompressed and returned.

    Returns
    -------
    numpy.ndarray
       The contents of (7FE0,0010) *Pixel Data* as a 1D array.

    Raises
    ------
    ImportError
        If Pillow is not available.
    NotImplementedError
        If the transfer syntax is not supported
    """
    logger.debug(
        "Trying to use Pillow to read pixel array "
        "(has pillow = %s)", HAVE_PIL)
    transfer_syntax = ds.file_meta.TransferSyntaxUID
    logger.debug("Transfer Syntax UID: '{}'".format(transfer_syntax))

    if not HAVE_PIL:
        msg = ("The pillow package is required to use pixel_array for "
               "this transfer syntax {0}, and pillow could not be "
               "imported.".format(transfer_syntax.name))
        raise ImportError(msg)

    if not HAVE_JPEG and transfer_syntax in PillowJPEGTransferSyntaxes:
        msg = ("this transfer syntax {0}, can not be read because "
               "Pillow lacks the jpeg decoder plugin".format(
                   transfer_syntax.name))
        raise NotImplementedError(msg)

    if not HAVE_JPEG2K and transfer_syntax in PillowJPEG2000TransferSyntaxes:
        msg = ("this transfer syntax {0}, can not be read because "
               "Pillow lacks the jpeg 2000 decoder plugin".format(
                   transfer_syntax.name))
        raise NotImplementedError(msg)

    if transfer_syntax == pydicom.uid.JPEGExtended and ds.BitsAllocated != 8:
        raise NotImplementedError(
            "{} - {} only supported by Pillow if Bits Allocated = 8".format(
                pydicom.uid.JPEGExtended, pydicom.uid.JPEGExtended.name))

    pixel_bytes = bytearray()
    if getattr(ds, 'NumberOfFrames', 1) > 1:
        j2k_precision = None
        # multiple compressed frames
        for frame in decode_data_sequence(ds.PixelData):
            im = Image.open(io.BytesIO(frame))
            if 'YBR' in ds.PhotometricInterpretation:
                im.draft('YCbCr', (ds.Rows, ds.Columns))
            pixel_bytes.extend(im.tobytes())

            if not j2k_precision:
                j2k_precision = _get_j2k_precision(frame)
    else:
        # single compressed frame
        pixel_data = defragment_data(ds.PixelData)
        im = Image.open(io.BytesIO(pixel_data))
        if 'YBR' in ds.PhotometricInterpretation:
            im.draft('YCbCr', (ds.Rows, ds.Columns))
        pixel_bytes.extend(im.tobytes())

        j2k_precision = _get_j2k_precision(pixel_data)

    logger.debug("Successfully read %s pixel bytes", len(pixel_bytes))

    arr = numpy.frombuffer(pixel_bytes, pixel_dtype(ds))

    if transfer_syntax in PillowJPEG2000TransferSyntaxes:
        # Pillow converts N-bit data to 8- or 16-bit unsigned data
        # See Pillow src/libImaging/Jpeg2KDecode.c::j2ku_gray_i
        if ds.PixelRepresentation == 1:
            # Pillow converts signed data to unsigned
            #   so we need to undo this conversion
            arr -= 2**(ds.BitsAllocated - 1)

        if j2k_precision and j2k_precision != ds.BitsStored:
            warnings.warn(
                "The (0028,0101) 'Bits Stored' value doesn't match the "
                "sample bit depth of the JPEG2000 pixel data ({} vs {} bit). "
                "It's recommended that you first change the 'Bits Stored' "
                "value to match the JPEG2000 bit depth in order to get the "
                "correct pixel data".format(ds.BitsStored, j2k_precision))

        shift = ds.BitsAllocated - ds.BitsStored
        if shift:
            logger.debug("Shifting right by {} bits".format(shift))
            numpy.right_shift(arr, shift, out=arr)

    if should_change_PhotometricInterpretation_to_RGB(ds):
        ds.PhotometricInterpretation = "RGB"

    return arr
コード例 #31
0
ファイル: sop.py プロジェクト: fedorov/highdicom
    def add_segments(
        self,
        pixel_array: np.ndarray,
        segment_descriptions: Sequence[SegmentDescription],
        plane_positions: Optional[Sequence[PlanePositionSequence]] = None
    ) -> None:
        """Adds one or more segments to the segmentation image.

        Parameters
        ----------
        pixel_array: numpy.ndarray
            Array of segmentation pixel data of boolean, unsigned integer or
            floating point data type representing a mask image. If `pixel_array`
            is a floating-point array or a binary array (containing only the
            values ``True`` and ``False`` or ``0`` and ``1``), the segment
            number used to encode the segment is taken from
            `segment_descriptions`.
            Otherwise, if `pixel_array` contains multiple integer values, each
            value is treated as a different segment whose segment number is
            that integer value. In this case, all segments found in the array
            must be described in `segment_descriptions`. Note that this is
            valid for both ``"BINARY"`` and ``"FRACTIONAL"`` segmentations.
            For ``"FRACTIONAL"`` segmentations, values either encode the
            probability of a given pixel belonging to a segment
            (if `fractional_type` is ``"PROBABILITY"``)
            or the extent to which a segment occupies the pixel
            (if `fractional_type` is ``"OCCUPANCY"``).
            When `pixel_array` has a floating point data type, only one segment
            can be encoded. Additional segments can be subsequently
            added to the `Segmentation` instance using the ``add_segments()``
            method.
            If `pixel_array` represents a 3D image, the first dimension
            represents individual 2D planes and these planes must be ordered
            based on their position in the three-dimensional patient
            coordinate system (first along the X axis, second along the Y axis,
            and third along the Z axis).
            If `pixel_array` represents a tiled 2D image, the first dimension
            represents individual 2D tiles (for one channel and z-stack) and
            these tiles must be ordered based on their position in the tiled
            total pixel matrix (first along the row dimension and second along
            the column dimension, which are defined in the three-dimensional
            slide coordinate system by the direction cosines encoded by the
            *Image Orientation (Slide)* attribute).
        segment_descriptions: Sequence[highdicom.seg.content.SegmentDescription]
            Description of each segment encoded in `pixel_array`. In the case of
            pixel arrays with multiple integer values, the segment description
            with the corresponding segment number is used to describe each
            segment.
        plane_positions: Sequence[highdicom.content.PlanePositionSequence], optional
            Position of each plane in `pixel_array` relative to the
            three-dimensional patient or slide coordinate system.

        Raises
        ------
        ValueError
            When
                - The pixel array is not 2D or 3D numpy array
                - The shape of the pixel array does not match the source images
                - The numbering of the segment descriptions is not
                  monotonically increasing by 1
                - The numbering of the segment descriptions does
                  not begin at 1 (for the first segments added to the instance)
                  or at one greater than the last added segment (for
                  subsequent segments)
                - One or more segments already exist within the
                  segmentation instance
                - The segmentation is binary and the pixel array contains
                  integer values that belong to segments that are not described
                  in the segment descriptions
                - The segmentation is binary and pixel array has floating point
                  values not equal to 0.0 or 1.0
                - The segmentation is fractional and pixel array has floating
                  point values outside the range 0.0 to 1.0
                - The segmentation is fractional and pixel array has floating
                  point values outside the range 0.0 to 1.0
                - Plane positions are provided but the length of the array
                  does not match the number of frames in the pixel array
        TypeError
            When the dtype of the pixel array is invalid


        Note
        ----
        Segments must be sorted by segment number in ascending order and
        increase by 1.  Additionally, the first segment description must have a
        segment number one greater than the segment number of the last segment
        added to the segmentation, or 1 if this is the first segment added.

        In case `segmentation_type` is ``"BINARY"``, the number of items in
        `segment_descriptions` must be greater than or equal to the number of
        unique positive pixel values in `pixel_array`. It is possible for some
        segments described in `segment_descriptions` not to appear in the
        `pixel_array`. In case `segmentation_type` is ``"FRACTIONAL"``, only
        one segment can be encoded by `pixel_array` and hence only one item is
        permitted in `segment_descriptions`.

        """  # noqa
        if pixel_array.ndim == 2:
            pixel_array = pixel_array[np.newaxis, ...]
        if pixel_array.ndim != 3:
            raise ValueError('Pixel array must be a 2D or 3D array.')

        if pixel_array.shape[1:3] != (self.Rows, self.Columns):
            raise ValueError(
                'Pixel array representing segments has the wrong number of '
                'rows and columns.')

        # Determine the expected starting number of the segments to ensure
        # they will be continuous with existing segments
        if self._segment_inventory:
            # Next segment number is one greater than the largest existing
            # segment number
            seg_num_start = max(self._segment_inventory) + 1
        else:
            # No existing segments so start at 1
            seg_num_start = 1

        # Check segment numbers
        # Check the existing descriptions
        described_segment_numbers = np.array(
            [int(item.SegmentNumber) for item in segment_descriptions])
        # Check segment numbers in the segment descriptions are
        # monotonically increasing by 1
        if not (np.diff(described_segment_numbers) == 1).all():
            raise ValueError(
                'Segment descriptions must be sorted by segment number '
                'and monotonically increasing by 1.')
        if described_segment_numbers[0] != seg_num_start:
            if seg_num_start == 1:
                msg = ('Segment descriptions should be numbered starting '
                       f'from 1. Found {described_segment_numbers[0]}. ')
            else:
                msg = ('Segment descriptions should be numbered to '
                       'continue from existing segments. Expected the first '
                       f'segment to be numbered {seg_num_start} but found '
                       f'{described_segment_numbers[0]}.')
            raise ValueError(msg)

        if pixel_array.dtype in (np.bool_, np.uint8, np.uint16):
            segments_present = np.unique(pixel_array[pixel_array > 0].astype(
                np.uint16))

            # Special case where the mask is binary and there is a single
            # segment description. Mark the positive segment with
            # the correct segment number
            if (np.array_equal(segments_present, np.array([1]))
                    and len(segment_descriptions) == 1):
                pixel_array = pixel_array.astype(np.uint8)
                pixel_array *= described_segment_numbers.item()

            # Otherwise, the pixel values in the pixel array must all belong to
            # a described segment
            else:
                if not np.all(
                        np.in1d(segments_present, described_segment_numbers)):
                    raise ValueError('Pixel array contains segments that lack '
                                     'descriptions.')

        elif (pixel_array.dtype in (np.float_, np.float32, np.float64)):
            unique_values = np.unique(pixel_array)
            if np.min(unique_values) < 0.0 or np.max(unique_values) > 1.0:
                raise ValueError(
                    'Floating point pixel array values must be in the '
                    'range [0, 1].')
            if len(segment_descriptions) != 1:
                raise ValueError(
                    'When providing a float-valued pixel array, provide only '
                    'a single segment description')
            if self.SegmentationType == SegmentationTypeValues.BINARY.value:
                non_boolean_values = np.logical_and(unique_values > 0.0,
                                                    unique_values < 1.0)
                if np.any(non_boolean_values):
                    raise ValueError(
                        'Floating point pixel array values must be either '
                        '0.0 or 1.0 in case of BINARY segmentation type.')
                pixel_array = pixel_array.astype(np.bool_)
        else:
            raise TypeError('Pixel array has an invalid data type.')

        # Check that the new segments do not already exist
        if len(set(described_segment_numbers) & self._segment_inventory) > 0:
            raise ValueError(
                'Segment with given segment number already exists')

        # Set the optional tag value SegmentsOverlapValues to NO to indicate
        # that the segments do not overlap. We can know this for sure if it's
        # the first segment (or set of segments) to be added because they are
        # contained within a single pixel array.
        if len(self._segment_inventory) == 0:
            self.SegmentsOverlap = SegmentsOverlapValues.NO.value
        else:
            # If this is not the first set of segments to be added, we cannot
            # be sure whether there is overlap with the existing segments
            self.SegmentsOverlap = SegmentsOverlapValues.UNDEFINED.value

        src_image = self._source_images[0]
        is_multiframe = hasattr(src_image, 'NumberOfFrames')
        if is_multiframe:
            source_plane_positions = \
                self.DimensionIndexSequence.get_plane_positions_of_image(
                    src_image
                )
        else:
            source_plane_positions = \
                self.DimensionIndexSequence.get_plane_positions_of_series(
                    self._source_images
                )

        if plane_positions is None:
            if pixel_array.shape[0] != len(source_plane_positions):
                if is_multiframe:
                    raise ValueError(
                        'Number of frames in pixel array does not match number '
                        ' of frames in source image.')
                else:
                    raise ValueError(
                        'Number of frames in pixel array does not match number '
                        'of source images.')
            plane_positions = source_plane_positions
        else:
            if pixel_array.shape[0] != len(plane_positions):
                raise ValueError(
                    'Number of pixel array planes does not match number of '
                    'provided plane positions.')

        plane_position_values, plane_sort_index = \
            self.DimensionIndexSequence.get_index_values(plane_positions)

        are_spatial_locations_preserved = (
            all(plane_positions[i] == source_plane_positions[i]
                for i in range(len(plane_positions)))
            and self._plane_orientation == self._source_plane_orientation)

        # Get unique values of attributes in the Plane Position Sequence or
        # Plane Position Slide Sequence, which define the position of the plane
        # with respect to the three dimensional patient or slide coordinate
        # system, respectively. These can subsequently be used to look up the
        # relative position of a plane relative to the indexed dimension.
        dimension_position_values = [
            np.unique(plane_position_values[:, index], axis=0)
            for index in range(plane_position_values.shape[1])
        ]

        # In certain circumstances, we can add new pixels without unpacking the
        # previous ones, which is more efficient. This can be done when using
        # non-encapsulated transfer syntaxes when there is no padding required
        # for each frame to be a multiple of 8 bits.
        framewise_encoding = False
        is_encaps = self.file_meta.TransferSyntaxUID.is_encapsulated
        if not is_encaps:
            if self.SegmentationType == SegmentationTypeValues.FRACTIONAL.value:
                framewise_encoding = True
            elif self.SegmentationType == SegmentationTypeValues.BINARY.value:
                # Framewise encoding can only be used if there is no padding
                # This requires the number of pixels in each frame to be
                # multiple of 8
                if (self.Rows * self.Columns * self.SamplesPerPixel) % 8 == 0:
                    framewise_encoding = True
                else:
                    logger.warning(
                        'pixel data needs to be re-encoded for binary '
                        'bitpacking - consider using FRACTIONAL instead of '
                        'BINARY segmentation type')

        if framewise_encoding:
            # Before adding new pixel data, remove trailing null padding byte
            if len(self.PixelData) == get_expected_length(self) + 1:
                self.PixelData = self.PixelData[:-1]
        else:
            # In the case of encapsulated transfer syntaxes, we will accumulate
            # a list of encoded frames to re-encapsulate at the end
            if is_encaps:
                if hasattr(self, 'PixelData') and len(self.PixelData) > 0:
                    # Undo the encapsulation but not the encoding within each
                    # frame
                    full_frames_list = decode_data_sequence(self.PixelData)
                else:
                    full_frames_list = []
            else:
                if hasattr(self, 'PixelData') and len(self.PixelData) > 0:
                    full_pixel_array = self.pixel_array.flatten()
                else:
                    full_pixel_array = np.array([], np.bool_)

        for i, segment_number in enumerate(described_segment_numbers):
            if pixel_array.dtype in (np.float_, np.float32, np.float64):
                # Floating-point numbers must be mapped to 8-bit integers in
                # the range [0, max_fractional_value].
                planes = np.around(pixel_array *
                                   float(self.MaximumFractionalValue))
                planes = planes.astype(np.uint8)
            elif pixel_array.dtype in (np.uint8, np.uint16):
                # Labeled masks must be converted to binary masks.
                planes = np.zeros(pixel_array.shape, dtype=np.bool_)
                planes[pixel_array == segment_number] = True
            elif pixel_array.dtype == np.bool_:
                planes = pixel_array
            else:
                raise TypeError('Pixel array has an invalid data type.')

            contained_plane_index = []
            for j in plane_sort_index:
                if np.sum(planes[j]) == 0:
                    logger.info('skip empty plane {} of segment #{}'.format(
                        j, segment_number))
                    continue
                contained_plane_index.append(j)
                logger.info('add plane #{} for segment #{}'.format(
                    j, segment_number))

                pffp_item = Dataset()
                frame_content_item = Dataset()
                frame_content_item.DimensionIndexValues = [segment_number]

                # Look up the position of the plane relative to the indexed
                # dimension.
                try:
                    if self._coordinate_system == CoordinateSystemNames.SLIDE:
                        index_values = [
                            np.where((dimension_position_values[idx]
                                      == pos))[0][0] + 1
                            for idx, pos in enumerate(plane_position_values[j])
                        ]
                    else:
                        # In case of the patient coordinate system, the
                        # value of the attribute the Dimension Index Sequence
                        # points to (Image Position Patient) has a value
                        # multiplicity greater than one.
                        index_values = [
                            np.where((dimension_position_values[idx]
                                      == pos).all(axis=1))[0][0] + 1
                            for idx, pos in enumerate(plane_position_values[j])
                        ]
                except IndexError as error:
                    raise IndexError(
                        'Could not determine position of plane #{} in '
                        'three dimensional coordinate system based on '
                        'dimension index values: {}'.format(j, error))
                frame_content_item.DimensionIndexValues.extend(index_values)
                pffp_item.FrameContentSequence = [frame_content_item]
                if self._coordinate_system == CoordinateSystemNames.SLIDE:
                    pffp_item.PlanePositionSlideSequence = plane_positions[j]
                else:
                    pffp_item.PlanePositionSequence = plane_positions[j]

                # Determining the source images that map to the frame is not
                # always trivial. Since DerivationImageSequence is a type 2
                # attribute, we leave its value empty.
                pffp_item.DerivationImageSequence = []

                if are_spatial_locations_preserved:
                    derivation_image_item = Dataset()
                    derivation_code = codes.cid7203.Segmentation
                    derivation_image_item.DerivationCodeSequence = [
                        CodedConcept(derivation_code.value,
                                     derivation_code.scheme_designator,
                                     derivation_code.meaning,
                                     derivation_code.scheme_version),
                    ]

                    derivation_src_img_item = Dataset()
                    if len(plane_sort_index) > len(self._source_images):
                        # A single multi-frame source image
                        src_img_item = self.SourceImageSequence[0]
                        # Frame numbers are one-based
                        derivation_src_img_item.ReferencedFrameNumber = j + 1
                    else:
                        # Multiple single-frame source images
                        src_img_item = self.SourceImageSequence[j]
                    derivation_src_img_item.ReferencedSOPClassUID = \
                        src_img_item.ReferencedSOPClassUID
                    derivation_src_img_item.ReferencedSOPInstanceUID = \
                        src_img_item.ReferencedSOPInstanceUID
                    purpose_code = \
                        codes.cid7202.SourceImageForImageProcessingOperation
                    derivation_src_img_item.PurposeOfReferenceCodeSequence = [
                        CodedConcept(purpose_code.value,
                                     purpose_code.scheme_designator,
                                     purpose_code.meaning,
                                     purpose_code.scheme_version),
                    ]
                    derivation_src_img_item.SpatialLocationsPreserved = 'YES'
                    derivation_image_item.SourceImageSequence = [
                        derivation_src_img_item,
                    ]
                    pffp_item.DerivationImageSequence.append(
                        derivation_image_item)
                else:
                    logger.warning('spatial locations not preserved')

                identification = Dataset()
                identification.ReferencedSegmentNumber = segment_number
                pffp_item.SegmentIdentificationSequence = [
                    identification,
                ]
                self.PerFrameFunctionalGroupsSequence.append(pffp_item)
                self.NumberOfFrames += 1

            if framewise_encoding:
                # Straightforward concatenation of the binary data
                self.PixelData += self._encode_pixels(
                    planes[contained_plane_index])
            else:
                if is_encaps:
                    # Encode this frame and add to the list for encapsulation
                    # at the end
                    for f in contained_plane_index:
                        full_frames_list.append(self._encode_pixels(planes[f]))
                else:
                    # Concatenate the 1D array for re-encoding at the end
                    full_pixel_array = np.concatenate([
                        full_pixel_array,
                        planes[contained_plane_index].flatten()
                    ])

            # In case of a tiled Total Pixel Matrix pixel data for the same
            # segment may be added.
            if segment_number not in self._segment_inventory:
                self.SegmentSequence.append(segment_descriptions[i])
            self._segment_inventory.add(segment_number)

        # Re-encode the whole pixel array at once if necessary
        if not framewise_encoding:
            if is_encaps:
                self.PixelData = encapsulate(full_frames_list)
            else:
                self.PixelData = self._encode_pixels(full_pixel_array)

        # Add back the null trailing byte if required
        if len(self.PixelData) % 2 == 1:
            self.PixelData += b'0'