diff --git a/AppModel/include/app_api.h b/AppModel/include/app_api.h index 349c734..981f18c 100644 --- a/AppModel/include/app_api.h +++ b/AppModel/include/app_api.h @@ -9,10 +9,49 @@ extern "C" { typedef void* Parameter; +enum AppExtensionClassId +{ + AppExtensionClassId_Unknown, + AppExtensionClassId_Uint8, + AppExtensionClassId_Int8, + AppExtensionClassId_Uint16, + AppExtensionClassId_Int16, + AppExtensionClassId_Uint32, + AppExtensionClassId_Int32, + AppExtensionClassId_Uint64, + AppExtensionClassId_Int64, + AppExtensionClassId_Double, + AppExtensionClassId_Single, +}; + struct IAppExtensionFunctions { + bool (*pfn_IsNanOrInfDouble)(double d); + + bool (*pfn_IsNanOrInfSingle)(float f); + void* (*pfn_GetData)(const Parameter* parameter); + uint8_t* (*pfn_GetUint8s)(const Parameter* parameter); + + int8_t* (*pfn_GetInt8s)(const Parameter* parameter); + + uint16_t* (*pfn_GetUint16s)(const Parameter* parameter); + + int16_t* (*pfn_GetInt16s)(const Parameter* parameter); + + uint32_t* (*pfn_GetUint32s)(const Parameter* parameter); + + int32_t* (*pfn_GetInt32s)(const Parameter* parameter); + + uint64_t* (*pfn_GetUint64s)(const Parameter* parameter); + + int64_t* (*pfn_GetInt64s)(const Parameter* parameter); + + double* (*pfn_GetDoubles)(const Parameter* parameter); + + float* (*pfn_GetSingles)(const Parameter* parameter); + bool (*pfn_IsNumeric)(const Parameter* parameter); bool (*pfn_IsChar)(const Parameter* parameter); @@ -34,6 +73,20 @@ struct IAppExtensionFunctions char* (*pfn_ConvertToUTF8String)(const Parameter* parameter); void (*pfn_Free)(void* ptr); + + Parameter* (*pfn_CreateNumericMatrixReal)(size_t m, size_t n, enum AppExtensionClassId class_id); + + enum AppExtensionClassId (*pfn_GetClassId)(const Parameter* parameter); + + size_t (*pfn_GetNumberOfElements)(const Parameter* parameter); + + size_t(*pfn_GetNumberOfDimensions)(const Parameter* parameter); + + void (*pfn_GetSizeOfDimensions)(const Parameter* parameter, size_t number_of_dimension, size_t* sizes); + + Parameter* (*pfn_GetField)(const Parameter* parameter, const char* field_name); + + Parameter* (*pfn_CreateNumericArrayReal)(size_t ndim, const size_t* dims, enum AppExtensionClassId class_id); //* }; #ifdef __cplusplus diff --git a/MatlabMex/app_api_implementation.c b/MatlabMex/app_api_implementation.c index 1d033f3..18ea5a5 100644 --- a/MatlabMex/app_api_implementation.c +++ b/MatlabMex/app_api_implementation.c @@ -2,11 +2,71 @@ #include #include +bool matlabMexIsNanOrInfDouble(double value) +{ + return mxIsNaN(value) || mxIsInf(value); +} + +bool matlabMexIsNanOrInfSingle(float value) +{ + return mxIsNaN(value) || mxIsInf(value); +} + void* matlabMexGetData(const Parameter* parameter) { return mxGetData((const mxArray*)parameter); } +uint8_t* matlabMexGetUint8s(const Parameter* parameter) +{ + return mxGetUint8s((const mxArray*)parameter); +} + +int8_t* matlabMexGetInt8s(const Parameter* parameter) +{ + return mxGetInt8s((const mxArray*)parameter); +} + +uint16_t* matlabMexGetUint16s(const Parameter* parameter) +{ + return mxGetUint16s((const mxArray*)parameter); +} + +int16_t* matlabMexGetInt16s(const Parameter* parameter) +{ + return mxGetInt16s((const mxArray*)parameter); +} + +uint32_t* matlabMexGetUint32s(const Parameter* parameter) +{ + return mxGetUint32s((const mxArray*)parameter); +} + +int32_t* matlabMexGetInt32s(const Parameter* parameter) +{ + return mxGetInt32s((const mxArray*)parameter); +} + +uint64_t* matlabMexGetUint64s(const Parameter* parameter) +{ + return mxGetUint64s((const mxArray*)parameter); +} + +int64_t* matlabMexGetInt64s(const Parameter* parameter) +{ + return mxGetInt64s((const mxArray*)parameter); +} + +double* matlabMexGetDoubles(const Parameter* parameter) +{ + return mxGetDoubles((const mxArray*)parameter); +} + +float* matlabMexGetSingles(const Parameter* parameter) +{ + return mxGetSingles((const mxArray*)parameter); +} + bool matlabMexIsNumeric(const Parameter* parameter) { return mxIsNumeric((const mxArray*)parameter); @@ -66,9 +126,166 @@ void matlabFree(void* ptr) mxFree(ptr); } +Parameter* matlabCreateNumericMatrixReal(size_t m, size_t n, enum AppExtensionClassId class_id) +{ + mxClassID mx_class_id; + switch (class_id) + { + case AppExtensionClassId_Uint8: + mx_class_id = mxUINT8_CLASS; + break; + case AppExtensionClassId_Int8: + mx_class_id = mxINT8_CLASS; + break; + case AppExtensionClassId_Uint16: + mx_class_id = mxUINT16_CLASS; + break; + case AppExtensionClassId_Int16: + mx_class_id = mxINT16_CLASS; + break; + case AppExtensionClassId_Uint32: + mx_class_id = mxUINT32_CLASS; + break; + case AppExtensionClassId_Int32: + mx_class_id = mxINT32_CLASS; + break; + case AppExtensionClassId_Uint64: + mx_class_id = mxUINT64_CLASS; + break; + case AppExtensionClassId_Int64: + mx_class_id = mxINT64_CLASS; + break; + case AppExtensionClassId_Double: + mx_class_id = mxDOUBLE_CLASS; + break; + case AppExtensionClassId_Single: + mx_class_id = mxSINGLE_CLASS; + break; + default: + mx_class_id = mxUNKNOWN_CLASS; + break; + } + + return (Parameter*)mxCreateNumericMatrix(m, n, mx_class_id, mxREAL); +} + +enum AppExtensionClassId matlabGetClassId(const Parameter* parameter) +{ + mxClassID mx_class_id = mxGetClassID((const mxArray*)parameter); + switch (mx_class_id) + { + case mxUINT8_CLASS: + return AppExtensionClassId_Uint8; + case mxINT8_CLASS: + return AppExtensionClassId_Int8; + case mxUINT16_CLASS: + return AppExtensionClassId_Uint16; + case mxINT16_CLASS: + return AppExtensionClassId_Int16; + case mxUINT32_CLASS: + return AppExtensionClassId_Uint32; + case mxINT32_CLASS: + return AppExtensionClassId_Int32; + case mxUINT64_CLASS: + return AppExtensionClassId_Uint64; + case mxINT64_CLASS: + return AppExtensionClassId_Int64; + case mxDOUBLE_CLASS: + return AppExtensionClassId_Double; + case mxSINGLE_CLASS: + return AppExtensionClassId_Single; + default: + return AppExtensionClassId_Unknown; + } +} + +size_t matlabGetNumberOfElements(const Parameter* parameter) +{ + return mxGetNumberOfElements((const mxArray*)parameter); +} + +size_t matlabGetNumberOfDimensions(const Parameter* parameter) +{ + return mxGetNumberOfDimensions((const mxArray*)parameter); +} + +void matlabGetSizeOfDimensions(const Parameter* parameter, size_t number_of_dimension, size_t* sizes) +{ + const size_t* ptr_sizes = mxGetDimensions((const mxArray*)parameter); + for (size_t i = 0; i < number_of_dimension; ++i) + { + sizes[i] = ptr_sizes[i]; + } +} + +Parameter* matlabGetField(const Parameter* parameter, const char* field_name) +{ + if (!matlabMexIsStruct(parameter)) + { + return NULL; + } + + return (Parameter*)mxGetField((const mxArray*)parameter, 0, field_name); +} + +Parameter* matlabCreateNumericArrayReal(size_t ndim, const size_t* dims, enum AppExtensionClassId class_id) +{ + mxClassID mx_class_id; + switch (class_id) + { + case AppExtensionClassId_Uint8: + mx_class_id = mxUINT8_CLASS; + break; + case AppExtensionClassId_Int8: + mx_class_id = mxINT8_CLASS; + break; + case AppExtensionClassId_Uint16: + mx_class_id = mxUINT16_CLASS; + break; + case AppExtensionClassId_Int16: + mx_class_id = mxINT16_CLASS; + break; + case AppExtensionClassId_Uint32: + mx_class_id = mxUINT32_CLASS; + break; + case AppExtensionClassId_Int32: + mx_class_id = mxINT32_CLASS; + break; + case AppExtensionClassId_Uint64: + mx_class_id = mxUINT64_CLASS; + break; + case AppExtensionClassId_Int64: + mx_class_id = mxINT64_CLASS; + break; + case AppExtensionClassId_Double: + mx_class_id = mxDOUBLE_CLASS; + break; + case AppExtensionClassId_Single: + mx_class_id = mxSINGLE_CLASS; + break; + default: + mx_class_id = mxUNKNOWN_CLASS; + break; + } + + return (Parameter*)mxCreateNumericArray(ndim, dims, mx_class_id, mxREAL); +} + struct IAppExtensionFunctions g_appExtensionFunctions = { + .pfn_IsNanOrInfDouble = matlabMexIsNanOrInfDouble, + .pfn_IsNanOrInfSingle = matlabMexIsNanOrInfSingle, .pfn_GetData = matlabMexGetData, + .pfn_GetUint8s = matlabMexGetUint8s, + .pfn_GetInt8s = matlabMexGetInt8s, + .pfn_GetUint16s = matlabMexGetUint16s, + .pfn_GetInt16s = matlabMexGetInt16s, + .pfn_GetUint32s = matlabMexGetUint32s, + .pfn_GetInt32s = matlabMexGetInt32s, + .pfn_GetUint64s = matlabMexGetUint64s, + .pfn_GetInt64s = matlabMexGetInt64s, + .pfn_GetDoubles = matlabMexGetDoubles, + .pfn_GetSingles = matlabMexGetSingles, .pfn_IsNumeric = matlabMexIsNumeric, .pfn_IsChar = matlabMexIsChar, .pfn_IsSparse = matlabMexIsSparse, @@ -79,5 +296,12 @@ struct IAppExtensionFunctions g_appExtensionFunctions = .pfn_CreateStructArray = matlabCreateStructArray, .pfn_SetFieldByNumber = matlabSetFieldByNumber, .pfn_ConvertToUTF8String = matlabConvertToUTF8String, - .pfn_Free = matlabFree + .pfn_Free = matlabFree, + .pfn_CreateNumericMatrixReal = matlabCreateNumericMatrixReal, + .pfn_GetClassId = matlabGetClassId, + .pfn_GetNumberOfElements = matlabGetNumberOfElements, + .pfn_GetNumberOfDimensions = matlabGetNumberOfDimensions, + .pfn_GetSizeOfDimensions = matlabGetSizeOfDimensions, + .pfn_GetField = matlabGetField, + .pfn_CreateNumericArrayReal = matlabCreateNumericArrayReal }; \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 326a9fd..a2074d6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -10,7 +10,40 @@ set (libSourceFiles "src/implementation/inc_libczi.h" "src/implementation/libraryinfo.h" "src/implementation/libraryinfo.cpp" -) + "src/implementation/CziReaderManager.h" + "src/implementation/CziReaderManager.cpp" + "src/implementation/CziReader.h" + "src/implementation/CziReader.cpp" + "src/implementation/dbgprint.h" + "src/implementation/dbgprint.cpp" + "src/implementation/CziReaderSbBlkStore.cpp" + "src/implementation/CziReaderSbBlkStore.h" + "src/implementation/CziUtilities.cpp" + "src/implementation/CziUtilities.h" + "src/implementation/include_rapidjson.h" + "src/implementation/argsutils.h" + "src/implementation/argsutils.cpp" + "src/implementation/CziWriter.cpp" "src/implementation/CziWriter.h") + +if(CMAKE_BUILD_TYPE MATCHES Debug) + set(lib_ENABLE_LOGGING 1) +else() + set(lib_ENABLE_LOGGING 0) +endif() + +set(lib_LOGLEVEL 1) + + +set(lib_VERSION_MAJOR ${MEXCZI_MAJOR}) +set(lib_VERSION_MINOR ${MEXCZI_MINOR}) +set(lib_VERSION_PATCH ${MEXCZI_PATCH}) +set(lib_VERSION_EXT ${MEXCZI_EXT}) + +configure_file ( + "${CMAKE_CURRENT_SOURCE_DIR}/lib_config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/lib_config.h" + ESCAPE_QUOTES @ONLY) + add_library (lib @@ -18,5 +51,10 @@ add_library (lib set_target_properties(lib PROPERTIES CXX_STANDARD 17) target_include_directories(lib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../AppModel/include") -target_include_directories(lib PRIVATE ${LIBCZI_INCLUDE_DIR}) -target_link_libraries(lib PRIVATE libCZIStatic JxrDecodeStatic) \ No newline at end of file +target_include_directories(lib PRIVATE ${LIBCZI_INCLUDE_DIR} ${RAPIDJSON_INCLUDE_DIR}) +target_link_libraries(lib PRIVATE libCZIStatic JxrDecodeStatic) +target_include_directories(lib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # this is necessary so that we can find "lib_config.h" which we created above + +if(WIN32) + target_compile_definitions(lib PRIVATE _WIN32API=1) +endif() \ No newline at end of file diff --git a/lib/lib_config.h.in b/lib/lib_config.h.in new file mode 100644 index 0000000..5c5bcad --- /dev/null +++ b/lib/lib_config.h.in @@ -0,0 +1,13 @@ +#pragma once + +#define LIB_ENABLELOGGING @lib_ENABLE_LOGGING@ + +#define LIB_LOGLEVEL @lib_LOGLEVEL@ + +#define LIB_VERSION_MAJOR @lib_VERSION_MAJOR@ + +#define LIB_VERSION_MINOR @lib_VERSION_MINOR@ + +#define LIB_VERSION_PATCH @lib_VERSION_PATCH@ + +#define LIB_VERSION_EXT "@lib_VERSION_EXT@" diff --git a/lib/src/implementation/CziReader.cpp b/lib/src/implementation/CziReader.cpp new file mode 100644 index 0000000..dfceffd --- /dev/null +++ b/lib/src/implementation/CziReader.cpp @@ -0,0 +1,703 @@ +#include "CziReader.h" +#include "inc_libczi.h" +#include +#include + +#include "CziUtilities.h" +//#include "mexapi.h" +#include +#include "utils.h" + +using namespace std; +using namespace libCZI; + +void CziReader::Open(const std::string& utf8_filename) +{ + wstring wstr = ::Utils::convertUtf8ToWchar_t(utf8_filename); + auto stream = libCZI::CreateStreamFromFile(wstr.c_str()); + this->reader->Open(stream); +} + +std::array CziReader::GetScaling() +{ + const libCZI::ScalingInfo& scaling = this->GetScalingInfoFromCzi(); + return std::array + { + scaling.IsScaleXValid() ? scaling.scaleX : -1, + scaling.IsScaleYValid() ? scaling.scaleY : -1, + scaling.IsScaleZValid() ? scaling.scaleZ : -1 + }; +} + +Parameter* CziReader::GetScalingAsMatlabStruct(IAppExtensionFunctions* app_functions) +{ + static const char* fieldNames[] = { "scaleX", "scaleY", "scaleZ" }; + + static const size_t dims[2] = { 1, 1 }; + //auto mexApi = MexApi::GetInstance(); + //auto* s = mexApi.MxCreateStructArray( + auto* s = app_functions->pfn_CreateStructArray( + 2, + dims, + sizeof(fieldNames) / sizeof(fieldNames[0]), + fieldNames); + const libCZI::ScalingInfo& scaling = this->GetScalingInfoFromCzi(); + //mexApi.MxSetFieldByNumber(s, 0, 0, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleXValid() ? scaling.scaleX : numeric_limits::quiet_NaN())); + //mexApi.MxSetFieldByNumber(s, 0, 1, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleYValid() ? scaling.scaleY : numeric_limits::quiet_NaN())); + //mexApi.MxSetFieldByNumber(s, 0, 2, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleZValid() ? scaling.scaleZ : numeric_limits::quiet_NaN())); + + app_functions->pfn_SetFieldByNumber(s, 0, 0, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleXValid() ? scaling.scaleX : numeric_limits::quiet_NaN())); + app_functions->pfn_SetFieldByNumber(s, 0, 1, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleYValid() ? scaling.scaleY : numeric_limits::quiet_NaN())); + app_functions->pfn_SetFieldByNumber(s, 0, 2, MexUtils::DoubleTo1x1Matrix(scaling.IsScaleZValid() ? scaling.scaleZ : numeric_limits::quiet_NaN())); + return s; +} + +const libCZI::ScalingInfo& CziReader::GetScalingInfoFromCzi() +{ + this->InitializeInfoFromCzi(); + return this->scalingInfoFromCzi; +} + +void CziReader::InitializeInfoFromCzi() +{ + std::call_once( + this->flagInfoFromCziMetadata, + [this]() + { + auto mds = this->reader->ReadMetadataSegment(); + auto md = mds->CreateMetaFromMetadataSegment(); + const auto docInfo = md->GetDocumentInfo(); + this->displaySettingsFromCzi = docInfo->GetDisplaySettings(); + this->scalingInfoFromCzi = docInfo->GetScalingInfoEx(); + }); +} + +Parameter* CziReader::GetInfo(IAppExtensionFunctions* app_functions) +{ + auto statistics = this->reader->GetStatistics(); + + static const char* fieldNames[] = { "subblockcount", "boundingBox", "boundingBoxLayer0", "dimBounds", "sceneBoundingBoxes", "minMindex", "maxMindex" }; + + //auto mexApi = MexApi::GetInstance(); + auto s = app_functions->pfn_CreateStructArray( + 2, + MexUtils::Dims_1_by_1, + (sizeof(fieldNames) / sizeof(fieldNames[0])) - (statistics.IsMIndexValid() ? 0 : 2), + fieldNames); + + app_functions->pfn_SetFieldByNumber(s, 0, 0, MexUtils::Int32To1x1Matrix(statistics.subBlockCount)); + app_functions->pfn_SetFieldByNumber(s, 0, 1, CziReader::ConvertToMatlabStruct(statistics.boundingBox)); + app_functions->pfn_SetFieldByNumber(s, 0, 2, CziReader::ConvertToMatlabStruct(statistics.boundingBoxLayer0Only)); + app_functions->pfn_SetFieldByNumber(s, 0, 3, CziReader::ConvertToMatlabStruct(&statistics.dimBounds)); + app_functions->pfn_SetFieldByNumber(s, 0, 4, CziReader::ConvertToMatlabStruct(statistics.sceneBoundingBoxes)); + + if (statistics.IsMIndexValid()) + { + app_functions->pfn_SetFieldByNumber(s, 0, 5, MexUtils::Int32To1x1Matrix(statistics.minMindex)); + app_functions->pfn_SetFieldByNumber(s, 0, 6, MexUtils::Int32To1x1Matrix(statistics.maxMindex)); + } + + return s; +} + +std::string CziReader::GetMetadataXml() +{ + auto mds = this->reader->ReadMetadataSegment(); + auto m = mds->CreateMetaFromMetadataSegment(); + if (!m->IsXmlValid()) + { + throw runtime_error("Metadata-XML found to be invalid."); + } + + return m->GetXml(); +} + +Parameter* CziReader::GetMetadataXmlAsMxArray(IAppExtensionFunctions* app_functions) +{ + //auto s = MexApi::GetInstance().MxCreateString(this->GetMetadataXml().c_str()); + auto s = app_functions->pfn_CreateString(this->GetMetadataXml().c_str()); + return s; +} + +Parameter* CziReader::GetDefaultDisplaySettingsAsMxArray(IAppExtensionFunctions* app_functions) +{ + const auto displaySettings = this->GetDisplaySettingsFromCzi(); + return CziReader::ConvertToMatlabStruct(*displaySettings, app_functions); +} + +Parameter* CziReader::GetSubBlockImage(int sbBlkNo) +{ + auto sbBlk = this->reader->ReadSubBlock(sbBlkNo); + if (!sbBlk) + { + std::stringstream ss; + ss << "SubBlock for id=" << sbBlkNo << " was not found."; + throw invalid_argument(ss.str()); + } + + auto bm = sbBlk->CreateBitmap(); + return ConvertToMxArray(bm.get()); +} + +Parameter* CziReader::GetMultiChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const char* displaySettingsJson, IAppExtensionFunctions* app_functions) +{ + if (displaySettingsJson == nullptr || *displaySettingsJson == '\0') + { + return CziReader::GetMultiChannelScalingTileComposite(roi, planeCoordinate, zoom, this->GetDisplaySettingsFromCzi().get(), app_functions); + } + + ChannelDisplaySettingsInfo dsinfo = CziUtilities::ParseDisplaySettings(displaySettingsJson); + + if (dsinfo.isToBeMerged == true) + { + const auto displaySettingsFromCzi = this->GetDisplaySettingsFromCzi(); + const auto combinedDisplaySettings = CziUtilities::CombineDisplaySettings(displaySettingsFromCzi.get(), dsinfo.displaySettings); + + return CziReader::GetMultiChannelScalingTileComposite(roi, planeCoordinate, zoom, combinedDisplaySettings.get(), app_functions); + } + else + { + const auto resultingDisplaySettings = CziUtilities::ConvertToDisplaySettings(dsinfo.displaySettings); + return CziReader::GetMultiChannelScalingTileComposite(roi, planeCoordinate, zoom, resultingDisplaySettings.get(), app_functions); + } +} + +Parameter* CziReader::GetMultiChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::IDisplaySettings* displaySettings, IAppExtensionFunctions* app_functions) +{ + std::vector activeChannels = libCZI::CDisplaySettingsHelper::GetActiveChannels(displaySettings); + + // we need to deal with the pathological case that all channels are disabled + if (activeChannels.empty()) + { + return CziReader::GetMultiChannelScalingTileCompositeAllChannelsDisabled(roi, zoom); + } + + IntSize sizeResult; + std::vector> channelBitmaps = CziUtilities::GetBitmapsFromSpecifiedChannels( + this->reader.get(), + planeCoordinate, + roi, + zoom, + [&](int idx, int& chNo)-> bool + { + if (idx < static_cast(activeChannels.size())) + { + chNo = activeChannels.at(idx); + return true; + } + + return false; + }, + &sizeResult); + + libCZI::CDisplaySettingsHelper dsplHlp; + dsplHlp.Initialize(displaySettings, [&](int chIndx)->libCZI::PixelType + { + const auto idx = std::distance(activeChannels.cbegin(), std::find(activeChannels.cbegin(), activeChannels.cend(), chIndx)); + return channelBitmaps[idx]->GetPixelType(); + }); + + std::vector vecBm; vecBm.reserve(channelBitmaps.size()); + for (int i = 0; i < channelBitmaps.size(); ++i) + { + vecBm.emplace_back(channelBitmaps[i].get()); + } + + auto bitmap = Compositors::ComposeMultiChannel_Bgr24( + (int)channelBitmaps.size(), + vecBm.data(), + dsplHlp.GetChannelInfosArray()); + + size_t dims[3] = { bitmap->GetHeight(), bitmap->GetWidth(), 3 }; + //auto mexApi = MexApi::GetInstance(); + //auto* arr = mxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL); + //auto* arr = mexApi.MxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL); + auto* arr = app_functions->pfn_CreateNumericArrayReal(3, dims, AppExtensionClassId_Uint8); + CziReader::CopyTransposeInterleavedToPlanarBgr24( + bitmap.get(), + app_functions->pfn_GetData(arr),// mexApi.MxGetData(arr), + static_cast(bitmap->GetHeight()), + static_cast(bitmap->GetWidth()) * static_cast(bitmap->GetHeight())); + return arr; +} + +Parameter* CziReader::GetMultiChannelScalingTileCompositeAllChannelsDisabled(const libCZI::IntRect& roi, float zoom, IAppExtensionFunctions* app_functions) +{ + auto accessor = reader->CreateSingleChannelScalingTileAccessor(); + const auto sizeResult = accessor->CalcSize(roi, zoom); + RgbFloatColor color{ 0,0,0 }; + + size_t dims[3] = { sizeResult.h, sizeResult.w, 3 }; + //auto* arr = mxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL); + //auto* arr = MexApi::GetInstance().MxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL); + auto* arr = app_functions->pfn_CreateNumericArrayReal(3, dims, AppExtensionClassId_Uint8); + return arr; +} + +Parameter* CziReader::GetSingleChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom) +{ + const RgbFloatColor backgndCol{ 0,0,0 }; + return this->GetSingleChannelScalingTileComposite(roi, planeCoordinate, zoom, backgndCol); +} + +Parameter* CziReader::GetSingleChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::RgbFloatColor& backgroundColor) +{ + auto scsta = this->reader->CreateSingleChannelScalingTileAccessor(); + libCZI::IntSize size = scsta->CalcSize(roi, zoom); + + int c = (numeric_limits::min)(); + planeCoordinate->TryGetPosition(libCZI::DimensionIndex::C, &c); + + // the idea is: for the cornerstone-case where we do not have a C-index, the call to "TryGetSubBlockInfoOfArbitrarySubBlockInChannel" + // will ignore the specified index _if_ there are no C-indices at all + libCZI::PixelType pixeltype = libCZI::Utils::TryDeterminePixelTypeForChannel(this->reader.get(), c); + + // however - if we get here with an invalid pixeltype, then we need to give up + if (pixeltype == PixelType::Invalid) + { + throw invalid_argument("Unable to determine pixeltype."); + } + + ISingleChannelScalingTileAccessor::Options accessorGetOptions; + accessorGetOptions.Clear(); + accessorGetOptions.backGroundColor = backgroundColor; + auto bitmap = scsta->Get(pixeltype, roi, planeCoordinate, zoom, &accessorGetOptions); + + auto* mxarray = ConvertToMxArray(bitmap.get()); + return mxarray; +} + +std::shared_ptr CziReader::GetDisplaySettingsFromCzi() +{ + this->InitializeInfoFromCzi(); + return this->displaySettingsFromCzi; +} + +/*static*/Parameter* CziReader::ConvertToMxArray(libCZI::IBitmapData* bitmapData) +{ + auto mexApi = MexApi::GetInstance(); + switch (bitmapData->GetPixelType()) + { + case PixelType::Gray8: + { + auto* arr = mexApi.MxCreateNumericMatrix(bitmapData->GetHeight(), bitmapData->GetWidth(), mxUINT8_CLASS, mxREAL); + CziReader::CopyTransposeGray8(bitmapData, mexApi.MxGetData(arr), 1 * static_cast(bitmapData->GetHeight())); + return arr; + } + case PixelType::Gray16: + { + auto* arr = mexApi.MxCreateNumericMatrix(bitmapData->GetHeight(), bitmapData->GetWidth(), mxUINT16_CLASS, mxREAL); + CziReader::CopyTransposeGray16(bitmapData, mexApi.MxGetData(arr), 2 * static_cast(bitmapData->GetHeight())); + return arr; + } + case PixelType::Bgr24: + { + size_t dims[3] = { bitmapData->GetHeight(), bitmapData->GetWidth(), 3 }; + auto* arr = mexApi.MxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL); + CziReader::CopyTransposeInterleavedToPlanarBgr24( + bitmapData, + mexApi.MxGetData(arr), + static_cast(bitmapData->GetHeight()), + static_cast(bitmapData->GetWidth()) * static_cast(bitmapData->GetHeight())); + return arr; + } + case PixelType::Bgr48: + { + size_t dims[3] = { bitmapData->GetHeight(), bitmapData->GetWidth() ,3 }; + auto* arr = mexApi.MxCreateNumericArray(3, dims, mxUINT16_CLASS, mxREAL); + CziReader::CopyTransposeInterleavedToPlanarBgr48( + bitmapData, + mexApi.MxGetData(arr), + static_cast(bitmapData->GetHeight()) * 2, + static_cast(bitmapData->GetWidth()) * static_cast(bitmapData->GetHeight()) * 2); + return arr; + } + case PixelType::Gray32Float: + { + auto* arr = mexApi.MxCreateNumericMatrix(bitmapData->GetHeight(), bitmapData->GetWidth(), mxSINGLE_CLASS, mxREAL); + CziReader::CopyTransposeGrayFloat(bitmapData, mexApi.MxGetData(arr), 4 * static_cast(bitmapData->GetHeight())); + return arr; + } + default: + throw std::invalid_argument("unsupported pixeltype"); + } +} + +/*static*/void CziReader::CopyTransposeGray8(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength) +{ + auto height = bitmapData->GetHeight(); + auto width = bitmapData->GetWidth(); + ScopedBitmapLocker lckBm{ bitmapData }; + for (decltype(height) y = 0; y < height; ++y) + { + const uint8_t* ptrSrc = static_cast(lckBm.ptrDataRoi) + y * static_cast(lckBm.stride); + uint8_t* ptrDst = static_cast(pDst) + y; + for (decltype(width) x = 0; x < width; ++x) + { + *ptrDst = *ptrSrc++; + ptrDst += lineLength; + } + } +} + +/*static*/void CziReader::CopyTransposeGray16(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength) +{ + auto height = bitmapData->GetHeight(); + auto width = bitmapData->GetWidth(); + ScopedBitmapLocker lckBm{ bitmapData }; + for (decltype(height) y = 0; y < height; ++y) + { + const uint16_t* ptrSrc = reinterpret_cast(static_cast(lckBm.ptrDataRoi) + y * static_cast(lckBm.stride)); + uint16_t* ptrDst = reinterpret_cast(static_cast(pDst) + static_cast(y) * 2); + for (decltype(width) x = 0; x < width; ++x) + { + *ptrDst = *ptrSrc++; + ptrDst = reinterpret_cast(reinterpret_cast(ptrDst) + lineLength); + } + } +} + +/*static*/void CziReader::CopyTransposeGrayFloat(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength) +{ + auto height = bitmapData->GetHeight(); + auto width = bitmapData->GetWidth(); + ScopedBitmapLocker lckBm{ bitmapData }; + for (decltype(height) y = 0; y < height; ++y) + { + const float* ptrSrc = reinterpret_cast(static_cast(lckBm.ptrDataRoi) + y * static_cast(lckBm.stride)); + float* ptrDst = reinterpret_cast(static_cast(pDst) + static_cast(y) * 4); + for (decltype(width) x = 0; x < width; ++x) + { + *ptrDst = *ptrSrc++; + ptrDst = reinterpret_cast(reinterpret_cast(ptrDst) + lineLength); + } + } +} + +/*static*/void CziReader::CopyTransposeInterleavedToPlanarBgr24(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineStride, size_t planeStride) +{ + auto height = bitmapData->GetHeight(); + auto width = bitmapData->GetWidth(); + ScopedBitmapLocker lckBm{ bitmapData }; + for (decltype(height) y = 0; y < height; ++y) + { + const uint8_t* ptrSrc = static_cast(lckBm.ptrDataRoi) + y * static_cast(lckBm.stride); + uint8_t* ptrDst = static_cast(pDst) + y; + for (decltype(width) x = 0; x < width; ++x) + { + const uint8_t b = *ptrSrc++; + const uint8_t g = *ptrSrc++; + const uint8_t r = *ptrSrc++; + + *ptrDst = r; + *(ptrDst + planeStride) = g; + *(ptrDst + 2 * planeStride) = b; + ptrDst += lineStride; + } + } +} + +/*static*/void CziReader::CopyTransposeInterleavedToPlanarBgr48(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineStride, size_t planeStride) +{ + auto height = bitmapData->GetHeight(); + auto width = bitmapData->GetWidth(); + ScopedBitmapLocker lckBm{ bitmapData }; + for (decltype(height) y = 0; y < height; ++y) + { + const uint16_t* ptrSrc = reinterpret_cast(static_cast(lckBm.ptrDataRoi) + y * static_cast(lckBm.stride)); + uint16_t* ptrDst = reinterpret_cast(static_cast(pDst) + y * static_cast(2)); + for (decltype(width) x = 0; x < width; ++x) + { + const uint16_t b = *ptrSrc++; + const uint16_t g = *ptrSrc++; + const uint16_t r = *ptrSrc++; + + *ptrDst = r; + *reinterpret_cast(reinterpret_cast(ptrDst) + planeStride) = g; + *reinterpret_cast(reinterpret_cast(ptrDst) + 2 * planeStride) = b; + ptrDst = reinterpret_cast(reinterpret_cast(ptrDst) + lineStride); + } + } +} + +/*static*/Parameter* CziReader::ConvertToMatlabStruct(const libCZI::IDimBounds* bounds) +{ + vector dimensions; + for (auto i = (std::underlying_type::type)(libCZI::DimensionIndex::MinDim); i <= (std::underlying_type::type)(libCZI::DimensionIndex::MaxDim); ++i) + { + auto d = static_cast(i); + if (bounds->IsValid(d)) + { + char dimStr[2] = { libCZI::Utils::DimensionToChar(d) ,'\0' }; + dimensions.emplace_back(dimStr); + } + } + + vector fieldNames; + fieldNames.reserve(dimensions.size()); + for (const auto& i : dimensions) + { + fieldNames.emplace_back(i.c_str()); + } + + size_t dims[2] = { 1, 1 }; + auto mexApi = MexApi::GetInstance(); + auto* s = mexApi.MxCreateStructArray(2, dims, (int)fieldNames.size(), &fieldNames[0]); + + for (int i = 0; i < dimensions.size(); ++i) + { + int startIndex, size; + bounds->TryGetInterval(libCZI::Utils::CharToDimension(dimensions[i][0]), &startIndex, &size); + auto* no = mexApi.MxCreateNumericMatrix(1, 2, mxINT32_CLASS, mxREAL); + int* ptr = mexApi.MxGetInt32s(no); + *ptr = startIndex; + *(ptr + 1) = size; + mexApi.MxSetFieldByNumber(s, 0, i, no); + } + + return s; +} + +/*static*/Parameter* CziReader::ConvertToMatlabStruct(const std::map& boundingBoxMap) +{ + static const char* fieldNames[] = { "sceneIndex", "boundingBox", "boundingBoxLayer0" }; + size_t dims[2] = { 1, boundingBoxMap.size() }; + auto mexApi = MexApi::GetInstance(); + auto* s = mexApi.MxCreateStructArray(2, dims, sizeof(fieldNames) / sizeof(fieldNames[0]), fieldNames); + + int i = 0; + for (auto it = boundingBoxMap.cbegin(); it != boundingBoxMap.cend(); ++it) + { + mexApi.MxSetFieldByNumber(s, i, 0, MexUtils::Int32To1x1Matrix(it->first)); + mexApi.MxSetFieldByNumber(s, i, 1, CziReader::ConvertToMatlabStruct(it->second.boundingBox)); + mexApi.MxSetFieldByNumber(s, i, 2, CziReader::ConvertToMatlabStruct(it->second.boundingBoxLayer0)); + ++i; + } + + return s; +} + +/*static*/ Parameter* CziReader::ConvertToMatlabStruct(const libCZI::IntRect& rect) +{ + auto mexApi = MexApi::GetInstance(); + auto* m = mexApi.MxCreateNumericMatrix(1, 4, mxINT32_CLASS, mxREAL); + int* ptr = mexApi.MxGetInt32s(m); + ptr[0] = rect.x; + ptr[1] = rect.y; + ptr[2] = rect.w; + ptr[3] = rect.h; + return m; +} + +/*static*/ Parameter* CziReader::ConvertToMatlabStruct(const libCZI::IntSize& size) +{ + auto mexApi = MexApi::GetInstance(); + auto* m = mexApi.MxCreateNumericMatrix(1, 2, mxINT32_CLASS, mxREAL); + int* ptr = mexApi.MxGetInt32s(m); + ptr[0] = size.w; + ptr[1] = size.h; + return m; +} + +/*static*/ Parameter* CziReader::ConvertToMatlabStruct(const libCZI::IDisplaySettings& ds, IAppExtensionFunctions* app_functions) +{ + static const char* tintingModesNone = "none"; + static const char* tintingModesColor = "color"; + + static const char* gradationcurveLinear = "linear"; + static const char* gradationcurveGamma = "gamma"; + static const char* gradationcurveSpline = "spline"; + + static const char* fieldNames[] = + { + "channelIndex", + "isEnabled", + "weight", + "tintingmode", + "tintingcolor", + "blackwhitepoint", + "gradationcurvemode", + "gamma", + "splinectrlpts" + }; + vector chIndices; + ds.EnumChannels( + [&](int idx) -> bool + { + chIndices.emplace_back(idx); + return true; + } + ); + + size_t dims[2] = { 1, chIndices.size() }; + //auto mexApi = MexApi::GetInstance(); + //auto* s = mexApi.MxCreateStructArray(2, dims, sizeof(fieldNames) / sizeof(fieldNames[0]), fieldNames); + auto* s = app_functions->pfn_CreateStructArray(2, dims, sizeof(fieldNames) / sizeof(fieldNames[0]), fieldNames); + size_t i = 0; + for (auto it : chIndices) + { + auto chDs = ds.GetChannelDisplaySettings(it); + //mexApi.MxSetFieldByNumber(s, i, 0, MexUtils::Int32To1x1Matrix(it)); + app_functions->pfn_SetFieldByNumber(s, i, 0, MexUtils::Int32To1x1Matrix(it)); + //mexApi.MxSetFieldByNumber(s, i, 1, MexUtils::BooleanTo1x1Matrix(chDs->GetIsEnabled())); + app_functions->pfn_SetFieldByNumber(s, i, 1, MexUtils::BooleanTo1x1Matrix(chDs->GetIsEnabled())); + //mexApi.MxSetFieldByNumber(s, i, 2, MexUtils::DoubleTo1x1Matrix(chDs->GetWeight())); + app_functions->pfn_SetFieldByNumber(s, i, 2, MexUtils::DoubleTo1x1Matrix(chDs->GetWeight())); + + Rgb8Color tintingColor; + if (chDs->TryGetTintingColorRgb8(&tintingColor)) + { + //mexApi.MxSetFieldByNumber(s, i, 3, mexApi.MxCreateString(tintingModesColor)); + app_functions->pfn_SetFieldByNumber(s, i, 3, app_functions->pfn_CreateString(tintingModesColor)); + //auto* m = MexApi::GetInstance().MxCreateNumericMatrix(3, 1, mxUINT8_CLASS, mxREAL); + auto* m = app_functions->pfn_CreateNumericMatrixReal(3, 1, AppExtensionClassId_Uint8); + //auto* ptr = MexApi::GetInstance().MxGetUint8s(m); + auto* ptr = app_functions->pfn_GetUint8s(m); + ptr[0] = tintingColor.r; + ptr[1] = tintingColor.g; + ptr[2] = tintingColor.b; + //mexApi.MxSetFieldByNumber(s, i, 4, m); + app_functions->pfn_SetFieldByNumber(s, i, 4, m); + } + /*else if TODO -> Lookup-Table */ + else + { + //mexApi.MxSetFieldByNumber(s, i, 3, mexApi.MxCreateString(tintingModesNone)); + app_functions->pfn_SetFieldByNumber(s, i, 3, app_functions->pfn_CreateString(tintingModesNone)); + } + + float blackPoint, whitePoint; + chDs->GetBlackWhitePoint(&blackPoint, &whitePoint); + //mexApi.MxSetFieldByNumber(s, i, 5, MexUtils::DoublesAsNx1Matrix(2, static_cast(blackPoint), static_cast(whitePoint))); + app_functions->pfn_SetFieldByNumber(s, i, 5, MexUtils::DoublesAsNx1Matrix(2, static_cast(blackPoint), static_cast(whitePoint))); + + switch (chDs->GetGradationCurveMode()) + { + case IDisplaySettings::GradationCurveMode::Linear: + //mexApi.MxSetFieldByNumber(s, i, 6, mexApi.MxCreateString(gradationcurveLinear)); + app_functions->pfn_SetFieldByNumber(s, i, 6, app_functions->pfn_CreateString(gradationcurveLinear)); + break; + case IDisplaySettings::GradationCurveMode::Gamma: + //mexApi.MxSetFieldByNumber(s, i, 6, mexApi.MxCreateString(gradationcurveGamma)); + app_functions->pfn_SetFieldByNumber(s, i, 6, app_functions->pfn_CreateString(gradationcurveGamma)); + float gamma; + chDs->TryGetGamma(&gamma); + //mexApi.MxSetFieldByNumber(s, i, 7, MexUtils::DoubleTo1x1Matrix(gamma)); + app_functions->pfn_SetFieldByNumber(s, i, 7, MexUtils::DoubleTo1x1Matrix(gamma)); + break; + case IDisplaySettings::GradationCurveMode::Spline: + //mexApi.MxSetFieldByNumber(s, i, 6, mexApi.MxCreateString(gradationcurveSpline)); + app_functions->pfn_SetFieldByNumber(s, i, 6, app_functions->pfn_CreateString(gradationcurveSpline)); + vector splineCtrlPts; + chDs->TryGetSplineControlPoints(&splineCtrlPts); + //auto* m = MexApi::GetInstance().MxCreateNumericMatrix(splineCtrlPts.size(), 2, mxDOUBLE_CLASS, mxREAL); + auto* m = app_functions->pfn_CreateNumericMatrixReal(splineCtrlPts.size(), 2, AppExtensionClassId_Double); + //auto* ptrDbls = MexApi::GetInstance().MxGetDoubles(m); + auto* ptrDbls = app_functions->pfn_GetDoubles(m); + for (const auto& splineCtrlPt : splineCtrlPts) + { + *ptrDbls++ = splineCtrlPt.x; + *ptrDbls++ = splineCtrlPt.y; + } + + //mexApi.MxSetFieldByNumber(s, i, 8, m); + app_functions->pfn_SetFieldByNumber(s, i, 8, m); + } + + ++i; + } + + return s; +} + +Parameter* CziReader::ReadSubBlock(int no) +{ + auto sbBlk = this->reader->ReadSubBlock(no); + if (!sbBlk) + { + std::stringstream ss; + ss << "SubBlock for id=" << no << " was not found."; + throw invalid_argument(ss.str()); + } + + int32_t h = this->sbBlkStore.AddSubBlock(sbBlk); + return MexUtils::Int32To1x1Matrix(h); +} + +Parameter* CziReader::GetInfoFromSubBlock(int subBlkHandle) +{ + auto sbBlk = this->sbBlkStore.GetForHandle(subBlkHandle); + if (!sbBlk) + { + std::stringstream ss; + ss << "SubBlock for handle=" << subBlkHandle << " is not present."; + throw invalid_argument(ss.str()); + } + + const auto& sbInfo = sbBlk->GetSubBlockInfo(); + return CziReader::ConvertToMatlabStruct(sbInfo); +} + +Parameter* CziReader::GetMetadataFromSubBlock(int subBlkHandle) +{ + auto sbBlk = this->sbBlkStore.GetForHandle(subBlkHandle); + if (!sbBlk) + { + std::stringstream ss; + ss << "SubBlock for handle=" << subBlkHandle << " is not present."; + throw invalid_argument(ss.str()); + } + + auto mexApi = MexApi::GetInstance(); + size_t sizeData; + auto ptrData = sbBlk->GetRawData(ISubBlock::MemBlkType::Metadata, &sizeData); + if (!ptrData) + { + return mexApi.MxCreateString(""); + } + + string metadataXml(static_cast(ptrData.get()), sizeData); + return mexApi.MxCreateString(metadataXml.c_str()); +} + +Parameter* CziReader::GetBitmapFromSubBlock(int subBlkHandle) +{ + auto sbBlk = this->sbBlkStore.GetForHandle(subBlkHandle); + if (!sbBlk) + { + std::stringstream ss; + ss << "SubBlock for handle=" << subBlkHandle << " is not present."; + throw invalid_argument(ss.str()); + } + + auto bm = sbBlk->CreateBitmap(); + return ConvertToMxArray(bm.get()); +} + +bool CziReader::ReleaseSubBlock(int subBlkHandle) +{ + return this->sbBlkStore.RemoveSubBlock(subBlkHandle); +} + +/*static*/Parameter* CziReader::ConvertToMatlabStruct(const libCZI::SubBlockInfo& sbBlkInfo) +{ + auto mexApi = MexApi::GetInstance(); + array fieldNames = { "Mode", "Pixeltype", "Coordinate", "LogicalRect", "PhysicalSize", "MIndex", "Zoom" }; + auto s = mexApi.MxCreateStructArray( + 2, + MexUtils::Dims_1_by_1, + static_cast(fieldNames.size()), + fieldNames.data()); + mexApi.MxSetFieldByNumber(s, 0, 0, mexApi.MxCreateString(libCZI::Utils::CompressionModeToInformalString(sbBlkInfo.GetCompressionMode()))); + mexApi.MxSetFieldByNumber(s, 0, 1, mexApi.MxCreateString(libCZI::Utils::PixelTypeToInformalString(sbBlkInfo.pixelType))); + mexApi.MxSetFieldByNumber(s, 0, 2, mexApi.MxCreateString(libCZI::Utils::DimCoordinateToString(&sbBlkInfo.coordinate).c_str())); + mexApi.MxSetFieldByNumber(s, 0, 3, CziReader::ConvertToMatlabStruct(sbBlkInfo.logicalRect)); + mexApi.MxSetFieldByNumber(s, 0, 4, CziReader::ConvertToMatlabStruct(sbBlkInfo.physicalSize)); + if (sbBlkInfo.mIndex != std::numeric_limits::max() && sbBlkInfo.mIndex != std::numeric_limits::min()) + { + mexApi.MxSetFieldByNumber(s, 0, 5, MexUtils::Int32To1x1Matrix(sbBlkInfo.mIndex)); + } + + mexApi.MxSetFieldByNumber(s, 0, 6, MexUtils::DoubleTo1x1Matrix(sbBlkInfo.GetZoom())); + return s; +} diff --git a/lib/src/implementation/CziReader.h b/lib/src/implementation/CziReader.h new file mode 100644 index 0000000..0facf5b --- /dev/null +++ b/lib/src/implementation/CziReader.h @@ -0,0 +1,70 @@ +#pragma once + +#include "inc_libczi.h" +#include +#include +#include +#include "CziReaderSbBlkStore.h" +//#include "mexapi.h" +#include + +class CziReader +{ +private: + std::shared_ptr reader; + + std::once_flag flagInfoFromCziMetadata; + + std::shared_ptr displaySettingsFromCzi; + libCZI::ScalingInfoEx scalingInfoFromCzi; + + CziReaderSubBlockStore sbBlkStore; +public: + CziReader() : reader(libCZI::CreateCZIReader()) + {} + + void Open(const std::string& utf8_filename); + + Parameter* GetInfo(IAppExtensionFunctions* app_functions); + std::string GetMetadataXml(); + Parameter* GetMetadataXmlAsMxArray(IAppExtensionFunctions* app_functions); + + Parameter* GetDefaultDisplaySettingsAsMxArray(IAppExtensionFunctions* app_functions); + + Parameter* GetSubBlockImage(int sbBlkNo); + Parameter* GetMultiChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const char* displaySettingsJson, IAppExtensionFunctions* app_functions); + Parameter* GetSingleChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom); + Parameter* GetSingleChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::RgbFloatColor& backgroundColor); + + std::array GetScaling(); + Parameter* GetScalingAsMatlabStruct(IAppExtensionFunctions* app_functions); + + Parameter* ReadSubBlock(int no); + Parameter* GetInfoFromSubBlock(int subBlkHandle); + Parameter* GetMetadataFromSubBlock(int subBlkHandle); + Parameter* GetBitmapFromSubBlock(int subBlkHandle); + bool ReleaseSubBlock(int subBlkHandle); +private: + static Parameter* ConvertToMatlabStruct(const libCZI::IDimBounds* bounds); + + /// Initializes the members "displaySettingsFromCzi" and "scalingInfoFromCzi". + void InitializeInfoFromCzi(); + std::shared_ptr GetDisplaySettingsFromCzi(); + const libCZI::ScalingInfo& GetScalingInfoFromCzi(); + + Parameter* GetMultiChannelScalingTileComposite(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::IDisplaySettings* displaySettings, IAppExtensionFunctions* app_functions); + Parameter* GetMultiChannelScalingTileCompositeAllChannelsDisabled(const libCZI::IntRect& roi, float zoom, IAppExtensionFunctions* app_functions); + + static Parameter* ConvertToMxArray(libCZI::IBitmapData* bitmapData); + static Parameter* ConvertToMatlabStruct(const std::map& boundingBoxMap); + static Parameter* ConvertToMatlabStruct(const libCZI::IntRect& rect); + static Parameter* ConvertToMatlabStruct(const libCZI::IntSize& size); + static Parameter* ConvertToMatlabStruct(const libCZI::IDisplaySettings& ds, IAppExtensionFunctions* app_functions); + static Parameter* ConvertToMatlabStruct(const libCZI::SubBlockInfo& sbBlkInfo); + + static void CopyTransposeGray8(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength); + static void CopyTransposeGray16(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength); + static void CopyTransposeGrayFloat(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineLength); + static void CopyTransposeInterleavedToPlanarBgr24(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineStride, size_t planeStride); + static void CopyTransposeInterleavedToPlanarBgr48(libCZI::IBitmapData* bitmapData, void* pDst, size_t lineStride, size_t planeStride); +}; \ No newline at end of file diff --git a/lib/src/implementation/CziReaderManager.cpp b/lib/src/implementation/CziReaderManager.cpp new file mode 100644 index 0000000..6e024ba --- /dev/null +++ b/lib/src/implementation/CziReaderManager.cpp @@ -0,0 +1,48 @@ +#include "CziReaderManager.h" +#include "dbgprint.h" + +using namespace std; + +/*static*/CziReaderManager CziReaderManager::instance; + +/*static*/CziReaderManager& CziReaderManager::GetInstance() +{ + return CziReaderManager::instance; +} + +void CziReaderManager::AddInstance(int id) +{ + VDBGPRINT((CDbg::Level::Trace, "CziReaderManager::AddInstance: add instance %i.\n", id)); + std::unique_lock lck(this->mutex); + this->map.insert(make_pair(id, make_shared< CziReader>())); +} + +bool CziReaderManager::RemoveInstance(int id) +{ + std::unique_lock lck(this->mutex); + const auto noOfElementsRemoved = this->map.erase(id); + VDBGPRINT((CDbg::Level::Trace, "CziReaderManager::RemoveInstance: remove instance %i -> %s.", id, noOfElementsRemoved > 0 ? "success" : "failure")); + return noOfElementsRemoved > 0; +} + +std::shared_ptr CziReaderManager::GetInstance(int id) +{ + std::unique_lock lck(this->mutex); + return this->map.at(id); +} + +int CziReaderManager::CreateNewInstance() +{ + const int id = this->instanceCounter.fetch_add(1); + this->AddInstance(id); + return id; +} + +int CziReaderManager::RemoveAllInstances() +{ + std::unique_lock lck(this->mutex); + const int noOfExistingInstances = static_cast(this->map.size()); + VDBGPRINT((CDbg::Level::Trace, "CziReaderManager::RemoveAllInstances: # of existing instances is %i.\n", noOfExistingInstances)); + this->map.clear(); + return noOfExistingInstances; +} \ No newline at end of file diff --git a/lib/src/implementation/CziReaderManager.h b/lib/src/implementation/CziReaderManager.h new file mode 100644 index 0000000..efd6d31 --- /dev/null +++ b/lib/src/implementation/CziReaderManager.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include "CziReader.h" +#include + +/// This is the manager of "CZIReader"-objects. They are created by the manager and are accessed +/// by a handle. +class CziReaderManager +{ +private: + std::atomic instanceCounter; + mutable std::mutex mutex; + std::unordered_map> map; + CziReaderManager() : instanceCounter{ 1 } {} + + static CziReaderManager instance; +public: + + /// Gets the (one and only) instance. + /// \returns The instance. + static CziReaderManager& GetInstance(); + + /// Removes the instance described by the specified id. + /// \param id The identifier. + /// \returns True if it succeeds, false if it fails. + bool RemoveInstance(int id); + + /// Creates a new instance and returns its id. + /// \returns The id of the new instance. + int CreateNewInstance(); + + /// Gets the instance associated with the specified id. + /// If the specified key is not present, then an out_of_range-exception is thrown. + /// \param id The identifier. + /// \returns The instance. + std::shared_ptr GetInstance(int id); + + /// Removes all instances, and return how many instances were destroyed. + /// \returns The number of instances that had to be destroyed. + int RemoveAllInstances(); +protected: + void AddInstance(int id); +}; \ No newline at end of file diff --git a/lib/src/implementation/CziReaderSbBlkStore.cpp b/lib/src/implementation/CziReaderSbBlkStore.cpp new file mode 100644 index 0000000..07f4511 --- /dev/null +++ b/lib/src/implementation/CziReaderSbBlkStore.cpp @@ -0,0 +1,41 @@ +#include "CziReaderSbBlkStore.h" +#include + +CziReaderSubBlockStore::CziReaderSubBlockStore() : lastHandle(1000) +{ +} + +std::int32_t CziReaderSubBlockStore::AddSubBlock(std::shared_ptr sbBlk) +{ + auto h = this->GetNewHandle(); + this->sbBlkMap[h] = std::move(sbBlk); + return h; +} + +std::shared_ptr CziReaderSubBlockStore::GetForHandle(std::int32_t h) +{ + const auto it = this->sbBlkMap.find(h); + if (it == this->sbBlkMap.cend()) + { + return std::shared_ptr(); + } + + return it->second; +} + +bool CziReaderSubBlockStore::RemoveSubBlock(std::int32_t h) +{ + const auto it = this->sbBlkMap.find(h); + if (it == this->sbBlkMap.cend()) + { + return false; + } + + this->sbBlkMap.erase(it); + return true; +} + +std::int32_t CziReaderSubBlockStore::GetNewHandle() +{ + return ++this->lastHandle; +} \ No newline at end of file diff --git a/lib/src/implementation/CziReaderSbBlkStore.h b/lib/src/implementation/CziReaderSbBlkStore.h new file mode 100644 index 0000000..4ee3b12 --- /dev/null +++ b/lib/src/implementation/CziReaderSbBlkStore.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "inc_libczi.h" + +class CziReaderSubBlockStore +{ +private: + std::unordered_map> sbBlkMap; + std::int32_t lastHandle; +public: + CziReaderSubBlockStore(); + std::int32_t AddSubBlock(std::shared_ptr sbBlk); + std::shared_ptr GetForHandle(std::int32_t h); + bool RemoveSubBlock(std::int32_t h); +private: + std::int32_t GetNewHandle(); +}; diff --git a/lib/src/implementation/CziUtilities.cpp b/lib/src/implementation/CziUtilities.cpp new file mode 100644 index 0000000..6c832de --- /dev/null +++ b/lib/src/implementation/CziUtilities.cpp @@ -0,0 +1,397 @@ +#include "CziUtilities.h" +#include "include_rapidjson.h" +#include "utils.h" + +using namespace libCZI; +using namespace std; + +void ChannelDisplaySettingsValidity::Set(Property prop, bool value) +{ + this->flags.set(static_cast(prop) - 1, value); +} + +bool ChannelDisplaySettingsValidity::Get(Property prop) const +{ + return this->flags.test(static_cast(prop) - 1); +} + +/*static*/std::vector> CziUtilities::GetBitmapsFromSpecifiedChannels(libCZI::ICZIReader* reader, const libCZI::IDimCoordinate* planeCoordinate, const libCZI::IntRect& roi, float zoom, std::function getChannelNo, libCZI::IntSize* ptrPixelSize) +{ + std::vector> chBitmaps; + libCZI::CDimCoordinate coordinate{planeCoordinate}; + + const auto subBlockStatistics = reader->GetStatistics(); + + libCZI::ISingleChannelScalingTileAccessor::Options sctaOptions; + sctaOptions.Clear(); + sctaOptions.backGroundColor = RgbFloatColor{ 0,0,0 }; + + auto accessor = reader->CreateSingleChannelScalingTileAccessor(); + + for (int i = 0;; ++i) + { + int chNo; + if (getChannelNo(i, chNo) == false) + { + break; + } + + if (subBlockStatistics.dimBounds.IsValid(DimensionIndex::C)) + { + // That's a cornerstone case - or a loophole in the specification: if the document + // does not contain C-dimension (=none of the sub-blocks has a valid C-dimension), + // then we must not set the C-dimension here. I suppose we should define that a + // valid C-dimension is mandatory... + coordinate.Set(DimensionIndex::C, chNo); + } + + chBitmaps.emplace_back(accessor->Get(roi, &coordinate, zoom, &sctaOptions)); + } + + if (ptrPixelSize != nullptr) + { + if (!chBitmaps.empty()) + { + *ptrPixelSize = chBitmaps[0]->GetSize(); + } + else + { + *ptrPixelSize = accessor->CalcSize(roi, zoom); + } + } + + return chBitmaps; +} + +/*static*/ChannelDisplaySettingsInfo CziUtilities::ParseDisplaySettings(const char* sz) +{ + rapidjson::Document document; + document.Parse(sz); + if (document.HasParseError()) + { + throw std::invalid_argument("Invalid JSON, document could not be parsed."); + } + + const bool isObj = document.IsObject(); + if (!isObj) + { + throw std::invalid_argument("No root object."); + } + + const bool hasChannels = document.HasMember(CziUtilities::JsonKey_Channels); + if (!hasChannels) + { + throw invalid_argument("No \"channels\"-node."); + } + + const bool isChannelsArray = document[CziUtilities::JsonKey_Channels].IsArray(); + if (!isChannelsArray) + { + throw invalid_argument("No \"channels\"-array."); + } + + const auto& channels = document[CziUtilities::JsonKey_Channels]; + + ChannelDisplaySettingsInfo result; + for (decltype(channels.Size()) i = 0; i < channels.Size(); ++i) + { + auto r = CziUtilities::ParseChannelDisplaySettings(channels[i]); + + // note: if there are duplicate channels, the last one wins + result.displaySettings[get<0>(r)] = get<1>(r); + } + + result.isToBeMerged = true; + if (document.HasMember(CziUtilities::JsonKey_MergeWithEmbeddedDisplaySettings)) + { + if (document[CziUtilities::JsonKey_MergeWithEmbeddedDisplaySettings].IsBool()) + { + result.isToBeMerged = document[CziUtilities::JsonKey_MergeWithEmbeddedDisplaySettings].GetBool(); + } + } + + return result; +} + +/*static*/std::tuple CziUtilities::ParseChannelDisplaySettings(const rapidjson::Value& v) +{ + // We only throw an exception if there is no valid channel-number. If we encounter other problem, we ignore it. + if (v.HasMember(CziUtilities::JsonKey_Ch) == false) + { + throw std::invalid_argument("No channel specified"); + } + + if (v[CziUtilities::JsonKey_Ch].IsInt() == false) + { + throw std::invalid_argument("Channel-number must be an integer"); + } + + ChannelDisplaySettingsAndValidity cds; + cds.channel_display_settings.Clear(); + + int chNo = v[CziUtilities::JsonKey_Ch].GetInt(); + if (chNo < 0) + { + throw invalid_argument("Channel-Number must not be negative."); + } + + if (v.HasMember(CziUtilities::JsonKey_Enabled)) + { + if (v[CziUtilities::JsonKey_Enabled].IsBool()) + { + cds.channel_display_settings.isEnabled = v[CziUtilities::JsonKey_Enabled].GetBool(); + cds.validity.Set(ChannelDisplaySettingsValidity::Property::IsEnabled, true); + } + } + + if (v.HasMember(CziUtilities::JsonKey_BlackPoint)) + { + if (v[CziUtilities::JsonKey_BlackPoint].IsFloat()) + { + cds.channel_display_settings.blackPoint = v[CziUtilities::JsonKey_BlackPoint].GetFloat(); + cds.validity.Set(ChannelDisplaySettingsValidity::Property::BlackPoint, true); + } + } + + if (v.HasMember(CziUtilities::JsonKey_WhitePoint)) + { + if (v[CziUtilities::JsonKey_WhitePoint].IsFloat()) + { + cds.channel_display_settings.whitePoint = v[CziUtilities::JsonKey_WhitePoint].GetFloat(); + cds.validity.Set(ChannelDisplaySettingsValidity::Property::WhitePoint, true); + } + } + + if (v.HasMember(CziUtilities::JsonKey_TintingMode)) + { + if (v[CziUtilities::JsonKey_TintingMode].IsString()) + { + const auto& str = ::Utils::trim(v[CziUtilities::JsonKey_TintingMode].GetString()); + + if (::Utils::icasecmp(str, "none")) + { + cds.channel_display_settings.tintingMode = IDisplaySettings::TintingMode::None; + cds.validity.Set(ChannelDisplaySettingsValidity::Property::TintingMode, true); + } + else if (::Utils::icasecmp(str, "color")) + { + cds.channel_display_settings.tintingMode = IDisplaySettings::TintingMode::Color; + cds.validity.Set(ChannelDisplaySettingsValidity::Property::TintingMode, true); + } + } + } + + if (v.HasMember(CziUtilities::JsonKey_TintingColor)) + { + if (v[CziUtilities::JsonKey_TintingColor].IsString()) + { + const auto& str = ::Utils::trim(v[CziUtilities::JsonKey_TintingColor].GetString()); + if (TryParseColor(str, &cds.channel_display_settings.tintingColor) == true) + { + cds.validity.Set(ChannelDisplaySettingsValidity::Property::TintingColor, true); + } + } + } + + if (v.HasMember(CziUtilities::JsonKey_GradationCurveMode)) + { + if (v[CziUtilities::JsonKey_GradationCurveMode].IsString()) + { + const auto& str = ::Utils::trim(v[CziUtilities::JsonKey_GradationCurveMode].GetString()); + if (::Utils::icasecmp(str, "linear")) + { + cds.channel_display_settings.gradationCurveMode = IDisplaySettings::GradationCurveMode::Linear; + cds.validity.Set(ChannelDisplaySettingsValidity::Property::GradationCurveMode, true); + } + else if (::Utils::icasecmp(str, "gamma")) + { + cds.channel_display_settings.gradationCurveMode = IDisplaySettings::GradationCurveMode::Gamma; + cds.validity.Set(ChannelDisplaySettingsValidity::Property::GradationCurveMode, true); + } + else if (::Utils::icasecmp(str, "spline")) + { + cds.channel_display_settings.gradationCurveMode = IDisplaySettings::GradationCurveMode::Spline; + cds.validity.Set(ChannelDisplaySettingsValidity::Property::GradationCurveMode, true); + } + } + } + + if (v.HasMember(CziUtilities::JsonKey_Gamma)) + { + if (v[CziUtilities::JsonKey_Gamma].IsFloat()) + { + cds.channel_display_settings.gamma = v[CziUtilities::JsonKey_Gamma].GetFloat(); + cds.validity.Set(ChannelDisplaySettingsValidity::Property::Gamma, true); + } + } + + if (v.HasMember(CziUtilities::JsonKey_SplinePoints)) + { + const rapidjson::Value& sp = v[CziUtilities::JsonKey_SplinePoints]; + if (!sp.IsArray()) + { + throw std::logic_error("Invalid JSON"); + } + + for (rapidjson::Value::ConstValueIterator it = sp.Begin(); it != sp.End(); ++it) + { + const double d1 = it->GetDouble(); + ++it; + if (it == sp.End()) + break; + const double d2 = it->GetDouble(); + cds.channel_display_settings.splineCtrlPoints.push_back(IDisplaySettings::SplineControlPoint(d1, d2)); + } + + if (!cds.channel_display_settings.splineCtrlPoints.empty()) + { + cds.validity.Set(ChannelDisplaySettingsValidity::Property::SplineCtrlPts, true); + } + } + + return make_tuple(chNo, cds); +} + +/*static*/bool CziUtilities::TryParseColor(const std::string& str, libCZI::Rgb8Color* ptrRgbColor) +{ + // the rules are: + // - it has to start with a '#' + // - then, we expect 6 hex-chars (if there is more after those 6 chars, then they are ignored) + // - if we have less than 6 chars, we behave as if the missing chars would be 'F' + + if (str.empty() || str[0] != '#') + { + return false; + } + + std::uint8_t r{ 0xff }, g{ 0xff }, b{ 0xff }; + for (size_t i = 1; i < (std::min)(static_cast(7), str.size()); ++i) + { + const uint8_t nibble = ::Utils::HexCharToInt(str[i]); + if (nibble > 0x0f) + { + return false; + } + + switch (i) + { + case 1: + r = (r & 0x0f) | (nibble << 4); + break; + case 2: + r = (r & 0xf0) | nibble; + break; + case 3: + g = (g & 0x0f) | (nibble << 4); + break; + case 4: + g = (g & 0xf0) | nibble; + break; + case 5: + b = (b & 0x0f) | (nibble << 4); + break; + case 6: + b = (b & 0xf0) | nibble; + break; + } + } + + if (ptrRgbColor != nullptr) + { + ptrRgbColor->r = r; + ptrRgbColor->g = g; + ptrRgbColor->b = b; + } + + return true; +} + +/*static*/const char* CziUtilities::JsonKey_Channels = "channels"; +/*static*/const char* CziUtilities::JsonKey_Ch = "ch"; +/*static*/const char* CziUtilities::JsonKey_Enabled = "enabled"; +/*static*/const char* CziUtilities::JsonKey_BlackPoint = "black-point"; +/*static*/const char* CziUtilities::JsonKey_WhitePoint = "white-point";; +/*static*/const char* CziUtilities::JsonKey_TintingMode = "tinting-mode"; +/*static*/const char* CziUtilities::JsonKey_TintingColor = "tinting-color"; +/*static*/const char* CziUtilities::JsonKey_GradationCurveMode = "gradation-curve-mode"; +/*static*/const char* CziUtilities::JsonKey_Gamma = "gamma"; +/*static*/const char* CziUtilities::JsonKey_SplinePoints = "spline-control-points"; +/*static*/const char* CziUtilities::JsonKey_MergeWithEmbeddedDisplaySettings = "merge-with-embedded"; + +/*static*/std::shared_ptr CziUtilities::CombineDisplaySettings(const libCZI::IDisplaySettings* display_settings, const std::map& partialDs) +{ + libCZI::DisplaySettingsPOD dsPod; + IDisplaySettings::Clone(display_settings, dsPod); + + for (auto it = partialDs.cbegin(); it != partialDs.cend(); ++it) + { + auto& cds = dsPod.channelDisplaySettings.at(it->first); + CziUtilities::TransferPartialChannelDisplaySettings(cds, it->second); + } + + return DisplaySettingsPOD::CreateIDisplaySettingSp(dsPod); +} + +/*static*/std::shared_ptr CziUtilities::ConvertToDisplaySettings(const std::map& partialDs) +{ + libCZI::DisplaySettingsPOD dsPod; + + // we create "default channel-display-settings", and the apply the information provided with "partialDs" + for (auto it = partialDs.cbegin(); it != partialDs.cend(); ++it) + { + ChannelDisplaySettingsPOD channelDisplaySettings; + channelDisplaySettings.Clear(); + CziUtilities::TransferPartialChannelDisplaySettings(channelDisplaySettings, it->second); + dsPod.channelDisplaySettings[it->first] = channelDisplaySettings; + } + + return DisplaySettingsPOD::CreateIDisplaySettingSp(dsPod); +} + +/*static*/void CziUtilities::TransferPartialChannelDisplaySettings(libCZI::ChannelDisplaySettingsPOD& cds, const ChannelDisplaySettingsAndValidity& pds) +{ + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::IsEnabled)) + { + cds.isEnabled = pds.channel_display_settings.isEnabled; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::Weight)) + { + cds.weight = pds.channel_display_settings.weight; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::TintingMode)) + { + cds.tintingMode = pds.channel_display_settings.tintingMode; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::TintingColor)) + { + cds.tintingColor = pds.channel_display_settings.tintingColor; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::BlackPoint)) + { + cds.blackPoint = pds.channel_display_settings.blackPoint; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::WhitePoint)) + { + cds.whitePoint = pds.channel_display_settings.whitePoint; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::GradationCurveMode)) + { + cds.gradationCurveMode = pds.channel_display_settings.gradationCurveMode; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::Gamma)) + { + cds.gamma = pds.channel_display_settings.gamma; + } + + if (pds.validity.Get(ChannelDisplaySettingsValidity::Property::SplineCtrlPts)) + { + cds.splineCtrlPoints = pds.channel_display_settings.splineCtrlPoints; + } +} diff --git a/lib/src/implementation/CziUtilities.h b/lib/src/implementation/CziUtilities.h new file mode 100644 index 0000000..b1e6826 --- /dev/null +++ b/lib/src/implementation/CziUtilities.h @@ -0,0 +1,71 @@ +#pragma once + +#include "inc_libczi.h" +#include +#include + +#include "include_rapidjson.h" + +struct ChannelDisplaySettingsValidity +{ + enum class Property + { + IsEnabled = 1, + Weight = 2, + TintingMode = 3, + TintingColor = 4, + BlackPoint = 5, + WhitePoint = 6, + GradationCurveMode = 7, + Gamma = 8, + SplineCtrlPts = 9, + + PropertyMax = 9 + }; +private: + std::bitset<(size_t)Property::PropertyMax> flags; +public: + void Set(Property prop, bool value); + bool Get(Property prop) const; +}; + +struct ChannelDisplaySettingsAndValidity +{ + libCZI::ChannelDisplaySettingsPOD channel_display_settings; + ChannelDisplaySettingsValidity validity; +}; + +struct ChannelDisplaySettingsInfo +{ + /// True if the display-settings are to be merged with the display-settings embedded in the CZI; + /// false means that the display-settings here replace the embedded display-settings. + bool isToBeMerged; + std::map displaySettings; +}; + +class CziUtilities +{ +public: + static std::vector> GetBitmapsFromSpecifiedChannels(libCZI::ICZIReader* reader, const libCZI::IDimCoordinate* planeCoordinate, const libCZI::IntRect& roi, float zoom, std::function getChannelNo, libCZI::IntSize* ptrPixelSize); + + static ChannelDisplaySettingsInfo ParseDisplaySettings(const char* sz); + + static std::shared_ptr CombineDisplaySettings(const libCZI::IDisplaySettings* display_settings, const std::map& partialDs); + static std::shared_ptr ConvertToDisplaySettings(const std::map& partialDs); +private: + static const char* JsonKey_Channels; + static const char* JsonKey_Ch; + static const char* JsonKey_Enabled; + static const char* JsonKey_BlackPoint; + static const char* JsonKey_WhitePoint; + static const char* JsonKey_TintingMode; + static const char* JsonKey_TintingColor; + static const char* JsonKey_GradationCurveMode; + static const char* JsonKey_Gamma; + static const char* JsonKey_SplinePoints; + static const char* JsonKey_MergeWithEmbeddedDisplaySettings; + static std::tuple ParseChannelDisplaySettings(const rapidjson::Value& v); + static bool TryParseColor(const std::string& str, libCZI::Rgb8Color* ptrRgbColor); + static void TransferPartialChannelDisplaySettings(libCZI::ChannelDisplaySettingsPOD& cds, const ChannelDisplaySettingsAndValidity& pds); + +}; \ No newline at end of file diff --git a/lib/src/implementation/CziWriter.cpp b/lib/src/implementation/CziWriter.cpp new file mode 100644 index 0000000..3b96372 --- /dev/null +++ b/lib/src/implementation/CziWriter.cpp @@ -0,0 +1,152 @@ +#include "CziWriter.h" +#include "inc_libczi.h" +#include "utils.h" + +using namespace std; +using namespace libCZI; + +void CziWriter::Create(const std::string& utf8_filename, bool overwrite_existing) +{ + auto filename = ::Utils::convertUtf8ToWchar_t(utf8_filename); + + auto output_stream = libCZI::CreateOutputStreamForFile(filename.c_str(), overwrite_existing); + if (!output_stream) + { + throw runtime_error("Failed to create output stream for file"); + } + + this->writer->Create(output_stream, nullptr); +} + +void CziWriter::AddSubBlock(const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, const std::shared_ptr& bitmap_data, const std::string& subblock_metadata_xml, const libCZI::Utils::CompressionOption& compression_option) +{ + if (compression_option.first == libCZI::CompressionMode::UnCompressed) + { + this->AddSubBlockUncompressed(add_sub_block_info_base, bitmap_data, subblock_metadata_xml); + } + else + { + this->CompressAndAddSubBlock(add_sub_block_info_base, bitmap_data, subblock_metadata_xml, compression_option); + } +} + +void CziWriter::Close() +{ + PrepareMetadataInfo prepare_metadata_info; + auto mdBldr = writer->GetPreparedMetadata(prepare_metadata_info); + auto xml = mdBldr->GetXml(true); + WriteMetadataInfo writerMdInfo; + writerMdInfo.Clear(); + writerMdInfo.szMetadata = xml.c_str(); + writerMdInfo.szMetadataSize = xml.size(); + writer->SyncWriteMetadata(writerMdInfo); + this->writer->Close(); +} + +void CziWriter::AddSubBlockUncompressed( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + const std::shared_ptr& bitmap_data, + const std::string& subblock_metadata_xml) +{ + libCZI::AddSubBlockInfoStridedBitmap add_sub_block_info_strided_bitmap; + add_sub_block_info_strided_bitmap.Clear(); + add_sub_block_info_strided_bitmap.coordinate = add_sub_block_info_base.coordinate; + add_sub_block_info_strided_bitmap.mIndexValid = add_sub_block_info_base.mIndexValid; + add_sub_block_info_strided_bitmap.mIndex = add_sub_block_info_base.mIndex; + add_sub_block_info_strided_bitmap.x = add_sub_block_info_base.x; + add_sub_block_info_strided_bitmap.y = add_sub_block_info_base.y; + add_sub_block_info_strided_bitmap.logicalWidth = add_sub_block_info_base.logicalWidth; + add_sub_block_info_strided_bitmap.logicalHeight = add_sub_block_info_base.logicalHeight; + add_sub_block_info_strided_bitmap.physicalWidth = add_sub_block_info_base.physicalWidth; + add_sub_block_info_strided_bitmap.physicalHeight = add_sub_block_info_base.physicalHeight; + add_sub_block_info_strided_bitmap.PixelType = add_sub_block_info_base.PixelType; + add_sub_block_info_strided_bitmap.pyramid_type = add_sub_block_info_base.pyramid_type; + add_sub_block_info_strided_bitmap.SetCompressionMode(libCZI::CompressionMode::UnCompressed); + + if (!subblock_metadata_xml.empty()) + { + add_sub_block_info_strided_bitmap.ptrSbBlkMetadata = subblock_metadata_xml.c_str(); + add_sub_block_info_strided_bitmap.sbBlkMetadataSize = static_cast(subblock_metadata_xml.size()); + } + + libCZI::ScopedBitmapLockerSP bitmap_locker(bitmap_data); + add_sub_block_info_strided_bitmap.ptrBitmap = bitmap_locker.ptrDataRoi; + add_sub_block_info_strided_bitmap.strideBitmap = bitmap_locker.stride; + + this->writer->SyncAddSubBlock(add_sub_block_info_strided_bitmap); +} + +void CziWriter::CompressAndAddSubBlock( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + const std::shared_ptr& bitmap_data, + const std::string& subblock_metadata_xml, + const libCZI::Utils::CompressionOption& compression_option) +{ + if (compression_option.first == CompressionMode::Zstd0) + { + libCZI::ScopedBitmapLockerSP bitmap_locker(bitmap_data); + auto compressed_data_memory_block = ZstdCompress::CompressZStd0Alloc( + bitmap_data->GetWidth(), + bitmap_data->GetHeight(), + bitmap_locker.stride, + bitmap_data->GetPixelType(), + bitmap_locker.ptrDataRoi, + compression_option.second.get()); + this->AddSubBlockCompressed(add_sub_block_info_base, CompressionMode::Zstd0, compressed_data_memory_block, subblock_metadata_xml); + } + else if (compression_option.first == CompressionMode::Zstd1) + { + libCZI::ScopedBitmapLockerSP bitmap_locker(bitmap_data); + auto compressed_data_memory_block = ZstdCompress::CompressZStd1Alloc( + bitmap_data->GetWidth(), + bitmap_data->GetHeight(), + bitmap_locker.stride, + bitmap_data->GetPixelType(), + bitmap_locker.ptrDataRoi, + compression_option.second.get()); + this->AddSubBlockCompressed(add_sub_block_info_base, CompressionMode::Zstd1, compressed_data_memory_block, subblock_metadata_xml); + } + else if (compression_option.first == CompressionMode::JpgXr) + { + libCZI::ScopedBitmapLockerSP bitmap_locker(bitmap_data); + auto compressed_data_memory_block = JxrLibCompress::Compress( + bitmap_data->GetPixelType(), + bitmap_data->GetWidth(), + bitmap_data->GetHeight(), + bitmap_locker.stride, + bitmap_locker.ptrDataRoi, + compression_option.second.get()); + this->AddSubBlockCompressed(add_sub_block_info_base, CompressionMode::JpgXr, compressed_data_memory_block, subblock_metadata_xml); + } +} + +void CziWriter::AddSubBlockCompressed( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + libCZI::CompressionMode compression_mode, + const std::shared_ptr& compressed_data, + const std::string& subblock_metadata_xml) +{ + AddSubBlockInfoMemPtr addInfo; + addInfo.Clear(); + addInfo.coordinate = add_sub_block_info_base.coordinate; + addInfo.mIndexValid = add_sub_block_info_base.mIndexValid; + addInfo.mIndex = add_sub_block_info_base.mIndex; + addInfo.x = add_sub_block_info_base.x; + addInfo.y = add_sub_block_info_base.y; + addInfo.logicalWidth = add_sub_block_info_base.logicalWidth; + addInfo.logicalHeight = add_sub_block_info_base.logicalHeight; + addInfo.physicalWidth = add_sub_block_info_base.physicalWidth; + addInfo.physicalHeight = add_sub_block_info_base.physicalHeight; + addInfo.PixelType = add_sub_block_info_base.PixelType; + addInfo.ptrData = compressed_data->GetPtr(); + addInfo.dataSize = static_cast(compressed_data->GetSizeOfData()); + + if (!subblock_metadata_xml.empty()) + { + addInfo.ptrSbBlkMetadata = subblock_metadata_xml.c_str(); + addInfo.sbBlkMetadataSize = static_cast(subblock_metadata_xml.size()); + } + + addInfo.SetCompressionMode(compression_mode); + writer->SyncAddSubBlock(addInfo); +} \ No newline at end of file diff --git a/lib/src/implementation/CziWriter.h b/lib/src/implementation/CziWriter.h new file mode 100644 index 0000000..4e50ad6 --- /dev/null +++ b/lib/src/implementation/CziWriter.h @@ -0,0 +1,39 @@ +#pragma once + +#include "inc_libczi.h" +#include +#include + +class CziWriter +{ +private: + std::shared_ptr writer; +public: + CziWriter() : writer(libCZI::CreateCZIWriter()) + {} + + void Create(const std::string& utf8_filename, bool overwrite_existing); + + void AddSubBlock( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + const std::shared_ptr& bitmap_data, + const std::string& subblock_metadata_xml, + const libCZI::Utils::CompressionOption& compression_option); + void Close(); +private: + void AddSubBlockUncompressed( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + const std::shared_ptr& bitmap_data, + const std::string& subblock_metadata_xml); + void CompressAndAddSubBlock( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + const std::shared_ptr& bitmap_data, + const std::string& subblock_metadata_xml, + const libCZI::Utils::CompressionOption& compression_option); + + void AddSubBlockCompressed( + const libCZI::AddSubBlockInfoBase& add_sub_block_info_base, + libCZI::CompressionMode compression_mode, + const std::shared_ptr& compressed_data, + const std::string& subblock_metadata_xml); +}; diff --git a/lib/src/implementation/argsutils.cpp b/lib/src/implementation/argsutils.cpp new file mode 100644 index 0000000..6f30be4 --- /dev/null +++ b/lib/src/implementation/argsutils.cpp @@ -0,0 +1,626 @@ +#include "argsutils.h" +#include +#include "utils.h" +#include + +using namespace std; +using namespace libCZI; + +/*static*/bool CArgsUtils::TryGetInt32(const Parameter* pArr, std::int32_t* ptr, IAppExtensionFunctions* app_functions) +{ + //const mxClassID id = MexApi::GetInstance().MxGetClassID(pArr); + const AppExtensionClassId id = app_functions->pfn_GetClassId(pArr); + int rv; + switch (id) + { + case AppExtensionClassId_Double: + { + //auto* const pDbl = MexApi::GetInstance().MxGetDoubles(pArr); + auto* const pDbl = app_functions->pfn_GetDoubles(pArr); + const double v = *pDbl; + if (app_functions->pfn_IsNanOrInfDouble(v) || v > numeric_limits::max() || v < numeric_limits::min()) + { + return false; + } + + rv = lrint(v); + } + break; + case AppExtensionClassId_Single: + { + //auto* const pFlt = MexApi::GetInstance().MxGetSingles(pArr); + auto* const pFlt = app_functions->pfn_GetSingles(pArr); + const float v = *pFlt; + if (app_functions->pfn_IsNanOrInfDouble(v) || v > static_cast(numeric_limits::max()) || v < static_cast(numeric_limits::min())) + { + return false; + } + + rv = lrintf(v); + } + break; + case AppExtensionClassId_Int8: + { + //auto* const pi8 = MexApi::GetInstance().MxGetInt8s(pArr); + auto* const pi8 = app_functions->pfn_GetInt8s(pArr); + rv = *pi8; // NOLINT(bugprone-signed-char-misuse) + } + break; + case AppExtensionClassId_Uint8: + { + //auto* const pui8 = MexApi::GetInstance().MxGetUint8s(pArr); + auto* const pui8 = app_functions->pfn_GetUint8s(pArr); + rv = *pui8; + } + break; + case AppExtensionClassId_Int16: + { + //auto* const pi16 = MexApi::GetInstance().MxGetInt16s(pArr); + auto* const pi16 = app_functions->pfn_GetInt16s(pArr); + rv = *pi16; + } + break; + case AppExtensionClassId_Uint16: + { + //auto* const pui16 = MexApi::GetInstance().MxGetUint16s(pArr); + auto* const pui16 = app_functions->pfn_GetUint16s(pArr); + rv = *pui16; + } + break; + case AppExtensionClassId_Int32: + { + //const auto pi32 = MexApi::GetInstance().MxGetInt32s(pArr); + const auto pi32 = app_functions->pfn_GetInt32s(pArr); + rv = *pi32; + } + break; + case AppExtensionClassId_Uint32: + { + //const auto pui32 = MexApi::GetInstance().MxGetUint32s(pArr); + const auto pui32 = app_functions->pfn_GetUint32s(pArr); + if (*pui32 > static_cast(numeric_limits::max())) + { + return false; + } + + rv = *pui32; + } + break; + case AppExtensionClassId_Int64: + { + //auto* pi64 = MexApi::GetInstance().MxGetInt64s(pArr); + auto* pi64 = app_functions->pfn_GetInt64s(pArr); + if (*pi64 > static_cast(numeric_limits::max()) || *pi64 < static_cast(numeric_limits::min())) + { + return false; + } + + rv = static_cast(*pi64); + } + break; + case AppExtensionClassId_Uint64: + { + //const auto pui64 = MexApi::GetInstance().MxGetUint64s(pArr); + const auto pui64 = app_functions->pfn_GetUint64s(pArr); + if (*pui64 > (uint64_t)(numeric_limits::max())) + { + return false; + } + + rv = static_cast(*pui64); + } + break; + default: + return false; + } + + if (ptr != nullptr) + { + *ptr = rv; + } + + return true; +} + +/*static*/bool CArgsUtils::TryGetInt32(const Parameter* pArr, size_t index, std::int32_t* ptr, IAppExtensionFunctions* app_functions) +{ + //size_t numOfElements = MexApi::GetInstance().MxGetNumberOfElements(pArr); + size_t numOfElements = app_functions->pfn_GetNumberOfElements(pArr); + if (numOfElements <= index) + { + return false; + } + + //const mxClassID id = MexApi::GetInstance().MxGetClassID(pArr); + const AppExtensionClassId id = app_functions->pfn_GetClassId(pArr); + int rv; + switch (id) + { + case AppExtensionClassId_Double: + { + //auto* const pDbl = MexApi::GetInstance().MxGetDoubles(pArr); + auto* const pDbl = app_functions->pfn_GetDoubles(pArr); + const double v = *(pDbl + index); + if (app_functions->pfn_IsNanOrInfDouble(v) || v > numeric_limits::max() || v < numeric_limits::min()) + { + return false; + } + + rv = lrint(v); + } + break; + case AppExtensionClassId_Single: + { + //auto* const pFlt = MexApi::GetInstance().MxGetSingles(pArr); + auto* const pFlt = app_functions->pfn_GetSingles(pArr); + const float v = *(pFlt + index); + if (app_functions->pfn_IsNanOrInfSingle(v) || v > (float)numeric_limits::max() || v < (float)numeric_limits::min()) + { + return false; + } + + rv = lrintf(v); + } + break; + case AppExtensionClassId_Int8: + { + //auto* const pi8 = MexApi::GetInstance().MxGetInt8s(pArr); + auto* const pi8 = app_functions->pfn_GetInt8s(pArr); + rv = *(pi8 + index); // NOLINT(bugprone-signed-char-misuse) + } + break; + case AppExtensionClassId_Uint8: + { + //auto* const pui8 = MexApi::GetInstance().MxGetUint8s(pArr); + auto* const pui8 = app_functions->pfn_GetUint8s(pArr); + rv = *(pui8 + index); + } + break; + case AppExtensionClassId_Int16: + { + //auto* const pi16 = MexApi::GetInstance().MxGetInt16s(pArr); + auto* const pi16 = app_functions->pfn_GetInt16s(pArr); + rv = *(pi16 + index); + } + break; + case AppExtensionClassId_Uint16: + { + //auto* const pui16 = MexApi::GetInstance().MxGetUint16s(pArr); + auto* const pui16 = app_functions->pfn_GetUint16s(pArr); + rv = *(pui16 + index); + } + break; + case AppExtensionClassId_Int32: + { + //const auto pi32 = MexApi::GetInstance().MxGetInt32s(pArr); + const auto pi32 = app_functions->pfn_GetInt32s(pArr); + rv = *(pi32 + index); + } + break; + case AppExtensionClassId_Uint32: + { + //const auto pui32 = MexApi::GetInstance().MxGetUint32s(pArr); + const auto pui32 = app_functions->pfn_GetUint32s(pArr); + if (*pui32 > static_cast(numeric_limits::max())) + { + return false; + } + + rv = *pui32; + } + break; + case AppExtensionClassId_Int64: + { +// auto* pi64 = MexApi::GetInstance().MxGetInt64s(pArr); + auto* pi64 = app_functions->pfn_GetInt64s(pArr); + int64_t v = *(pi64 + index); + if (v > static_cast(numeric_limits::max()) || v < static_cast(numeric_limits::min())) + { + return false; + } + + rv = static_cast(v); + } + break; + case AppExtensionClassId_Uint64: + { + //const auto pui64 = MexApi::GetInstance().MxGetUint64s(pArr); + const auto pui64 = app_functions->pfn_GetUint64s(pArr); + uint64_t v = *(pui64 + index); + if (v > (uint64_t)(numeric_limits::max())) + { + return false; + } + + rv = static_cast(v); + } + break; + default: + return false; + } + + if (ptr != nullptr) + { + *ptr = rv; + } + + return true; +} + +/*static*/bool CArgsUtils::TryGetSingle(const Parameter* pArr, float* ptr, IAppExtensionFunctions* app_functions) +{ + return CArgsUtils::TryGetSingle(pArr, 0, ptr, app_functions); +} + +/*static*/bool CArgsUtils::TryGetSingle(const Parameter* pArr, size_t index, float* ptr, IAppExtensionFunctions* app_functions) +{ + //size_t numOfElements = MexApi::GetInstance().MxGetNumberOfElements(pArr); + size_t numOfElements = app_functions->pfn_GetNumberOfElements(pArr); + if (numOfElements <= index) + { + return false; + } + + //const mxClassID id = MexApi::GetInstance().MxGetClassID(pArr); + const AppExtensionClassId id = app_functions->pfn_GetClassId(pArr); + float rv; + switch (id) + { + case AppExtensionClassId_Double: + { + //auto* const pDbl = MexApi::GetInstance().MxGetDoubles(pArr); + auto* const pDbl = app_functions->pfn_GetDoubles(pArr); + const double v = *(pDbl + index); + rv = static_cast(v); + } + break; + case AppExtensionClassId_Single: + { + //auto* const pFlt = MexApi::GetInstance().MxGetSingles(pArr); + auto* const pFlt = app_functions->pfn_GetSingles(pArr); + const float v = *(pFlt + index); + rv = v; + } + break; + case AppExtensionClassId_Int8: + { + //auto* const pi8 = MexApi::GetInstance().MxGetInt8s(pArr); + auto* const pi8 = app_functions->pfn_GetInt8s(pArr); + rv = *(pi8 + index); // NOLINT(bugprone-signed-char-misuse) + } + break; + case AppExtensionClassId_Uint8: + { +// auto* const pui8 = MexApi::GetInstance().MxGetUint8s(pArr); + auto* const pui8 = app_functions->pfn_GetUint8s(pArr); + rv = *(pui8 + index); + } + break; + case AppExtensionClassId_Int16: + { + //auto* const pi16 = MexApi::GetInstance().MxGetInt16s(pArr); + auto* const pi16 = app_functions->pfn_GetInt16s(pArr); + rv = *(pi16 + index); + } + break; + case AppExtensionClassId_Uint16: + { + //auto* const pui16 = MexApi::GetInstance().MxGetUint16s(pArr); + auto* const pui16 = app_functions->pfn_GetUint16s(pArr); + rv = *(pui16 + index); + } + break; + case AppExtensionClassId_Int32: + { + //const auto pi32 = MexApi::GetInstance().MxGetInt32s(pArr); + const auto pi32 = app_functions->pfn_GetInt32s(pArr); + rv = *(pi32 + index); + } + break; + case AppExtensionClassId_Uint32: + { + //const auto pui32 = MexApi::GetInstance().MxGetUint32s(pArr); + const auto pui32 = app_functions->pfn_GetUint32s(pArr); + if (*pui32 > static_cast(numeric_limits::max())) + { + return false; + } + + rv = *pui32; + } + break; + case AppExtensionClassId_Int64: + { + //auto* pi64 = MexApi::GetInstance().MxGetInt64s(pArr); + auto* pi64 = app_functions->pfn_GetInt64s(pArr); + int64_t v = *(pi64 + index); + if (v > static_cast(numeric_limits::max()) || v < static_cast(numeric_limits::min())) + { + return false; + } + + rv = static_cast(v); + } + break; + case AppExtensionClassId_Uint64: + { + //const auto pui64 = MexApi::GetInstance().MxGetUint64s(pArr); + const auto pui64 = app_functions->pfn_GetUint64s(pArr); + uint64_t v = *(pui64 + index); + if (v > (uint64_t)(numeric_limits::max())) + { + return false; + } + + rv = static_cast(v); + } + break; + default: + return false; + } + + if (ptr != nullptr) + { + *ptr = rv; + } + + return true; +} + +/*static*/bool CArgsUtils::IsNumericArrayOfMinSize(const Parameter* pArr, size_t minElementCount, IAppExtensionFunctions* app_functions) +{ + //auto mexApi = MexApi::GetInstance(); + //const mxClassID id = mexApi.MxGetClassID(pArr); + const AppExtensionClassId id = app_functions->pfn_GetClassId(pArr); + if (!(id == AppExtensionClassId_Double || + id == AppExtensionClassId_Single || + id == AppExtensionClassId_Int8 || + id == AppExtensionClassId_Uint8 || + id == AppExtensionClassId_Int16 || + id == AppExtensionClassId_Uint16 || + id == AppExtensionClassId_Int32 || + id == AppExtensionClassId_Uint32 || + id == AppExtensionClassId_Int64 || + id == AppExtensionClassId_Uint64)) + { + return false; + } + + //size_t numOfElements = mexApi.MxGetNumberOfElements(pArr); + size_t numOfElements = app_functions->pfn_GetNumberOfElements(pArr); + return numOfElements >= minElementCount; +} + +/*static*/bool CArgsUtils::IsStructure(const Parameter* pArr, IAppExtensionFunctions* app_functions) +{ + return app_functions->pfn_IsStruct(pArr); + /*auto mexApi = MexApi::GetInstance(); + return mexApi.MxIsStruct(pArr);*/ +} + +/*static*/bool CArgsUtils::TryGetIntRect(const Parameter* pArr, libCZI::IntRect* rect, IAppExtensionFunctions* app_functions) +{ + IntRect r; + r.Invalidate(); + if (!CArgsUtils::TryGetInt32(pArr, 0, &r.x, app_functions) || + !CArgsUtils::TryGetInt32(pArr, 1, &r.y, app_functions) || + !CArgsUtils::TryGetInt32(pArr, 2, &r.w, app_functions) || + !CArgsUtils::TryGetInt32(pArr, 3, &r.h, app_functions)) + { + return false; + } + + if (r.w < 0 || r.h < 0) + { + return false; + } + + if (rect != nullptr) + { + *rect = r; + } + + return true; +} + +/*static*/bool CArgsUtils::TryGetDimCoordinate(const Parameter* pArr, libCZI::CDimCoordinate* coord, IAppExtensionFunctions* app_functions) +{ + string argStr; + if (!CArgsUtils::TryGetString(pArr, &argStr, app_functions)) + { + return false; + } + + CDimCoordinate planeCoordinate; + try + { + planeCoordinate = CDimCoordinate::Parse(argStr.c_str()); + } + catch (libCZI::LibCZIStringParseException&) + { + return false; + } + catch (exception&) + { + return false; + } + + if (coord != nullptr) + { + *coord = planeCoordinate; + } + + return true; +} + +/*static*/bool CArgsUtils::TryGetString(const Parameter* pArr, std::string* str, IAppExtensionFunctions* app_functions) +{ + //auto mexApi = MexApi::GetInstance(); + //if (!mexApi.MxIsChar(pArr)) + if (!app_functions->pfn_IsChar(pArr)) + { + return false; + } + + if (str != nullptr) + { + char* utf8_char_array = app_functions->pfn_ConvertToUTF8String(pArr); + string argStr{ utf8_char_array }; + app_functions->pfn_Free(utf8_char_array); + *str = argStr; + } + + return true; +} + +/*static*/bool CArgsUtils::TryGetArrayInfo(const Parameter* pArr, ArrayInfo* array_info, IAppExtensionFunctions* app_functions) +{ + //auto mexApi = MexApi::GetInstance(); + //if (!mexApi.MxIsNumeric(pArr)) + if (!app_functions->pfn_IsNumeric(pArr)) + { + return false; + } + + //if (mexApi.MxIsSparse(pArr)) + if (app_functions->pfn_IsSparse(pArr)) + { + return false; + } + + ArrayInfo info; + + //info.class_id = mexApi.MxGetClassID(pArr); + info.class_id = app_functions->pfn_GetClassId(pArr); + if (!(info.class_id == AppExtensionClassId_Double || + info.class_id == AppExtensionClassId_Single || + info.class_id == AppExtensionClassId_Int8 || + info.class_id == AppExtensionClassId_Uint8 || + info.class_id == AppExtensionClassId_Int16 || + info.class_id == AppExtensionClassId_Uint16 || + info.class_id == AppExtensionClassId_Int32 || + info.class_id == AppExtensionClassId_Uint32 || + info.class_id == AppExtensionClassId_Int64 || + info.class_id == AppExtensionClassId_Uint64)) + { + return false; + } + + //auto number_of_dimensions = mexApi.MxGetNumberOfDimensions(pArr); + auto number_of_dimensions = app_functions->pfn_GetNumberOfDimensions(pArr); + if (number_of_dimensions > ArrayInfo::kMaxDimensions) + { + return false; + } + + info.number_of_dimensions = static_cast(number_of_dimensions); + + //mexApi.GetSizeOfDimensions(pArr, number_of_dimensions, info.dimensions.data()); + app_functions->pfn_GetSizeOfDimensions(pArr, number_of_dimensions, info.dimensions.data()); + + //info.data = mexApi.MxGetData(pArr); + info.data = app_functions->pfn_GetData(pArr); + + if (array_info != nullptr) + { + *array_info = info; + } + + return true; + + /*ArrayInfo info; + info.class_id = mexApi.MxGetClassID(pArr); + info.number_of_dimensions = mexApi.MxGetNumberOfDimensions(pArr); + for (size_t i = 0; i < info.number_of_dimensions; ++i) + { + info.dimensions[i] = mexApi.MxGetDimensions(pArr)[i]; + } + + info.data = mexApi.MxGetData(pArr); + + if (array_info != nullptr) + { + *array_info = info; + } + + return true;*/ +} + +/*static*/bool CArgsUtils::TryGetPixelType(const Parameter* argument, libCZI::PixelType* pixel_type, IAppExtensionFunctions* app_functions) +{ + string string_argument; + if (CArgsUtils::TryGetString(argument, &string_argument, app_functions)) + { + static constexpr struct + { + const char* pixel_type_string; + libCZI::PixelType pixel_type; + } kPixelTypeNames[] = + { + { "gray8", libCZI::PixelType::Gray8 }, + { "gray16", libCZI::PixelType::Gray16 }, + { "bgr24", libCZI::PixelType::Bgr24 } + }; + + for (const auto& item : kPixelTypeNames) + { + if (::Utils::icasecmp(string_argument, item.pixel_type_string)) + { + if (pixel_type != nullptr) + { + *pixel_type = item.pixel_type; + return true; + } + } + } + + return false; + } + + int int_argument; + if (CArgsUtils::TryGetInt32(argument, &int_argument, app_functions)) + { + switch (static_cast(int_argument)) + { + case libCZI::PixelType::Gray8: + case libCZI::PixelType::Gray16: + case libCZI::PixelType::Bgr24: + if (pixel_type != nullptr) + { + *pixel_type = static_cast(int_argument); + } + + return true; + } + + return false; + } + + return false; +} + +/*static*/bool CArgsUtils::TryGetIntValueOfField(const Parameter* argument, const char* field_name, std::int32_t* int_value, IAppExtensionFunctions* app_functions) +{ + //auto mexApi = MexApi::GetInstance(); + //const auto field = mexApi.MxGetField(argument, field_name); + const auto field = app_functions->pfn_GetField(argument, field_name); + if (field == nullptr) + { + return false; + } + + return CArgsUtils::TryGetInt32(field, int_value, app_functions); +} + +/*static*/bool CArgsUtils::TryGetStringValueOfField(const Parameter* argument, const char* field_name, std::string* str, IAppExtensionFunctions* app_functions) +{ + /*auto mexApi = MexApi::GetInstance(); + const auto field = mexApi.MxGetField(argument, field_name);*/ + const auto field = app_functions->pfn_GetField(argument, field_name); + if (field == nullptr) + { + return false; + } + + return CArgsUtils::TryGetString(field, str, app_functions); +} \ No newline at end of file diff --git a/lib/src/implementation/argsutils.h b/lib/src/implementation/argsutils.h new file mode 100644 index 0000000..bb53908 --- /dev/null +++ b/lib/src/implementation/argsutils.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include "inc_libczi.h" + +class MexArray; + +class CArgsUtils +{ +public: + struct ArrayInfo + { + static constexpr size_t kMaxDimensions = 3; + AppExtensionClassId class_id; + std::uint8_t number_of_dimensions; + std::array dimensions; + void* data; + }; + + + static bool TryGetInt32(const Parameter* pArr, std::int32_t* ptr, IAppExtensionFunctions* app_functions); + static bool TryGetInt32(const Parameter* pArr, size_t index, std::int32_t* ptr, IAppExtensionFunctions* app_functions); + + static bool TryGetSingle(const Parameter* pArr, float* ptr, IAppExtensionFunctions* app_functions); + static bool TryGetSingle(const Parameter* pArr, size_t index, float* ptr, IAppExtensionFunctions* app_functions); + + static bool IsNumericArrayOfMinSize(const Parameter* pArr, size_t minElementCount, IAppExtensionFunctions* app_functions); + static bool IsStructure(const Parameter* pArr, IAppExtensionFunctions* app_functions); + + static bool TryGetIntRect(const Parameter* pArr, libCZI::IntRect* rect, IAppExtensionFunctions* app_functions); + + static bool TryGetDimCoordinate(const Parameter* pArr, libCZI::CDimCoordinate* coord, IAppExtensionFunctions* app_functions); + + static bool TryGetArrayInfo(const Parameter* pArr, ArrayInfo* array_info, IAppExtensionFunctions* app_functions); + + static bool TryGetString(const Parameter* pArr, std::string* str, IAppExtensionFunctions* app_functions); + + /// Attempts to interpret the given mxArray as a pixel type. + /// It can be either a string or a scalar integer. + /// + /// \param argument The array. + /// \param [out] pixel_type If non-null and successful, the pixel type is put here. + /// + /// \returns True if it succeeds; false otherwise. + static bool TryGetPixelType(const Parameter* argument, libCZI::PixelType* pixel_type, IAppExtensionFunctions* app_functions); + + static bool TryGetIntValueOfField(const Parameter* argument, const char* field_name, std::int32_t* int_value, IAppExtensionFunctions* app_functions); + + static bool TryGetStringValueOfField(const Parameter* argument, const char* field_name, std::string* str, IAppExtensionFunctions* app_functions); +}; diff --git a/lib/src/implementation/dbgprint.cpp b/lib/src/implementation/dbgprint.cpp new file mode 100644 index 0000000..cd6a8db --- /dev/null +++ b/lib/src/implementation/dbgprint.cpp @@ -0,0 +1,61 @@ +#include "dbgprint.h" +#include +#include + +#if _WIN32API +#include +#endif + +using namespace std; + +void CDbg::vPrint(Level lvl, const char* sz, ...) +{ + char buf[4096]; + va_list ap; + va_start(ap, sz); + vsnprintf(buf, sizeof(buf), sz, ap); + va_end(ap); + CDbg::Print(lvl, buf); +} + +void CDbg::Print(Level lvl, const char* sz) +{ + if (static_cast(lvl) < LIB_LOGLEVEL) + { + return; + } + + stringstream ss; + ss << CDbg::LevelToString(lvl) << ": " << sz; +#if _WIN32API + OutputDebugStringA(ss.str().c_str()); +#else + puts(ss.str().c_str()); +#endif +} + +void CDbg::PrintL(Level lvl, std::function func) +{ + if (static_cast(lvl) < LIB_LOGLEVEL) + { + return; + } + + string txt = func(); + Print(lvl, txt.c_str()); +} + +/*static*/const char* CDbg::LevelToString(Level lvl) +{ + switch (lvl) + { + case Level::Trace: return "Trace"; + case Level::Debug: return "Debug"; + case Level::Info: return "Info"; + case Level::Warn: return "Warn"; + case Level::Error: return "Error"; + case Level::Fatal: return "Fatal"; + } + + return "Invalid"; +} diff --git a/lib/src/implementation/dbgprint.h b/lib/src/implementation/dbgprint.h new file mode 100644 index 0000000..a130e74 --- /dev/null +++ b/lib/src/implementation/dbgprint.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include + +class CDbg +{ +public: + enum class Level + { + Trace = 1, + Debug = 2, + Info = 3, + Warn = 4, + Error = 30, + Fatal = 64 + }; + + static void vPrint(Level lvl, const char* sz, ...); + static void Print(Level lvl, const char* sz); + static void PrintL(Level lvl, std::function func); + + static const char* LevelToString(Level lvl); +}; + +#if LIB_ENABLELOGGING + #define DBGPRINT( x ) do { CDbg::Print x; } while (0) + #define VDBGPRINT( x ) do { CDbg::vPrint x; } while (0) +#else + #define DBGPRINT( x ) + #define VDBGPRINT( x ) +#endif diff --git a/lib/src/implementation/include_rapidjson.h b/lib/src/implementation/include_rapidjson.h new file mode 100644 index 0000000..40b45ef --- /dev/null +++ b/lib/src/implementation/include_rapidjson.h @@ -0,0 +1,6 @@ +#pragma once + +#define RAPIDJSON_HAS_STDSTRING 1 + +#include "rapidjson/rapidjson.h" +#include "rapidjson/document.h" \ No newline at end of file diff --git a/lib/src/implementation/utils.cpp b/lib/src/implementation/utils.cpp new file mode 100644 index 0000000..fc42c65 --- /dev/null +++ b/lib/src/implementation/utils.cpp @@ -0,0 +1,359 @@ +#include "utils.h" +#include +#include +#include +#include +#include "CziReaderManager.h" +#include "CziWriterManager.h" +#include "mexapi.h" +#include "dbgprint.h" +#if _WIN32 +#include +#endif + +using namespace std; + +/*static*/std::string Utils::trim(const std::string& str, const std::string& whitespace /*= " \t"*/) +{ + const auto strBegin = str.find_first_not_of(whitespace); + if (strBegin == std::string::npos) + { + return ""; // no content + } + + const auto strEnd = str.find_last_not_of(whitespace); + const auto strRange = strEnd - strBegin + 1; + + return str.substr(strBegin, strRange); +} + +/*static*/bool Utils::icasecmp(const std::string& l, const std::string& r) +{ + return l.size() == r.size() + && equal(l.cbegin(), l.cend(), r.cbegin(), + [](std::string::value_type l1, std::string::value_type r1) + { return toupper(l1) == toupper(r1); }); +} + +/*static*/std::uint8_t Utils::HexCharToInt(char c) +{ + switch (c) + { + case '0':return 0; + case '1':return 1; + case '2':return 2; + case '3':return 3; + case '4':return 4; + case '5':return 5; + case '6':return 6; + case '7':return 7; + case '8':return 8; + case '9':return 9; + case 'A':case 'a':return 10; + case 'B':case 'b':return 11; + case 'C':case 'c':return 12; + case 'D':case 'd':return 13; + case 'E':case 'e':return 14; + case 'F':case 'f':return 15; + } + + return 0xff; +} + +/*static*/std::wstring Utils::convertUtf8ToWchar_t(const char* sz) +{ +#if _WIN32 + // Get the size of the wide char string we need. + int len = MultiByteToWideChar(CP_UTF8, 0, sz, -1, nullptr, 0); + if (len == 0) + { + return L""; + } + + // Create a wstring to hold the result. + std::wstring conv(len, 0); + + // Perform the actual conversion. + MultiByteToWideChar(CP_UTF8, 0, sz, -1, conv.data(), len); + + // Remove the extra null terminator added by MultiByteToWideChar + conv.resize(len - 1); + return conv; +#else + std::wstring_convert> utf8conv; + std::wstring conv = utf8conv.from_bytes(sz); + return conv; +#endif +} + +/*static*/std::shared_ptr Utils::GetReaderOrThrow(int id) +{ + try + { + return CziReaderManager::GetInstance().GetInstance(id); + } + catch (out_of_range&) + { + VDBGPRINT((CDbg::Level::Warn, "Utils::GetReaderOrThrow: No instance found for id=%i.", id)); + throw invalid_argument("invalid handle specified"); + } +} + +/*static*/std::shared_ptr Utils::GetWriterOrThrow(int id) +{ + try + { + return CziWriterManager::GetInstance().GetInstance(id); + } + catch (out_of_range&) + { + VDBGPRINT((CDbg::Level::Warn, "Utils::GetWriterOrThrow: No instance found for id=%i.", id)); + throw invalid_argument("invalid handle specified"); + } +} + +class Converters +{ +public: + static void Convert_UINT8_to_Gray8(const CArgsUtils::ArrayInfo& array_info, libCZI::IBitmapData* destination) + { + libCZI::ScopedBitmapLockerP destination_locker(destination); + + const uint8_t* src = static_cast(array_info.data); + + for (size_t x = 0; x < array_info.dimensions[1]; ++x) + { + uint8_t* dst = static_cast(destination_locker.ptrDataRoi) + x; + for (size_t y = 0; y < array_info.dimensions[0]; ++y) + { + *dst = *src++; + dst += destination_locker.stride; + } + } + } + + static void Convert_UINT8_3d_to_Bgr24(const CArgsUtils::ArrayInfo& array_info, libCZI::IBitmapData* destination) + { + // dimensions[0] is number of rows, dimensions[1] is number of columns + libCZI::ScopedBitmapLockerP destination_locker(destination); + + const uint8_t* src_b = static_cast(array_info.data); + const uint8_t* src_g = static_cast(array_info.data) + array_info.dimensions[0] * array_info.dimensions[1]; + const uint8_t* src_r = static_cast(array_info.data) + 2 * array_info.dimensions[0] * array_info.dimensions[1]; + + for (size_t x = 0; x < array_info.dimensions[1]; ++x) + { + uint8_t* dst = static_cast(destination_locker.ptrDataRoi) + 3 * x; + for (size_t y = 0; y < array_info.dimensions[0]; ++y) + { + *dst = *src_r++; + *(dst + 1) = *src_g++; + *(dst + 2) = *src_b++; + dst += destination_locker.stride; + } + } + } + + static void Convert_UINT16_to_Gray16(const CArgsUtils::ArrayInfo& array_info, libCZI::IBitmapData* destination) + { + libCZI::ScopedBitmapLockerP destination_locker(destination); + + const uint16_t* src = static_cast(array_info.data); + + for (size_t x = 0; x < array_info.dimensions[1]; ++x) + { + uint16_t* dst = static_cast(destination_locker.ptrDataRoi) + x; + for (size_t y = 0; y < array_info.dimensions[0]; ++y) + { + *dst = *src++; + dst = reinterpret_cast(reinterpret_cast(dst) + destination_locker.stride); + } + } + } + + static void Convert_UINT16_3d_to_Bgr48(const CArgsUtils::ArrayInfo& array_info, libCZI::IBitmapData* destination) + { + libCZI::ScopedBitmapLockerP destination_locker(destination); + + const uint16_t* src_b = static_cast(array_info.data); + const uint16_t* src_g = static_cast(array_info.data) + array_info.dimensions[0] * array_info.dimensions[1]; + const uint16_t* src_r = static_cast(array_info.data) + 2 * array_info.dimensions[0] * array_info.dimensions[1]; + + for (size_t x = 0; x < array_info.dimensions[1]; ++x) + { + uint16_t* dst = static_cast(destination_locker.ptrDataRoi) + 3 * x; + for (size_t y = 0; y < array_info.dimensions[0]; ++y) + { + *dst = *src_r++; + *(dst + 1) = *src_g++; + *(dst + 2) = *src_b++; + dst = reinterpret_cast(reinterpret_cast(dst) + destination_locker.stride); + } + } + } +}; + +/*static*/std::shared_ptr Utils::ConvertToBitmapData(const CArgsUtils::ArrayInfo& array_info, libCZI::PixelType pixel_type) +{ + // check arguments and whether we can deal with the input + if (array_info.number_of_dimensions < 2) + { + throw invalid_argument("array must be at least 2-dimensional"); + } + + if (array_info.number_of_dimensions == 3) + { + if (array_info.dimensions[2] != 3) + { + throw invalid_argument("3rd dimension must be of size 3 (for RGB)"); + } + } + + if (array_info.number_of_dimensions > 3) + { + throw invalid_argument("array must be at most 3-dimensional"); + } + + if (array_info.dimensions[1] > (numeric_limits::max)() || array_info.dimensions[0] > (numeric_limits::max)()) + { + throw invalid_argument("array dimensions are too large"); + } + + static constexpr struct ConversionInfo + { + mxClassID mx_class; + bool has_3rd_dimension; // whether the 3rd dimension is present and has size 3 + libCZI::PixelType pixel_type; + void(*conversion_function)(const CArgsUtils::ArrayInfo&, libCZI::IBitmapData*); + } kConversionTable[] = + { + { mxUINT8_CLASS, false, libCZI::PixelType::Gray8 , Converters::Convert_UINT8_to_Gray8}, + { mxUINT8_CLASS, true, libCZI::PixelType::Bgr24, Converters::Convert_UINT8_3d_to_Bgr24 }, + { mxUINT16_CLASS, false, libCZI::PixelType::Gray16, Converters::Convert_UINT16_to_Gray16 }, + { mxUINT16_CLASS, true, libCZI::PixelType::Bgr48, Converters::Convert_UINT16_3d_to_Bgr48 }, + }; + + const bool has_3rd_dimension = array_info.number_of_dimensions == 3; + for (const auto& conversionInfo : kConversionTable) + { + if (conversionInfo.mx_class == array_info.class_id && conversionInfo.has_3rd_dimension == has_3rd_dimension) + { + auto bitmapData = make_shared(pixel_type, static_cast(array_info.dimensions[1]), static_cast(array_info.dimensions[0])); + conversionInfo.conversion_function(array_info, bitmapData.get()); + return bitmapData; + } + } + + throw invalid_argument("unsupported array type"); +} + +// ---------------------------------------------------------------------------- + +/*static*/mwSize MexUtils::Dims_1_by_1[2] = { 1, 1 }; + +/*static*/MexArray* MexUtils::FloatTo1x1Matrix(float v) +{ + auto m = MexApi::GetInstance().MxCreateNumericMatrix(1, 1, mxSINGLE_CLASS, mxREAL); + float* ptr = MexApi::GetInstance().MxGetSingles(m); + *ptr = v; + return m; +} + +/*static*/MexArray* MexUtils::DoubleTo1x1Matrix(double v) +{ + auto m = MexApi::GetInstance().MxCreateNumericMatrix(1, 1, mxDOUBLE_CLASS, mxREAL); + double* ptr = MexApi::GetInstance().MxGetDoubles(m); + *ptr = MexUtils::CoerceValueDbl(v); + return m; +} + +/*static*/MexArray* MexUtils::DoublesAsNx1Matrix(int count, ...) +{ + auto m = MexApi::GetInstance().MxCreateNumericMatrix(count, 1, mxDOUBLE_CLASS, mxREAL); + double* ptr = MexApi::GetInstance().MxGetDoubles(m); + va_list list; + va_start(list, count); + for (int arg = 0; arg < count; ++arg) + { + *ptr++ = MexUtils::CoerceValueDbl(va_arg(list, double)); + } + + // Cleanup the va_list when we're done. + va_end(list); + + return m; +} + +/*static*/MexArray* MexUtils::Int32To1x1Matrix(int v) +{ + auto* m = MexApi::GetInstance().MxCreateNumericMatrix(1, 1, mxINT32_CLASS, mxREAL); + int* ptr = MexApi::GetInstance().MxGetInt32s(m); + *ptr = v; + return m; +} + +/*static*/MexArray* MexUtils::BooleanTo1x1Matrix(bool b) +{ + auto* m = MexApi::GetInstance().MxCreateNumericMatrix(1, 1, mxLOGICAL_CLASS, mxREAL); + bool* ptr = MexApi::GetInstance().MxGetLogicals(m); + *ptr = b; + return m; +} + +/*static*/double MexUtils::CoerceValueDbl(double d) +{ + if (isnan(d)) + { + return MexApi::GetInstance().GetDblNan(); + } + else if (isinf(d)) + { + return MexApi::GetInstance().GetDblInf(); + } + else + { + return d; + } +} + +// ---------------------------------------------------------------------------- + +Bitmap::Bitmap(libCZI::PixelType pixel_type, std::uint32_t width, std::uint32_t height) + : pixeltype_(pixel_type), width_(width), height_(height) +{ + this->stride_ = width * libCZI::Utils::GetBytesPerPixel(pixel_type); + this->ptrData_ = malloc(static_cast(this->stride_) * height); +} + +Bitmap::~Bitmap() +{ + if (this->ptrData_) + { + free(this->ptrData_); + } +} + +libCZI::PixelType Bitmap::GetPixelType() const +{ + return this->pixeltype_; +} + +libCZI::IntSize Bitmap::GetSize() const +{ + return libCZI::IntSize{ this->width_, this->height_ }; +} + +libCZI::BitmapLockInfo Bitmap::Lock() +{ + libCZI::BitmapLockInfo bitmapLockInfo; + bitmapLockInfo.ptrData = this->ptrData_; + bitmapLockInfo.ptrDataRoi = this->ptrData_; + bitmapLockInfo.stride = this->stride_; + bitmapLockInfo.size = static_cast(this->stride_) * this->height_; + return bitmapLockInfo; +} + +void Bitmap::Unlock() +{ + // nothing to do +} \ No newline at end of file diff --git a/lib/src/implementation/utils.h b/lib/src/implementation/utils.h new file mode 100644 index 0000000..4016402 --- /dev/null +++ b/lib/src/implementation/utils.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include "CziReader.h" +#include "CziWriter.h" +//#include "mexapi.h" +#include +#include "argsutils.h" + +class Utils +{ +public: + static std::string trim(const std::string& str, const std::string& whitespace = " \t"); + static bool icasecmp(const std::string& l, const std::string& r); + static std::uint8_t HexCharToInt(char c); + static std::wstring convertUtf8ToWchar_t(const std::string& str) { return Utils::convertUtf8ToWchar_t(str.c_str()); } + static std::wstring convertUtf8ToWchar_t(const char* sz); + + static std::shared_ptr GetReaderOrThrow(int id); + static std::shared_ptr GetWriterOrThrow(int id); + + static std::shared_ptr ConvertToBitmapData(const CArgsUtils::ArrayInfo& array_info, libCZI::PixelType pixel_type); +}; + +class MexUtils +{ +public: + static size_t Dims_1_by_1[2]; + static Parameter* FloatTo1x1Matrix(float v); + static Parameter* DoubleTo1x1Matrix(double v); + static Parameter* Int32To1x1Matrix(int v); + static Parameter* BooleanTo1x1Matrix(bool b); + static Parameter* DoublesAsNx1Matrix(int count, ...); +private: + static double CoerceValueDbl(double d); +}; + +class Bitmap : public libCZI::IBitmapData +{ +private: + void* ptrData_{ nullptr }; + libCZI::PixelType pixeltype_{ libCZI::PixelType::Invalid }; + std::uint32_t width_{ 0 }; + std::uint32_t height_{ 0 }; + std::uint32_t stride_{ 0 }; +public: + Bitmap(libCZI::PixelType pixel_type, std::uint32_t width, std::uint32_t height); + virtual ~Bitmap(); + virtual libCZI::PixelType GetPixelType() const; + virtual libCZI::IntSize GetSize() const; + virtual libCZI::BitmapLockInfo Lock(); + virtual void Unlock(); +}; \ No newline at end of file