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)
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
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)
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]
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
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
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)
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
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