imaris / ImarisWriter

Apache License 2.0
31 stars 14 forks source link

Fail to convert image if block size is smaller than image size on windows platform. #12

Open Dr-QTDS opened 2 months ago

Dr-QTDS commented 2 months ago

I am using Windows 10 to convert images. I have tested the testPy/PyImarisWriterExample.py code and it ran correctly. I edited the code as below:

import PyImarisWriter as PW
import numpy as np

from datetime import datetime

class TestConfiguration:

    def __init__(self, id, title, np_type, imaris_type, color_table):
        self.mId = id
        self.mTitle = title
        self.mNp_type = np_type
        self.mImaris_type = imaris_type
        self.mColor_table = color_table

def get_test_configurations():
    configurations = []

    configurations.append(TestConfiguration(len(configurations), 'uint8 image from uint8 numpy array', np.uint8, 'uint8',
                                            [PW.Color(0.086, 0.608, 0.384, 1), PW.Color(1, 1, 1, 1), PW.Color(1, 0.533, 0.243, 1)]))

    return configurations

class MyCallbackClass(PW.CallbackClass):
    def __init__(self):
        self.mUserDataProgress = 0

    def RecordProgress(self, progress, total_bytes_written):
        progress100 = int(progress * 100)
        if progress100 - self.mUserDataProgress >= 5:
            self.mUserDataProgress = progress100
            print('User Progress {}, Bytes written: {}'.format(self.mUserDataProgress, total_bytes_written))

def run(configuration):
    image_size = PW.ImageSize(x=600, y=400, z=1, c=1, t=1)
    dimension_sequence = PW.DimensionSequence('x', 'y', 'z', 'c', 't')
    block_size = PW.ImageSize(x=300, y=400, z=1, c=1, t=1)
    sample_size = PW.ImageSize(x=1, y=1, z=1, c=1, t=1)
    output_filename = f'PyImarisWriterNumpyExample{configuration.mId}.ims'

    options = PW.Options()
    options.mNumberOfThreads = 12
    options.mCompressionAlgorithmType = PW.eCompressionAlgorithmGzipLevel2
    options.mEnableLogProgress = True

    np_data = np.zeros((image_size.y, image_size.x), dtype=configuration.mNp_type)
    np_data[:, 0:100] = 50
    np_data[:, 200:300] = 150
    np_data[:, 400:500] = 250

    application_name = 'PyImarisWriter'
    application_version = '1.0.0'

    callback_class = MyCallbackClass()
    converter = PW.ImageConverter(configuration.mImaris_type, image_size, sample_size, dimension_sequence, block_size,
                                  output_filename, options, application_name, application_version, callback_class)

    num_blocks = image_size / block_size

    block_index = PW.ImageSize()
    for c in range(num_blocks.c):
        block_index.c = c
        for t in range(num_blocks.t):
            block_index.t = t
            for z in range(num_blocks.z):
                block_index.z = z
                for y in range(num_blocks.y):
                    block_index.y = y
                    for x in range(num_blocks.x):
                        block_index.x = x
                        if converter.NeedCopyBlock(block_index):
                            if x == 0:
                                converter.CopyBlock(np_data[:, 0:300], block_index)
                            else:
                                converter.CopyBlock(np_data[:, 300:], block_index)

    adjust_color_range = False
    image_extents = PW.ImageExtents(0, 0, 0, image_size.x, image_size.y, image_size.z)
    parameters = PW.Parameters()
    # parameters.set_value('Image', 'ImageSizeInMB', 2400)
    parameters.set_value('Image', 'Info', configuration.mTitle)
    parameters.set_channel_name(0, 'My Channel 1')
    time_infos = [datetime.today()]
    color_infos = [PW.ColorInfo() for _ in range(image_size.c)]
    # color_infos[0].set_color_table(configuration.mColor_table)

    converter.Finish(image_extents, parameters, time_infos, color_infos, adjust_color_range)

    converter.Destroy()
    print('Wrote {} to {}'.format(configuration.mTitle, output_filename))

def main():
    configurations = get_test_configurations()
    for test_config in configurations:
            run(test_config)

if __name__ == "__main__":

    main() 

I edited the image_size in line 57, block_size in line 59, changed values between lines 69 and 71, and edited lines between 94 and 97 to test how to paste a small image to a big canvas. Though the code can run successfully, the output image is incorrect. The output image looks like the below picture.

微信截图_20240921150755

But if I edit the configurations.append(TestConfiguration(len(configurations), 'uint8 image from uint8 numpy array', np.uint8, 'uint8', [PW.Color(0.086, 0.608, 0.384, 1), PW.Color(1, 1, 1, 1), PW.Color(1, 0.533, 0.243, 1)])) to configurations.append(TestConfiguration(len(configurations), 'uint8 image from uint8 numpy array', np.uint8, 'uint16', [PW.Color(0.086, 0.608, 0.384, 1), PW.Color(1, 1, 1, 1), PW.Color(1, 0.533, 0.243, 1)])), I just changed 'uint8' to 'uint16', the output image shows correctly as below.

1726902711731

But as in the above code, the dtype of the NumPy array is uint8. So is this a bug or is something wrong in my code. Please help me, thanks!

imaris commented 1 month ago

Hello, the problem seems to be happening because of the slicing (np_data[:, 0:300]), which apparently gets lost when passed through the library. In fact, calling converter.CopyBlock(np_data, block_index) for both blocks produces the same result. While this looks like a bug to me as well, allocating arrays larger than a block sort of defeats its purpose. The following produces the expected result (note the block_size instead of image_size):

    np_data = np.zeros((block_size.y, block_size.x), dtype=configuration.mNp_type)
    [...]
                            if x == 0:
                                np_data[:, 0:100] = 50
                                np_data[:, 100:200] = 0
                                np_data[:, 200:300] = 150
                            else:
                                np_data[:, 0:100] = 0
                                np_data[:, 100:200] = 250
                                np_data[:, 200:300] = 0
                            converter.CopyBlock(np_data, block_index)

Mismatching the declared types produces the expected pattern because a non-sliced numpy array of the file type is created by copying the given numpy sliced array data (the library makes sure that an array of the right type is passed to the library, even if creating a temporary copy might be expensive in terms of time and memory).

Hope this helps.