Skip to content
Snippets Groups Projects
czi_file.cpp 14.97 KiB
#include "czi_file.h"

bool CziFile::is_master_file() const
{
    return ((header.filePart == 0) && (header.fileGuid == header.masterFileGuid));
}

void CziFile::print_segment_header(const SegmentHeader &segmentHeader) const
{
    printf("---------SegmentHeader---------\n");
    printf("%-25s %15s\n", "SID", segmentHeader.sId.c_str());
    printf("%-25s %15li\n", "AllocatedSize", segmentHeader.allocatedSize);
    printf("%-25s %15li\n", "UsedSize", segmentHeader.usedSize);
    printf("-------------------------------\n");
}

const char *CziFile::pixel_type_str(const PixelType px) const
{

    switch (px)
    {
    case PixelType_Gray8:
        return "Gray8";
    case PixelType_Gray16:
        return "Gray16";
    case PixelType_Gray32Float:
        return "Gray32Float";
    case PixelType_Bgr24:
        return "Bgr24";
    case PixelType_Bgr48:
        return "Bgr48";
    case PixelType_Bgr96Float:
        return "Bgr96Float";
    case PixelType_Bgra32:
        return "Bgra32";
    case PixelType_Gray64ComplexFloat:
        return "Gray64ComplexFloat";
    case PixelType_Bgr192ComplexFloat:
        return "Bgr192ComplexFloat";
    case PixelType_Gray32:
        return "Gray32";
    case PixelType_Gray64:
        return "Gray64";
    default:
        always_assert("Bad pixel type." && false);
        break;
    }

    return nullptr;
}

const char *CziFile::pyramid_type_str(const PyramidType pt) const
{
    switch (pt)
    {
    case PyramidType_None:
        return "None";
    case PyramidType_SingleSubBlock:
        return "SingleSubBlock";
    case PyramidType_MultiSubBlock:
        return "MultiSubBlock";
        break;
    default:
    {
        always_assert("Bad pyramid type." && false);
        break;
    }
    }
    return nullptr;
}

const char *CziFile::compression_type_str(const CompressionType ct) const
{
    switch (ct)
    {
    case CompressionType_Uncompressed:
        return "Uncompressed";
    case CompressionType_LZW:
        return "LZW";
    case CompressionType_JpgFile:
        return "JpgFile";
    case CompressionType_JpegXrFile:
        return "JpegXrFile";
    case CompressionType_Camera:
        return "Camera";
    case CompressionType_System:
        return "System";
        break;
    default:
    {
        always_assert("Bad compression type." && false);
        break;
    }
    }
    return nullptr;
}

const char *CziFile::dimension_type_str(const Dimension &d) const
{
    switch (d)
    {
    case Dimension_X:
        return "X (width)";
    case Dimension_Y:
        return "Y (height)";
    case Dimension_C:
        return "C (channels)";
    case Dimension_Z:
        return "Z (Z-slices)";
    case Dimension_T:
        return "T (timestamps)";
    case Dimension_R:
        return "R (rotation)";
    case Dimension_S:
        return "S (scene)";
    case Dimension_I:
        return "I (illumination)";
    case Dimension_B:
        return "B (block index)";
    case Dimension_M:
        return "M (mosaic)";
    case Dimension_H:
        return "H (phase)";
    case Dimension_V:
        return "V (views)";

        break;
    default:
    {
        always_assert("Bad dimension type." && false);
        break;
    }
    }
    return nullptr;
}

const char *CziFile::dimension_char(const Dimension &d) const
{
    std::string result;
    switch (d)
    {
    case Dimension_X:
        return "X";
        break;
    case Dimension_Y:
        return "Y";
        break;
    case Dimension_C:
        return "C";
    case Dimension_Z:
        return "Z";
    case Dimension_T:
        return "T";
    case Dimension_R:
        return "R";
    case Dimension_S:
        return "S";
    case Dimension_I:
        return "I";
    case Dimension_B:
        return "B";
    case Dimension_M:
        return "M";
    case Dimension_H:
        return "H";
    case Dimension_V:
        return "V";
    default:
    {
        always_assert("Bad dimension type." && false);
        break;
    }
    }
    return nullptr;
}

std::string CziFile::dimension_stack_str(const std::vector<DimensionEntryDV1> &dims, bool includeSize) const
{

    std::string result;
    for (const DimensionEntryDV1 &entry : dims)
    {
        result.append(dimension_char(entry.dimension));
        // + std::string(includeSize ? () : ""));
        if (includeSize)
            result.append("(" + std::to_string(entry.size) + ")-");
    }

    if (includeSize)
    {
        result = result.substr(0, result.length() - 1);
    }
    return result;

    // return (includeSize ?: result).c_str();
}

void CziFile::report_verbose() const
{
    report();

    printf("==========SubBlock directory==========\n");
    print_segment_header(subBlockDirectory.header);
    printf("%-25s %15i\n", "EntryCount", subBlockDirectory.entryCount);

    for (size_t entry = 0; entry < subBlockDirectory.entryCount; entry++)
    {
        printf("SubBlockId: %i\tPixelType: %s\tCompression: %s\tDimensionStack: %s\tPyramidType: %s\n",
               (int)entry,
               pixel_type_str(subBlockDirectory.entries[entry].pixelType),
               compression_type_str(subBlockDirectory.entries[entry].compression),
               dimension_stack_str(subBlockDirectory.entries[entry].dimensions, true).c_str(),
               pyramid_type_str(subBlockDirectory.entries[entry].pyramidType));
    }

    printf("======================================\n");
}

std::string byte_array_str(const std::vector<byte> &bytes)
{
    std::string result = "";

    for (size_t i = 0; i < bytes.size(); i++)
    {
        result.append(std::to_string(bytes[i]));
        if (i % 4 == 0 && i != bytes.size() - 1)
            result.append("-");
    }
    return result;
}

void CziFile::report() const
{
    printf("Loaded CZI FILE: %s\n", fileName.c_str());

    printf("==========Summary report==========\n");
    print_segment_header(header.header);
    printf("%-25s %13i.%i\n", "FileVersion", header.fileVersion.major, header.fileVersion.minor);

    printf("%-25s %15i\n", "FilePart", header.filePart);

    printf("%-25s %30s\n", "MasterGUID", byte_array_str(header.masterFileGuid).c_str());
    printf("%-25s %30s\n", "FileGUID", byte_array_str(header.masterFileGuid).c_str());

    //TODO: Replace with GUID print.
    printf("%-25s %15i\n", "GUIDs are matching", vecUtil::vector_eq(header.masterFileGuid, header.fileGuid));
    printf("%-25s %15i\n", "UpdatePending", header.updatePending);
    printf("%-25s %15li\n", "SubblockDirPosition", header.subBlockDirectoryPosition);
    printf("%-25s %15li\n", "MetadataPosition", header.metadataPosition);
    printf("%-25s %15li\n", "AttachmentDirPosition", header.attachmentDirectoryPosition);
    printf("%-25s %15i\n", "SubBlockCount", subBlockDirectory.entryCount);

    printf("%-25s %15i\n", "AttachmentCount", attachmentDirectory.entryCount);

    if (subBlockDirectory.entryCount > 0)
    {
        {
            std::map<PixelType, int> pixelTypeMap;
            std::map<CompressionType, int> compressionMap;
            std::map<std::string, int> dimensionMap;

            for (size_t sbId = 0; sbId < subBlockDirectory.entryCount; sbId++)
            {
                const DirectoryEntryDV entry = subBlockDirectory.entries[sbId];

                if (pixelTypeMap.count(entry.pixelType))
                    pixelTypeMap[entry.pixelType]++;
                else
                    pixelTypeMap.emplace(std::make_pair(entry.pixelType, 1));

                if (compressionMap.count(entry.compression))
                    compressionMap[entry.compression]++;
                else
                    compressionMap.emplace(std::make_pair(entry.compression, 1));

                std::string dimStackStr = dimension_stack_str(entry.dimensions, false);
                if (dimensionMap.count(dimStackStr))
                    dimensionMap[dimStackStr]++;
                else
                    dimensionMap.emplace(std::make_pair(dimStackStr, 1));
            }

            printf("Pixel type distribution:\n");
            for (auto &&ptPair : pixelTypeMap)
            {
                printf("\t%s %ix\n", pixel_type_str(ptPair.first), ptPair.second);
            }
            printf("Compression type distribution:\n");
            for (auto &&ctPair : compressionMap)
            {
                printf("\t%s %ix\n", compression_type_str(ctPair.first), ctPair.second);
            }
            printf("Dimension stack  distribution:\n");
            for (auto &&dimPair : dimensionMap)
            {
                printf("\t%s %ix\n", dimPair.first.c_str(), dimPair.second);
            }
        }

        //printf("%-25s %15i\n", "DimensionCount", subBlockDirectory.entries[0].dimensionCount);
        //printf("%-25s %15s\n", "PixelType", pixel_type_str(subBlockDirectory.entries[0].pixelType));
    }

    printf("==================================\n");
}

ImageMatrix CziFile::get_image(const uint subblockId) const
{
    // // subblockId is in bounds.
    // always_assert((uint)subblockId < subBlockDirectory.entries.size());
    // // Image is not empty.
    // always_assert(entry.width > 0 && entry.height > 0);
    // // Read image bytes.
    // std::vector<byte> imageBytes;
    // {
    //     BinaryFileStream cziStream(fileName);
    //     imageBytes = cziStream.move_and_consume_bytes(entry.subBlock.dataLocation, entry.subBlock.dataSize);
    // }

    auto imageBytes = get_image_data(subblockId, false);
    auto entry = subBlockDirectory.entries[subblockId];

    ImageMatrix image = ImageMatrix((uint)entry.width, (uint)entry.height, entry.pixelType, imageBytes);
    return image;
}

std::vector<byte> CziFile::get_image_data(const uint subblockId, const bool ZCurveOrdered) const
{
    // subblockId is in bounds.
    always_assert((uint)subblockId < subBlockDirectory.entries.size());
    auto entry = subBlockDirectory.entries[subblockId];
    // Image is not empty.
    always_assert(entry.width > 0 && entry.height > 0);

    // Read image bytes.
    std::vector<byte> imageBytes;
    {
        BinaryFileStream cziStream(fileName);
        imageBytes = cziStream.move_and_consume_bytes(entry.subBlock.dataLocation, entry.subBlock.dataSize);
    }
    // Check if read data are correct and not corrupted.
    int bytesPerPixel = get_bytes_per_pixel_type(entry.pixelType);
    ulong expectedSize = entry.width * entry.height * bytesPerPixel;
    always_assert(expectedSize == imageBytes.size());

    if (ZCurveOrdered)
    {
        always_assert((uint)entry.width <= UINT_MAX && (uint)entry.height <= UINT_MAX);
        uint componentCount = get_count_of_components_of_pixel(entry.pixelType);
        uint componentSize = get_component_size_of_pixel(entry.pixelType);

        auto zIndices = generate_ordered_z_order_indices(entry.width, entry.height, componentCount);
        always_assert(zIndices.size() == (ulong)(componentCount * entry.width * entry.height));

        std::vector<byte> zOrderedBytes = reoder_bytes_to_z_order(imageBytes, zIndices, componentSize);

        auto back = reoder_bytes_from_z_order(zOrderedBytes, zIndices, componentSize);
        bool eq = vecUtil::vector_eq(imageBytes, back);
        always_assert(eq);

        return zOrderedBytes;
    }
    else
    {
        return imageBytes;
    }
}

void CziFile::differences_between_next(const std::string baseName) const
{
    if (subBlockDirectory.entries.size() <= 1)
    {
        printf("There aren't 2 different images in this CZI file.\n");
        return;
    }

    std::string fName;
    ImageMatrix ref = get_image(0);
    for (size_t i = 1; i < subBlockDirectory.entries.size(); i++)
    {
        fName = baseName + "diff_" + std::to_string(i - 1) + "_" + std::to_string(i) + ".ppm";

        ImageMatrix next = get_image(i);
        ImageMatrix diff_ref_next = ref.create_difference_matrix(next);

        diff_ref_next.save_as_ppm(fName.c_str());

        ref = next;
    }
}

void CziFile::extract_images(const std::string &baseName) const
{
    std::string fName;
    for (size_t i = 0; i < subBlockDirectory.entries.size(); i++)
    {
        auto subblock = get_image(i);
        fName = baseName + "subblock_" + std::to_string(i) + ".ppm";
        subblock.save_as_ppm(fName.c_str());
    }
}

void CziFile::dump_image_data(const std::string &baseName) const
{
    BinaryFileStream cziStream(fileName);
    int imageIndex = 0;
    for (const DirectoryEntryDV &entry : subBlockDirectory.entries)
    {
        always_assert(entry.width > 0 && entry.height > 0);

        std::string binaryFileName = baseName + std::to_string(imageIndex++) + ".bin";

        auto imageBytes = cziStream.move_and_consume_bytes(entry.subBlock.dataLocation, entry.subBlock.dataSize);

        write_bytes_to_file(imageBytes, binaryFileName.c_str());

        printf("Wrote %s\n", binaryFileName.c_str());
    }
}
void CziFile::test_compression(CompressionMethod method, bool verbose) const
{

    switch (method)
    {
    case CompressionMethod_GZIP:
        printf("Selected compression: GZIP (zlib)\n");
        break;
    case CompressionMethod_LZMA:
        printf("Selected compression: LZMA (2?)\n");
        break;
    case CompressionMethod_BZIP2:
        printf("Selected compression: BZIP2 \n");
        break;
    default:
        INVALID_CASE;
        return;
    }

    CompressionResult overallN;
    CompressionResult overallZ;
    Stopwatch stopwatch;

    //TODO: Use settings.
    CompressionSettings settings;
    for (size_t i = 0; i < subBlockDirectory.entries.size(); i++)
    {
        auto data = get_image_data(i, false);
        auto dataZ = get_image_data(i, true);

        CompressionResult nResult = test_compression_method(data, method, settings, stopwatch);
        CompressionResult zResult = test_compression_method(dataZ, method, settings, stopwatch);

        overallN.compressionRatio += nResult.compressionRatio;
        overallZ.compressionRatio += zResult.compressionRatio;

        overallN.originalSize += data.size();
        overallN.compressedSize += nResult.compressedSize;
        overallZ.compressedSize += zResult.compressedSize;
        overallN.percentageOfOriginalSize += nResult.percentageOfOriginalSize;
        overallZ.percentageOfOriginalSize += zResult.percentageOfOriginalSize;

        if (verbose)
        {
            printf("Subblock %-3i Compression ratios: Normal: %8f Z-Ordered: %8f; Size(N): %5.3f%%; Size(Z): %5.3f%%; Time: %8.1f ms\n",
                   (int)i, nResult.compressionRatio, zResult.compressionRatio, nResult.percentageOfOriginalSize,
                   zResult.percentageOfOriginalSize, stopwatch.last_lap_milliseconds());
        }
    }
    float dataCount = (float)subBlockDirectory.entries.size();

    overallN.divide(dataCount);
    overallZ.divide(dataCount);

    printf("Overall compression ratios: Normal %8f Z-Ordered: %8f\n", overallN.compressionRatio, overallZ.compressionRatio);
    printf("Original size: %8lu B Compressed size: %8lu B Compressed Z-Order size: %8lu B\n", overallN.originalSize, overallN.compressedSize, overallZ.compressedSize);
    printf("Original size (%%): 100%% Compressed: %5.5f%% Compressed Z-Order: %5.5f%%\n", overallN.percentageOfOriginalSize, overallZ.percentageOfOriginalSize);
    printf("Average compression time: %.1f ms\n", stopwatch.average_lap_time_in_milliseconds());
}