diff --git a/.travis.yml b/.travis.yml index dc96ac8b..8e9b1e45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ cache: before_script: # Install ajv -- npm install ajv-cli -g +- npm install ajv-cli@3.3.0 -g # Install dicom3tools - wget --no-check-certificate http://slicer.kitware.com/midas3/download/item/270308/dicom3tools_macexe_1.00.snapshot.20161218101718.zip -O dicom3tools.zip - tar zxf dicom3tools.zip diff --git a/appveyor.yml b/appveyor.yml index d238d068..85a1ab9d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,7 +21,7 @@ environment: secure: 3+wBc5SxNqx3XjsY3EZ7afvQbLAWWcw30SPGK7q+QPA= before_build: # Install tools required to run DCMQI "doc" tests - - npm install ajv-cli -g + - npm install ajv-cli@3.3.0 -g # Display infos - cmake --version # Downloads diff --git a/doc/examples/seg-example_multiple_segments.json b/doc/examples/seg-example_multiple_segments.json index 03989a58..4b67b21c 100644 --- a/doc/examples/seg-example_multiple_segments.json +++ b/doc/examples/seg-example_multiple_segments.json @@ -27,8 +27,8 @@ "SegmentAlgorithmType": "SEMIAUTOMATIC", "SegmentAlgorithmName": "SlicerEditor", "recommendedDisplayRGBValue": [ - 221, - 130, + 220, + 129, 101 ] } diff --git a/docker/Makefile b/docker/Makefile index 4a142777..ca3e1759 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -54,7 +54,7 @@ prereq.install_npm_packages: prereq.pull_dockcross tar xf node-v6.9.5-linux-x64.tar.xz || true # Install tools required to run DCMQI "doc" tests cd $(ROOT_DIR) && \ - $(TMP)/dockcross bash -c "export PATH=/work/build/node-v6.9.5-linux-x64/bin:$$PATH && sudo npm install ajv-cli -g" + $(TMP)/dockcross bash -c "export PATH=/work/build/node-v6.9.5-linux-x64/bin:$$PATH && sudo npm install ajv-cli@3.3.0 -g" # Configure, build and package dcmqi.generate_package: prereq.pull_dockcross diff --git a/include/dcmqi/ColorUtilities.h b/include/dcmqi/ColorUtilities.h new file mode 100644 index 00000000..5e0fcd89 --- /dev/null +++ b/include/dcmqi/ColorUtilities.h @@ -0,0 +1,103 @@ +#ifndef DCMQI_COLORUTILITIES_H +#define DCMQI_COLORUTILITIES_H + + +using namespace std; + +namespace dcmqi { + +class ColorUtilities { +public: + + static void getIntegerScaledCIELabFromCIELab(int &sL, int &sA, int &sB, float L, float A, float B); + + /** + *
Convert 16 bit fractional integer scaled CIELab values used in ICC profiles and DICOM to floating point.
+ * + *See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
+ * + * @param cieLabScaled array of length 3 containing 16 bit scaled L*,a*,b* values from 0 to 65535 + * return array of length 3 containing L*,a*,b* values with L* from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 + */ + static void getCIELabPCSFromIntegerScaledCIELabPCS(float &L, float &A, float &B, int sL, int sA, int sB); + + /** + *Convert CIEXYZ to CIE 1976 L*, a*, b*.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing L*,a*,b* values + */ + static void getCIELabFromXYZ(float &L, float &A, float &B, float X, float Y, float Z); + + /** + *Convert CIE 1976 L*, a*, b* to CIEXYZ.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing X,Y,Z values + */ + static void getCIEXYZFromLAB(float &X, float &Y, float &Z, float L, float A, float B); + + /** + *Convert RGB values in sRGB to CIEXYZ in ICC PCS.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Reverse Transformation + * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing X,Y,Z values + */ + static void getCIEXYZPCSFromSRGB(float &X, float &Y, float &Z, int R, int G, int B); + + /** + *Convert CIEXYZ in ICC PCS to RGB values in sRGB.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Forward Transformation + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + static void getSRGBFromCIEXYZPCS(int &R, int &G, int &B, float X, float Y, float Z); + + /** + *Convert RGB values in sRGB to CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values + */ + static void getCIELabPCSFromSRGB(float &L, float &A, float &B, int r, int g, int b); + + /** + *Convert RGB values in sRGB to 16 bit fractional integer scaled CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values each from 0 to 65535 + */ + static void getIntegerScaledCIELabPCSFromSRGB(int &sL, int &sA, int &sB, int r, int g, int b); + + /** + *Convert CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + static void getSRGBFromCIELabPCS(int &r, int &g, int &b, float L, float A, float B); + + /** + *Convert 16 bit fractional integer scaled CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLabScaled array of length 3 containing L*,a*,b* values each from 0 to 65535 + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + static void getSRGBFromIntegerScaledCIELabPCS(int &sr, int &sg, int &sb, int sL, int sA, int sB); + +}; +} + +#endif // DCMQI_COLORUTILITIES_H diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index 903bd8d0..ca0db1f0 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -51,13 +51,6 @@ namespace dcmqi { static string toString(const unsigned int& value); - static float *getCIEXYZFromRGB(unsigned *rgb, float *cieXYZ); - static float *getCIEXYZFromCIELab(float *cieLab, float *cieXYZ); - static float *getCIELabFromCIEXYZ(float *cieXYZ, float *cieLab); - static float *getCIELabFromIntegerScaledCIELab(unsigned *cieLabScaled, float *cieLab); - static unsigned *getIntegerScaledCIELabFromCIELab(float *cieLab, unsigned *cieLabScaled); - static unsigned *getRGBFromCIEXYZ(float *cieXYZ, unsigned *rgb); - static CodeSequenceMacro stringToCodeSequenceMacro(string str); static DSRCodedEntryValue stringToDSRCodedEntryValue(string str); diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index c006c733..8bc1f031 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -14,6 +14,7 @@ set(HDRS ${INCLUDE_DIR}/ImageSEGConverter.h ${INCLUDE_DIR}/ParaMapConverter ${INCLUDE_DIR}/Helper.h + ${INCLUDE_DIR}/ColorUtilities.h ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h @@ -26,6 +27,7 @@ set(SRCS ImageSEGConverter.cpp ParaMapConverter.cpp Helper.cpp + ColorUtilities.cpp JSONMetaInformationHandlerBase.cpp JSONParametricMapMetaInformationHandler.cpp JSONSegmentationMetaInformationHandler.cpp diff --git a/libsrc/ColorUtilities.cpp b/libsrc/ColorUtilities.cpp new file mode 100644 index 00000000..929a2103 --- /dev/null +++ b/libsrc/ColorUtilities.cpp @@ -0,0 +1,230 @@ +#include "dcmqi/ColorUtilities.h" + +#includeConvert 16 bit fractional integer scaled CIELab values used in ICC profiles and DICOM to floating point.
+ * + *See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
+ * + * @param cieLabScaled array of length 3 containing 16 bit scaled L*,a*,b* values from 0 to 65535 + * return array of length 3 containing L*,a*,b* values with L* from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 + */ + void ColorUtilities::getCIELabPCSFromIntegerScaledCIELabPCS(float &L, float &A, float &B, int sL, int sA, int sB) { + L = (float)(((double)sL) / 65535 * 100); + A = (float)(((double)sA) / 65535 * 255 - 128); + B = (float)(((double)sB) / 65535 * 255 - 128); +//System.err.println("CIELab scaled ("+cieLabScaled[0]+","+cieLabScaled[1]+","+cieLabScaled[2]+") -> CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+")"); + } + + /** + *Convert CIEXYZ to CIE 1976 L*, a*, b*.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing L*,a*,b* values + */ + void ColorUtilities::getCIELabFromXYZ(float &L, float &A, float &B, float X, float Y, float Z) { + // per http://www.easyrgb.com/index.php?X=MATH&H=07#text7 + + double var_X = X / 95.047; //ref_X = 95.047 Observer= 2°, Illuminant= D65 + double var_Y = Y / 100.000; //ref_Y = 100.000 + double var_Z = Z / 108.883; //ref_Z = 108.883 + + if ( var_X > 0.008856 ) var_X = pow(var_X,1.0/3); + else var_X = ( 7.787 * var_X ) + ( 16.0 / 116 ); + + if ( var_Y > 0.008856 ) var_Y = pow(var_Y,1.0/3); + else var_Y = ( 7.787 * var_Y ) + ( 16.0 / 116 ); + + if ( var_Z > 0.008856 ) var_Z = pow(var_Z,1.0/3); + else var_Z = ( 7.787 * var_Z ) + ( 16.0 / 116 ); + + L = (float)(( 116 * var_Y ) - 16); // CIE-L* + A = (float)(500 * ( var_X - var_Y )); // CIE-a* + B = (float)(200 * ( var_Y - var_Z )); // CIE-b* + +//System.err.println("CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+") -> CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+")"); + } + + /** + *Convert CIE 1976 L*, a*, b* to CIEXYZ.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing X,Y,Z values + */ + void ColorUtilities::getCIEXYZFromLAB(float &X, float &Y, float &Z, float L, float A, float B) { + // per http://www.easyrgb.com/index.php?X=MATH&H=08#text8 + + double var_Y = ( L + 16 ) / 116; + double var_X = A / 500 + var_Y; + double var_Z = var_Y - B / 200; + +//System.err.println("var_Y = "+var_Y); +//System.err.println("var_X = "+var_X); +//System.err.println("var_Z = "+var_Z); + + double var_Y_pow3 = pow(var_Y,3.0); + double var_X_pow3 = pow(var_X,3.0); + double var_Z_pow3 = pow(var_Z,3.0); + +//System.err.println("var_Y_pow3 = "+var_Y_pow3); +//System.err.println("var_X_pow3 = "+var_X_pow3); +//System.err.println("var_Z_pow3 = "+var_Z_pow3); + + if (var_Y_pow3 > 0.008856) var_Y = var_Y_pow3; + else var_Y = ( var_Y - 16 / 116 ) / 7.787; + + if (var_X_pow3 > 0.008856) var_X = var_X_pow3; + else var_X = ( var_X - 16 / 116 ) / 7.787; + + if (var_Z_pow3 > 0.008856) var_Z = var_Z_pow3; + else var_Z = ( var_Z - 16 / 116 ) / 7.787; + +//System.err.println("var_Y = "+var_Y); +//System.err.println("var_X = "+var_X); +//System.err.println("var_Z = "+var_Z); + + X = (float)(95.047 * var_X); //ref_X = 95.047 Observer= 2°, Illuminant= D65 + Y = (float)(100.000 * var_Y); //ref_Y = 100.000 + Z = (float)(108.883 * var_Z); //ref_Z = 108.883 + +//System.err.println("CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+") -> CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+")"); + } + + /** + *Convert RGB values in sRGB to CIEXYZ in ICC PCS.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Reverse Transformation + * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing X,Y,Z values + */ + void ColorUtilities::getCIEXYZPCSFromSRGB(float &X, float &Y, float &Z, int R, int G, int B) { + // per http://www.easyrgb.com/index.php?X=MATH&H=02#text2 + + double var_R = ((double)R)/255.0; + double var_G = ((double)G)/255.0; + double var_B = ((double)B)/255.0; + + if ( var_R > 0.04045 ) var_R = pow((var_R+0.055)/1.055,2.4); + else var_R = var_R / 12.92; + + if ( var_G > 0.04045 ) var_G = pow((var_G+0.055)/1.055,2.4); + else var_G = var_G / 12.92; + + if ( var_B > 0.04045 ) var_B = pow((var_B+0.055)/1.055,2.4); + else var_B = var_B / 12.92; + + var_R = var_R * 100; + var_G = var_G * 100; + var_B = var_B * 100; + + // SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50 + X = (float)(var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805); + Y = (float)(var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722); + Z = (float)(var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505); + +//System.err.println("RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+") -> CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+")"); + } + + /** + *Convert CIEXYZ in ICC PCS to RGB values in sRGB.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Forward Transformation + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + void ColorUtilities::getSRGBFromCIEXYZPCS(int &R, int &G, int &B, float X, float Y, float Z) { + // per http://www.easyrgb.com/index.php?X=MATH&H=01#text1 + + double var_X = X / 100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65) + double var_Y = Y / 100; //Y from 0 to 100.000 + double var_Z = Z / 100; //Z from 0 to 108.883 + + double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986; + double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415; + double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570; + + if ( var_R > 0.0031308 ) var_R = 1.055 * pow(var_R,1/2.4) - 0.055; + else var_R = 12.92 * var_R; + + if ( var_G > 0.0031308 ) var_G = 1.055 * pow(var_G,1/2.4) - 0.055; + else var_G = 12.92 * var_G; + + if ( var_B > 0.0031308 ) var_B = 1.055 * pow(var_B,1/2.4) - 0.055; + else var_B = 12.92 * var_B; + + R = (int)round(var_R * 255); + G = (int)round(var_G * 255); + B = (int)round(var_B * 255); +//System.err.println("CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+") -> RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+")"); + } + + /** + *Convert RGB values in sRGB to CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values + */ + void ColorUtilities::getCIELabPCSFromSRGB(float &L, float &A, float &B, int r, int g, int b) { + float X, Y, Z; + ColorUtilities::getCIEXYZPCSFromSRGB(X, Y, Z, r, g, b); + ColorUtilities::getCIELabFromXYZ(L, A, B, X, Y, Z); + } + + /** + *Convert RGB values in sRGB to 16 bit fractional integer scaled CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values each from 0 to 65535 + */ + void ColorUtilities::getIntegerScaledCIELabPCSFromSRGB(int &sL, int &sA, int &sB, int r, int g, int b) { + float L, A, B; + ColorUtilities::getCIELabPCSFromSRGB(L, A, B, r, g, b); + ColorUtilities::getIntegerScaledCIELabFromCIELab(sL, sA, sB, L, A, B); + } + + /** + *Convert CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + void ColorUtilities::getSRGBFromCIELabPCS(int &r, int &g, int &b, float L, float A, float B) { + float X, Y, Z; + ColorUtilities::getCIEXYZFromLAB(X, Y, Z, L, A, B); + ColorUtilities::getSRGBFromCIEXYZPCS(r, g, b, X, Y, Z); + } + + /** + *Convert 16 bit fractional integer scaled CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLabScaled array of length 3 containing L*,a*,b* values each from 0 to 65535 + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + void ColorUtilities::getSRGBFromIntegerScaledCIELabPCS(int &sr, int &sg, int &sb, int sL, int sA, int sB) { + float L, A, B; + ColorUtilities::getCIELabPCSFromIntegerScaledCIELabPCS(L, A, B, sL, sA, sB); + ColorUtilities::getSRGBFromCIELabPCS(sr, sg, sb, L, A, B); + } + +} diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 54f77b23..f2ee32f2 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -159,203 +159,6 @@ namespace dcmqi { return oss.str(); } - /** -Convert RGB values in sRGB to CIEXYZ in ICC PCS.
- -SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
- - @see Wikipedia SRGB Reverse Transformation - - @param rgb array of length 3 containing R,G,B values each from 0 to 255 - return array of length 3 containing X,Y,Z values - **/ - float *Helper::getCIEXYZFromRGB(unsigned *rgb, float *cieXYZ) { - double var_R = ((double) rgb[0]) / 255.0; - double var_G = ((double) rgb[1]) / 255.0; - double var_B = ((double) rgb[2]) / 255.0; - - if (var_R > 0.04045) - var_R = pow((var_R + 0.055) / 1.055, 2.4); - else - var_R = var_R / 12.92; - - if (var_G > 0.04045) - var_G = pow((var_G + 0.055) / 1.055, 2.4); - else - var_G = var_G / 12.92; - - if (var_B > 0.04045) - var_B = pow((var_B + 0.055) / 1.055, 2.4); - else - var_B = var_B / 12.92; - - var_R = var_R * 100; - var_G = var_G * 100; - var_B = var_B * 100; - - // SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50 - cieXYZ[0] = (float) (var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805); - cieXYZ[1] = (float) (var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722); - cieXYZ[2] = (float) (var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505); - - return cieXYZ; - } - - /** -Convert CIEXYZ in ICC PCS to RGB values in sRGB.
- -SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
- - @see Wikipedia SRGB Forward Transformation - - @param cieXYZ array of length 3 containing X,Y,Z values - return array of length 3 containing R,G,B values each from 0 - - **/ - unsigned *Helper::getRGBFromCIEXYZ(float *cieXYZ, unsigned *rgb) { - // per http://www.easyrgb.com/index.php?X=MATH&H=01#text1 - double var_X = cieXYZ[0] / 100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65) - double var_Y = cieXYZ[1] / 100; //Y from 0 to 100.000 - double var_Z = cieXYZ[2] / 100; //Z from 0 to 108.883 - - double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986; - double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415; - double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570; - - if (var_R > 0.0031308) - var_R = 1.055 * pow(var_R, 1 / 2.4) - 0.055; - else - var_R = 12.92 * var_R; - - if (var_G > 0.0031308) - var_G = 1.055 * pow(var_G, 1 / 2.4) - 0.055; - else - var_G = 12.92 * var_G; - - if (var_B > 0.0031308) - var_B = 1.055 * pow(var_B, 1 / 2.4) - 0.055; - else - var_B = 12.92 * var_B; - - rgb[0] = (int) floor(var_R * 255. + .5); - rgb[1] = (int) floor(var_G * 255. + .5); - rgb[2] = (int) floor(var_B * 255. + .5); - //System.err.println("CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+") -> RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+")"); - return rgb; - } - - /** -Convert CIEXYZ to CIE 1976 L*, a*, b*.
- - @see Wikipedia CIELAB-CIEXYZ_conversions - - @param cieXYZ array of length 3 containing X,Y,Z values - return array of length 3 containing L*,a*,b* values - **/ - float *Helper::getCIELabFromCIEXYZ(float *cieXYZ, float *cieLab) { - double var_X = cieXYZ[0] / 95.047; //ref_X = 95.047 Observer= 2°, Illuminant= D65 - double var_Y = cieXYZ[1] / 100.000; //ref_Y = 100.000 - double var_Z = cieXYZ[2] / 108.883; //ref_Z = 108.883 - - if (var_X > 0.008856) - var_X = pow(var_X, 1.0 / 3); - else - var_X = (7.787 * var_X) + (16.0 / 116); - - if (var_Y > 0.008856) - var_Y = pow(var_Y, 1.0 / 3); - else - var_Y = (7.787 * var_Y) + (16.0 / 116); - - if (var_Z > 0.008856) - var_Z = pow(var_Z, 1.0 / 3); - else - var_Z = (7.787 * var_Z) + (16.0 / 116); - - cieLab[0] = (float) ((116 * var_Y) - 16); // CIE-L* - cieLab[1] = (float) (500 * (var_X - var_Y)); // CIE-a* - cieLab[2] = (float) (200 * (var_Y - var_Z)); // CIE-b* - - return cieLab; - } - - /** -Convert CIE 1976 L*, a*, b* to CIEXYZ.
- - @see Wikipedia CIELAB-CIEXYZ_conversions - - @param cieLab array of length 3 containing L*,a*,b* values - return array of length 3 containing X,Y,Z values - **/ - float *Helper::getCIEXYZFromCIELab(float *cieLab, float *cieXYZ) { - // per http://www.easyrgb.com/index.php?X=MATH&H=08#text8 - - double var_Y = (cieLab[0] + 16) / 116; - double var_X = cieLab[1] / 500 + var_Y; - double var_Z = var_Y - cieLab[2] / 200; - - double var_Y_pow3 = pow(var_Y, 3.0); - double var_X_pow3 = pow(var_X, 3.0); - double var_Z_pow3 = pow(var_Z, 3.0); - - if (var_Y_pow3 > 0.008856) - var_Y = var_Y_pow3; - else - var_Y = (var_Y - 16. / 116) / 7.787; - - if (var_X_pow3 > 0.008856) - var_X = var_X_pow3; - else - var_X = (var_X - 16. / 116) / 7.787; - - if (var_Z_pow3 > 0.008856) - var_Z = var_Z_pow3; - else - var_Z = (var_Z - 16. / 116) / 7.787; - - cieXYZ[0] = (float) (95.047 * var_X); //ref_X = 95.047 Observer= 2°, Illuminant= D65 - cieXYZ[1] = (float) (100.000 * var_Y); //ref_Y = 100.000 - cieXYZ[2] = (float) (108.883 * var_Z); //ref_Z = 108.883 - - return cieXYZ; - } - - /** - *Convert floating point CIELab values to the 16 bit fractional - * integer scaled representation used in ICC profiles and DICOM.
- * - *See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
- * - * @param cieLab array of length 3 containing L*,a*,b* values with L* - * from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 - * return array of length 3 containing 16 bit scaled L*,a*,b* - * values from 0 to 65535 - **/ - unsigned *Helper::getIntegerScaledCIELabFromCIELab(float *cieLab, unsigned *cieLabScaled) { - // per PS 3.3 C.10.7.1.1 ... scale same way as ICC profile encodes them - cieLabScaled[0] = (int) floor(cieLab[0] * 65535. / 100. + .5); - cieLabScaled[1] = (int) floor((cieLab[1] + 128) * 65535. / 255. + .5); - cieLabScaled[2] = (int) floor((cieLab[2] + 128) * 65535. / 255. + .5); - return cieLabScaled; - } - - /** -Convert 16 bit fractional integer scaled CIELab values used in ICC profiles and DICOM to floating point.
- -See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
- - @param cieLabScaled array of length 3 containing 16 bit scaled L*,a*,b* values from 0 to 65535 - return array of length 3 containing L*,a*,b* values with L* from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 - **/ - float *Helper::getCIELabFromIntegerScaledCIELab(unsigned *cieLabScaled, float *cieLab) { - cieLab[0] = (float) (((double) cieLabScaled[0]) / 65535 * 100); - cieLab[1] = (float) (((double) cieLabScaled[1]) / 65535 * 255 - 128); - cieLab[2] = (float) (((double) cieLabScaled[2]) / 65535 * 255 - 128); - return cieLab; - } - CodeSequenceMacro Helper::stringToCodeSequenceMacro(string str) { string tail, code, designator, meaning; splitString(str, code, tail, ","); diff --git a/libsrc/ImageSEGConverter.cpp b/libsrc/ImageSEGConverter.cpp index f1ce1d07..1371d708 100644 --- a/libsrc/ImageSEGConverter.cpp +++ b/libsrc/ImageSEGConverter.cpp @@ -1,9 +1,12 @@ // DCMQI includes #include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/ColorUtilities.h" + //DCMTK includes #includeVarious static methods helpful for color conversions.
+ * + * @author dclunie + */ +public class ColorUtilities { + private static final String identString = "@(#) $Header: /userland/cvs/pixelmed/imgbook/com/pixelmed/utils/ColorUtilities.java,v 1.10 2020/01/01 15:48:26 dclunie Exp $"; + + private ColorUtilities() {} + + /** + *Convert floating point CIELab values to the 16 bit fractional integer scaled representation used in ICC profiles and DICOM.
+ * + *See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
+ * + * @param cieLab array of length 3 containing L*,a*,b* values with L* from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 + * return array of length 3 containing 16 bit scaled L*,a*,b* values from 0 to 65535 + */ + public static int[] getIntegerScaledCIELabFromCIELab(float[] cieLab) { + int[] cieLabScaled = new int[3]; + // per PS 3.3 C.10.7.1.1 ... scale same way as ICC profile encodes them + cieLabScaled[0] = (int)Math.round (cieLab[0] * 65535 / 100); + cieLabScaled[1] = (int)Math.round((cieLab[1] + 128) * 65535 / 255); + cieLabScaled[2] = (int)Math.round((cieLab[2] + 128) * 65535 / 255); +//System.err.println("CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+") -> CIELab scaled ("+cieLabScaled[0]+","+cieLabScaled[1]+","+cieLabScaled[2]+")"); + return cieLabScaled; + } + + /** + *Convert 16 bit fractional integer scaled CIELab values used in ICC profiles and DICOM to floating point.
+ * + *See ICC v4.3 Tables 12 and 13, and DICOM PS 3.3 C.10.7.1.1.
+ * + * @param cieLabScaled array of length 3 containing 16 bit scaled L*,a*,b* values from 0 to 65535 + * return array of length 3 containing L*,a*,b* values with L* from 0.0 to 100.0, and a* and b* from -128.0 to +127.0 + */ + public static float[] getCIELabPCSFromIntegerScaledCIELabPCS(int[] cieLabScaled) { + float[] cieLab = new float[3]; + cieLab[0] = (float)(((double)cieLabScaled[0]) / 65535 * 100); + cieLab[1] = (float)(((double)cieLabScaled[1]) / 65535 * 255 - 128); + cieLab[2] = (float)(((double)cieLabScaled[2]) / 65535 * 255 - 128); +//System.err.println("CIELab scaled ("+cieLabScaled[0]+","+cieLabScaled[1]+","+cieLabScaled[2]+") -> CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+")"); + return cieLab; + } + + /** + *Convert CIEXYZ to CIE 1976 L*, a*, b*.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing L*,a*,b* values + */ + public static float[] getCIELabFromXYZ(float[] cieXYZ) { + // per http://www.easyrgb.com/index.php?X=MATH&H=07#text7 + + double var_X = cieXYZ[0] / 95.047; //ref_X = 95.047 Observer= 2°, Illuminant= D65 + double var_Y = cieXYZ[1] / 100.000; //ref_Y = 100.000 + double var_Z = cieXYZ[2] / 108.883; //ref_Z = 108.883 + + if ( var_X > 0.008856 ) var_X = Math.pow(var_X,1.0/3); + else var_X = ( 7.787 * var_X ) + ( 16.0 / 116 ); + + if ( var_Y > 0.008856 ) var_Y = Math.pow(var_Y,1.0/3); + else var_Y = ( 7.787 * var_Y ) + ( 16.0 / 116 ); + + if ( var_Z > 0.008856 ) var_Z = Math.pow(var_Z,1.0/3); + else var_Z = ( 7.787 * var_Z ) + ( 16.0 / 116 ); + + float[] cieLab = new float[3]; + cieLab[0] = (float)(( 116 * var_Y ) - 16); // CIE-L* + cieLab[1] = (float)(500 * ( var_X - var_Y )); // CIE-a* + cieLab[2] = (float)(200 * ( var_Y - var_Z )); // CIE-b* + +//System.err.println("CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+") -> CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+")"); + return cieLab; + } + + /** + *Convert CIE 1976 L*, a*, b* to CIEXYZ.
+ * + * @see Wikipedia CIELAB-CIEXYZ_conversions + * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing X,Y,Z values + */ + public static float[] getCIEXYZFromLAB(float[] cieLab) { + // per http://www.easyrgb.com/index.php?X=MATH&H=08#text8 + + double var_Y = ( cieLab[0] + 16 ) / 116; + double var_X = cieLab[1] / 500 + var_Y; + double var_Z = var_Y - cieLab[2] / 200; + +//System.err.println("var_Y = "+var_Y); +//System.err.println("var_X = "+var_X); +//System.err.println("var_Z = "+var_Z); + + double var_Y_pow3 = Math.pow(var_Y,3.0); + double var_X_pow3 = Math.pow(var_X,3.0); + double var_Z_pow3 = Math.pow(var_Z,3.0); + +//System.err.println("var_Y_pow3 = "+var_Y_pow3); +//System.err.println("var_X_pow3 = "+var_X_pow3); +//System.err.println("var_Z_pow3 = "+var_Z_pow3); + + if (var_Y_pow3 > 0.008856) var_Y = var_Y_pow3; + else var_Y = ( var_Y - 16d / 116 ) / 7.787; + + if (var_X_pow3 > 0.008856) var_X = var_X_pow3; + else var_X = ( var_X - 16d / 116 ) / 7.787; + + if (var_Z_pow3 > 0.008856) var_Z = var_Z_pow3; + else var_Z = ( var_Z - 16d / 116 ) / 7.787; + +//System.err.println("var_Y = "+var_Y); +//System.err.println("var_X = "+var_X); +//System.err.println("var_Z = "+var_Z); + + float[] cieXYZ = new float[3]; + cieXYZ[0] = (float)(95.047 * var_X); //ref_X = 95.047 Observer= 2°, Illuminant= D65 + cieXYZ[1] = (float)(100.000 * var_Y); //ref_Y = 100.000 + cieXYZ[2] = (float)(108.883 * var_Z); //ref_Z = 108.883 + +//System.err.println("CIELab ("+cieLab[0]+","+cieLab[1]+","+cieLab[2]+") -> CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+")"); + return cieXYZ; + } + + /** + *Convert RGB values in sRGB to CIEXYZ in ICC PCS.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Reverse Transformation + * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing X,Y,Z values + */ + public static float[] getCIEXYZPCSFromSRGB(int[] rgb) { + // per http://www.easyrgb.com/index.php?X=MATH&H=02#text2 + + double var_R = ((double)rgb[0])/255.0; + double var_G = ((double)rgb[1])/255.0; + double var_B = ((double)rgb[2])/255.0; + + if ( var_R > 0.04045 ) var_R = Math.pow((var_R+0.055)/1.055,2.4); + else var_R = var_R / 12.92; + + if ( var_G > 0.04045 ) var_G = Math.pow((var_G+0.055)/1.055,2.4); + else var_G = var_G / 12.92; + + if ( var_B > 0.04045 ) var_B = Math.pow((var_B+0.055)/1.055,2.4); + else var_B = var_B / 12.92; + + var_R = var_R * 100; + var_G = var_G * 100; + var_B = var_B * 100; + + float[] cieXYZ = new float[3]; + + // SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50 + cieXYZ[0] = (float)(var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805); + cieXYZ[1] = (float)(var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722); + cieXYZ[2] = (float)(var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505); + +//System.err.println("RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+") -> CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+")"); + return cieXYZ; + } + + /** + *Convert CIEXYZ in ICC PCS to RGB values in sRGB.
+ * + *SRGB Observer = 2°, Illuminant = D65, XYZ PCS Illuminant = D50.
+ * + * @see Wikipedia SRGB Forward Transformation + * + * @param cieXYZ array of length 3 containing X,Y,Z values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + public static int[] getSRGBFromCIEXYZPCS(float[] cieXYZ) { + // per http://www.easyrgb.com/index.php?X=MATH&H=01#text1 + + double var_X = cieXYZ[0] / 100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65) + double var_Y = cieXYZ[1] / 100; //Y from 0 to 100.000 + double var_Z = cieXYZ[2] / 100; //Z from 0 to 108.883 + + double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986; + double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415; + double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570; + + if ( var_R > 0.0031308 ) var_R = 1.055 * Math.pow(var_R,1/2.4) - 0.055; + else var_R = 12.92 * var_R; + + if ( var_G > 0.0031308 ) var_G = 1.055 * Math.pow(var_G,1/2.4) - 0.055; + else var_G = 12.92 * var_G; + + if ( var_B > 0.0031308 ) var_B = 1.055 * Math.pow(var_B,1/2.4) - 0.055; + else var_B = 12.92 * var_B; + + int[] rgb = new int[3]; + rgb[0] = (int)Math.round(var_R * 255); + rgb[1] = (int)Math.round(var_G * 255); + rgb[2] = (int)Math.round(var_B * 255); +//System.err.println("CIEXYZ ("+cieXYZ[0]+","+cieXYZ[1]+","+cieXYZ[2]+") -> RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+")"); + return rgb; + } + + /** + *Convert RGB values in sRGB to CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values + */ + public static float[] getCIELabPCSFromSRGB(int[] rgb) { + return getCIELabFromXYZ(getCIEXYZPCSFromSRGB(rgb)); + } + + /** + *Convert RGB values in sRGB to 16 bit fractional integer scaled CIELab in ICC PCS.
+ * + * @param rgb array of length 3 containing R,G,B values each from 0 to 255 + * return array of length 3 containing L*,a*,b* values each from 0 to 65535 + */ + public static int[] getIntegerScaledCIELabPCSFromSRGB(int[] rgb) { + return getIntegerScaledCIELabFromCIELab(getCIELabPCSFromSRGB(rgb)); + } + + /** + *Convert CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLab array of length 3 containing L*,a*,b* values + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + public static int[] getSRGBFromCIELabPCS(float[] cieLab) { + return getSRGBFromCIEXYZPCS(getCIEXYZFromLAB(cieLab)); + } + + /** + *Convert 16 bit fractional integer scaled CIELab in ICC PCS to RGB values in sRGB.
+ * + * @param cieLabScaled array of length 3 containing L*,a*,b* values each from 0 to 65535 + * return array of length 3 containing R,G,B values each from 0 to 255 + */ + public static int[] getSRGBFromIntegerScaledCIELabPCS(int[] cieLabScaled) { + return getSRGBFromCIELabPCS(getCIELabPCSFromIntegerScaledCIELabPCS(cieLabScaled)); + } + + + /** + *Convert color values
+ * + * @param arg sRGB8toCIELab16 or CIELab16tosRGB8 (case insensitive) and three color values (each decimal or 0xhex) + */ + public static void main(String arg[]) { + boolean bad = true; + try { + if (arg.length == 4) { + int[] input = new int[3]; + input[0] = arg[1].startsWith("0x") ? Integer.parseInt(arg[1].replaceFirst("0x",""),16) : Integer.parseInt(arg[1]); + input[1] = arg[2].startsWith("0x") ? Integer.parseInt(arg[2].replaceFirst("0x",""),16) : Integer.parseInt(arg[2]); + input[2] = arg[3].startsWith("0x") ? Integer.parseInt(arg[3].replaceFirst("0x",""),16) : Integer.parseInt(arg[3]); + int[] output = null; + String inputType = null; + String outputType = null; + if (arg[0].toLowerCase(java.util.Locale.US).equals("sRGB8toCIELab16".toLowerCase(java.util.Locale.US))) { + output = getIntegerScaledCIELabPCSFromSRGB(input); + inputType = "sRGB8"; + outputType = "CIELab16"; + bad = false; + } + else if (arg[0].toLowerCase(java.util.Locale.US).equals("CIELab16tosRGB8".toLowerCase(java.util.Locale.US))) { + output = getSRGBFromIntegerScaledCIELabPCS(input); + inputType = "CIELab16"; + outputType = "sRGB8"; + bad = false; + } + else { + System.err.println("Unrecognized conversion type "+arg[0]); + } + if (output != null) { + System.err.println(inputType+": "+input[0]+" "+input[1]+" "+input[2]+" (dec) (0x"+Integer.toHexString(input[0])+" 0x"+Integer.toHexString(input[1])+" 0x"+Integer.toHexString(input[2])+")"); + System.err.println(outputType+": "+output[0]+" "+output[1]+" "+output[2]+" (dec) (0x"+Integer.toHexString(output[0])+" 0x"+Integer.toHexString(output[1])+" 0x"+Integer.toHexString(output[2])+")"); + } + } + else { + System.err.println("Error: incorrect number of arguments"); + } + } catch (Exception e) { + e.printStackTrace(System.err); // no need to use SLF4J since command line utility/test + } + if (bad) { + System.err.println("Usage: ColorUtilities sRGB8toCIELab16|CIELab16tosRGB8 R|L G|a B|b"); + } + } +} + + diff --git a/util/TestColorConversions_SRGB_CIELabPCS.java b/util/TestColorConversions_SRGB_CIELabPCS.java new file mode 100644 index 00000000..1485cfd8 --- /dev/null +++ b/util/TestColorConversions_SRGB_CIELabPCS.java @@ -0,0 +1,75 @@ +/* Copyright (c) 2001-2020, David A. Clunie DBA Pixelmed Publishing. All rights reserved. */ + +package com.pixelmed.test; + +import com.pixelmed.utils.ColorUtilities; + +import junit.framework.*; + +public class TestColorConversions_SRGB_CIELabPCS extends TestCase { + + // constructor to support adding tests to suite ... + + public TestColorConversions_SRGB_CIELabPCS(String name) { + super(name); + } + + // add tests to suite manually, rather than depending on default of all test...() methods + // in order to allow adding TestColorConversions_SRGB_CIELabPCS.suite() in AllTests.suite() + // see Johannes Link. Unit Testing in Java pp36-47 + + public static Test suite() { + TestSuite suite = new TestSuite("TestColorConversions_SRGB_CIELabPCS"); + + suite.addTest(new TestColorConversions_SRGB_CIELabPCS("TestColorConversions_SRGB_CIELabPCS_SpecificValues")); + suite.addTest(new TestColorConversions_SRGB_CIELabPCS("TestColorConversions_SRGB_CIELabPCS_WhitePoint")); + + return suite; + } + + protected void setUp() { + } + + protected void tearDown() { + } + + int[][] testRGBValues = { + { 0,0,0 }, + { 255,0,0 }, + { 0,255,0 }, + { 0,0,255 }, + { 255,255,0 }, + { 0,255,255 }, + { 255,0,255 }, + { 255,255,255 }, + { 225,190,150 }, + { 200,200,200 }, + { 128,174,128 }, + { 221,130,101 } + }; + + public void TestColorConversions_SRGB_CIELabPCS_SpecificValues() throws Exception { + for (int[] rgb : testRGBValues) { + int[] lab = ColorUtilities.getIntegerScaledCIELabPCSFromSRGB(rgb); +System.err.println("TestColorConversions_SRGB_CIELabPCS_SpecificValues(): RGB ("+rgb[0]+","+rgb[1]+","+rgb[2]+") = Lab ("+lab[0]+","+lab[1]+","+lab[2]+")"); + int[] rgbRoundTrip = ColorUtilities.getSRGBFromIntegerScaledCIELabPCS(lab); + assertEquals("Checking round trip r",rgb[0],rgbRoundTrip[0]); + assertEquals("Checking round trip g",rgb[1],rgbRoundTrip[1]); + assertEquals("Checking round trip b",rgb[2],rgbRoundTrip[2]); + } + } + + // ICC v4.3 Table 14 + + public void TestColorConversions_SRGB_CIELabPCS_WhitePoint() throws Exception { + { + float[] lab = { 100f,0f,0f }; + int[] scaledExpect = { 0xffff,0x8080,0x8080 }; + int[] scale = ColorUtilities.getIntegerScaledCIELabFromCIELab(lab); + assertEquals("Checking scaling L"+lab[0],scaledExpect[0],scale[0]); + assertEquals("Checking scaling a"+lab[1],scaledExpect[1],scale[1]); + assertEquals("Checking scaling b"+lab[2],scaledExpect[2],scale[2]); + } + } + +}