Exemple #1
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
    def test_invalid_frame_data_raises(self):
        """Test that invalid segment data raises exception."""
        ds = dcmread(MR_RLE_1F)
        pixel_data = defragment_data(ds.PixelData)
        # Missing byte
        with pytest.raises(ValueError,
                           match=r'amount \(4095 vs. 4096 bytes\)'):
            _rle_decode_frame(pixel_data[:-1], ds.Rows, ds.Columns,
                              ds.SamplesPerPixel, ds.BitsAllocated)

        # Extra byte
        with pytest.raises(ValueError,
                           match=r'amount \(4097 vs. 4096 bytes\)'):
            _rle_decode_frame(pixel_data + b'\x00\x01', ds.Rows, ds.Columns,
                              ds.SamplesPerPixel, ds.BitsAllocated)
Exemple #3
0
 def test_defragment(self):
     """Test joining fragmented data works"""
     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'
     reference = b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
     assert defragment_data(bytestream) == reference
Exemple #4
0
 def test_defragment(self):
     """Test joining fragmented data works"""
     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'
     reference = b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
     assert defragment_data(bytestream) == reference
 def test_nonconf_segment_padding_warns(self):
     """Test non-conformant segment padding warns"""
     ds = dcmread(RLE_16_1_1F)
     pixel_data = defragment_data(ds.PixelData)
     msg = (
         r"The decoded RLE segment contains non-conformant padding - 4097 "
         r"vs. 4096 bytes expected"
     )
     with pytest.warns(UserWarning, match=msg):
         frame = _rle_decode_frame(
             pixel_data + b'\x00\x01',
             4096,
             1,
             ds.SamplesPerPixel,
             ds.BitsAllocated
         )
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)
    def test_one_row(self):
        """Test encoding data that contains only a single row."""
        ds = dcmread(RLE_8_1_1F)
        pixel_data = defragment_data(ds.PixelData)
        decoded = _rle_decode_segment(pixel_data[64:])
        assert ds.Rows * ds.Columns == len(decoded)
        arr = np.frombuffer(decoded, 'uint8').reshape(ds.Rows, ds.Columns)

        # Re-encode a single row of the decoded data
        row = arr[0]
        assert (ds.Columns,) == row.shape
        encoded = _encode_segment(
            row.tobytes(), columns=ds.Columns, rows=ds.Rows
        )

        # Decode the re-encoded data and check that it's the same
        redecoded = _rle_decode_segment(encoded)
        assert ds.Columns == len(redecoded)
        assert decoded[:ds.Columns] == redecoded
Exemple #8
0
    def test_invalid_frame_data_raises(self):
        """Test that invalid segment data raises exception."""
        ds = dcmread(MR_RLE_1F)
        pixel_data = defragment_data(ds.PixelData)
        # Missing byte
        # This should probably be ValueError
        with pytest.raises(AttributeError, match='Different number of bytes'):
            _rle_decode_frame(pixel_data[:-1],
                              ds.Rows,
                              ds.Columns,
                              ds.SamplesPerPixel,
                              ds.BitsAllocated)

        # Extra byte
        with pytest.raises(AttributeError, match='Different number of bytes'):
            _rle_decode_frame(pixel_data + b'\x00\x01',
                              ds.Rows,
                              ds.Columns,
                              ds.SamplesPerPixel,
                              ds.BitsAllocated)
Exemple #9
0
    def test_pixel_rep_mismatch(self):
        """Test mismatched j2k sign and Pixel Representation."""
        ds = dcmread(J2KR_16_13_1_1_1F_M2_MISMATCH)
        assert 1 == ds.PixelRepresentation
        assert 13 == ds.BitsStored

        bs = defragment_data(ds.PixelData)
        params = get_j2k_parameters(bs)
        assert 13 == params["precision"]
        assert not params["is_signed"]
        arr = ds.pixel_array

        assert 'int16' == arr.dtype
        assert (512, 512) == arr.shape
        assert arr.flags.writeable

        assert -2000 == arr[0, 0]
        assert [621, 412, 138, -193, -520, -767, -907, -966, -988,
                -995] == (arr[47:57, 279].tolist())
        assert [-377, -121, 141, 383, 633, 910, 1198, 1455, 1638,
                1732] == (arr[328:338, 106].tolist())
    def test_properties(self, fpath, data):
        """Test dataset and pixel array properties are as expected."""
        if data[0] not in JPEG2K_SUPPORTED_SYNTAXES:
            return

        ds = dcmread(fpath)
        assert ds.file_meta.TransferSyntaxUID == data[0]
        assert ds.BitsAllocated == data[1]
        assert ds.SamplesPerPixel == data[2]
        assert ds.PixelRepresentation == data[3]
        assert getattr(ds, 'NumberOfFrames', 1) == data[4]

        bs = defragment_data(ds.PixelData)
        if _get_j2k_precision(bs) != ds.BitsStored:
            with pytest.warns(UserWarning, match=r"doesn't match the sample"):
                arr = ds.pixel_array
        else:
            arr = ds.pixel_array

        assert arr.flags.writeable
        assert data[5] == arr.shape
        assert arr.dtype == data[6]
Exemple #11
0
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
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
Exemple #13
0
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
Exemple #14
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)
Exemple #15
0
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
Exemple #16
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