nroduit / weasis-dicom-tools

Basic dicom API
Other
33 stars 33 forks source link

in JPEG2000, CompressionRatioFactor is different in the image #29

Open sylvain-rouquette opened 3 months ago

sylvain-rouquette commented 3 months ago

problem:

result: default value for CompressionRatioFactor is 10 LossyImageCompressionRatio is 13.332180546726

expected: LossyImageCompressionRatio should be 10

nroduit commented 2 months ago

The ratio is recalculated from the result, see this code.

I guess the result differs in openJPEG due to the wavelet decomposition and an approximation to obtain a certain number of bytes, see the documentation https://github.com/uclouvain/openjpeg/wiki/DocJ2KCodec

sylvain-rouquette commented 2 months ago

The problem lies in how you compute the compressed length:

int compressedLength = buf.width() * buf.height() * (int) buf.elemSize();

You seem to assume that the size of the buffer didn't change, and that the size of each element changed, when in fact it's both. You should compute the ratio based on the size of the buffer in bytes.

def in_place_compress_file_data_set(ds: FileDataset,
                                    c_ratio: float = 14,
                                    buff_size: int = int(3e5)) -> None:
    pixel_array = ds.pixel_array
    # Compression
    mem_buffer = np.zeros(buff_size).astype(np.uint8)
    comp = MemJ2K(mem_buffer, data=pixel_array, c_ratio=c_ratio)
    compressed_pixel_data = mem_buffer[:comp.mem_pos].tobytes()
    original_size = pixel_array.nbytes
    compressed_size = len(compressed_pixel_data)
    compression_ratio = original_size / compressed_size
    # -------------------------
    ds.PixelData = pydicom.encaps.encapsulate([compressed_pixel_data])
    ds.file_meta.TransferSyntaxUID = pydicom.uid.JPEG2000
    ds.add_new((0x0028, 0x2110), 'CS', '01')
    ds.add_new((0x0028, 0x2112), 'DS', str(np.float16(compression_ratio)))
    ds.add_new((0x0028, 0x2114), 'CS', "ISO_15444_1")
    ds.SOPInstanceUID = pydicom.uid.generate_uid()
    ds.is_implicit_VR = False
    ds.is_little_endian = True

comp.mem_pos is the size of the compressed buffer.

nroduit commented 2 months ago

I'm not sure I follow the reasoning. The compression ratio is defined by compressedLength, which are the compressed bytes, and uncompressed, which are the bytes of the raw image.

int compressedLength = buf.width() * buf.height() * (int) buf.elemSize(); pixel numbers => buf.width() * buf.height() buf.elemSize() => number channels multiply by the size of pixel (1 for 8 bits, 2 for 16 bits)

For compressedLength, the Mat object is used only to store the compressed bytes. It is not an image, buf.elemSize() is always egual to 1.

nroduit commented 2 months ago

If you think something is wrong with the current code, please publish a PR.