/*========================================================================= Program: FusionViewer Module: $RCSfile: Image.cpp,v $ Language: C++ Date: $Date: 2008/01/11 20:29:35 $ Version: $Revision: 1.2 $ Copyright (c) Insightful Corporation. All rights reserved. See Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ //\class Image //\brief Implementations of native methods in the Image class. #include "stdafx.h" #include #include #include "org_fusionviewer_display_Image.h" #include "ImageProvider.h" #include "ItkImageProvider.h" //libraries for for DICOM Series Display #include "itkGDCMImageIO.h" #include "itkGDCMSeriesFileNames.h" ImageProvider *getImageReference(JNIEnv *env, jobject obj); void setImageReference(JNIEnv *env, jobject obj, ImageProvider *image); bool throwException(JNIEnv *env, const char *type, const char *message); bool throwNullPointerException(JNIEnv *env, const char *message); bool throwIllegalArgumentException(JNIEnv *env, const char *message); #define IMAGE_NATIVE(func) Java_org_fusionviewer_display_Image_##func const int kNumComponents = 3; // number of components in the colormap // Java Image.createFromFile JNIEXPORT jboolean JNICALL IMAGE_NATIVE(createFromFile) (JNIEnv *env, jobject obj, jstring jstr) { int status = 1; try { // Open the file const char *filename = env->GetStringUTFChars(jstr, 0); ImageProvider *image = ItkImageProvider::OpenFile(filename); env->ReleaseStringUTFChars(jstr, filename); setImageReference(env, obj, image); // Fill in image information fields jclass cls = env->GetObjectClass(obj); jintArray jsizeArray = env->NewIntArray(3); jint *jsize = env->GetIntArrayElements(jsizeArray, NULL); for (int i = 0; i < 3; i++) jsize[i] = image->GetSize(i); env->ReleaseIntArrayElements(jsizeArray, jsize, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_dimensions", "[I"), jsizeArray); jfloatArray jspacingArray = env->NewFloatArray(3); jfloat *jspacing = env->GetFloatArrayElements(jspacingArray, NULL); for (int i = 0; i < 3; i++) jspacing[i] = image->GetPixelSpacing(i); env->ReleaseFloatArrayElements(jspacingArray, jspacing, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_pixelSpacing", "[F"), jspacingArray); env->SetDoubleField(obj, env->GetFieldID(cls, "m_minValue", "D"), image->GetMinValue()); env->SetDoubleField(obj, env->GetFieldID(cls, "m_maxValue", "D"), image->GetMaxValue()); env->SetBooleanField(obj, env->GetFieldID(cls, "m_isByteType", "Z"), image->IsByteType()); } catch (...) { status = 0; } return status; } // Java Image.createDICOMFromFile JNIEXPORT jboolean JNICALL IMAGE_NATIVE(createDICOMFromFile) (JNIEnv *env, jobject obj, jstring jpath, jstring jseriesID) { int status = 1; try { const char *directory = env->GetStringUTFChars(jpath, NULL); const char *dicomIdentifier = env->GetStringUTFChars(jseriesID, NULL); typedef itk::GDCMSeriesFileNames NameGeneratorType; NameGeneratorType::Pointer nameGenerator = NameGeneratorType::New(); nameGenerator->SetUseSeriesDetails(true); nameGenerator->AddSeriesRestriction("0008|0021"); nameGenerator->SetDirectory(directory); typedef std::vector< std::string > SeriesIdContainer; const SeriesIdContainer & seriesUID = nameGenerator->GetSeriesUIDs(); SeriesIdContainer::const_iterator seriesItr = seriesUID.begin(); SeriesIdContainer::const_iterator seriesEnd = seriesUID.end(); typedef std::vector FileNameContainer; FileNameContainer filenames; try{ filenames = nameGenerator->GetFileNames(dicomIdentifier); } catch (itk::ExceptionObject &ex){ std::cout << ex << std::endl; return EXIT_FAILURE; } env->ReleaseStringUTFChars(jpath, directory); env->ReleaseStringUTFChars(jseriesID, dicomIdentifier); std::cout << "filenames length is:" << filenames.size() << std::endl; // Open the files ImageProvider *image = ItkImageProvider::OpenFile(filenames); setImageReference(env, obj, image); // Fill in image information fields jclass cls = env->GetObjectClass(obj); jintArray jsizeArray = env->NewIntArray(3); jint *jsize = env->GetIntArrayElements(jsizeArray, NULL); for (int i = 0; i < 3; i++) jsize[i] = image->GetSize(i); env->ReleaseIntArrayElements(jsizeArray, jsize, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_dimensions", "[I"), jsizeArray); jfloatArray jspacingArray = env->NewFloatArray(3); jfloat *jspacing = env->GetFloatArrayElements(jspacingArray, NULL); for (int i = 0; i < 3; i++) jspacing[i] = image->GetPixelSpacing(i); env->ReleaseFloatArrayElements(jspacingArray, jspacing, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_pixelSpacing", "[F"), jspacingArray); env->SetDoubleField(obj, env->GetFieldID(cls, "m_minValue", "D"), image->GetMinValue()); env->SetDoubleField(obj, env->GetFieldID(cls, "m_maxValue", "D"), image->GetMaxValue()); env->SetBooleanField(obj, env->GetFieldID(cls, "m_isByteType", "Z"), image->IsByteType()); } catch (...) { status = 0; } return status; } // Java Image.createFromSequence JNIEXPORT jboolean JNICALL IMAGE_NATIVE(createFromSequence) (JNIEnv *env, jobject obj, jobjectArray jfileList) { int status = 1; try { std::vector< std::string > filenames; for (int i = 0; i < env->GetArrayLength(jfileList); i++) { jstring jfileName = (jstring) env->GetObjectArrayElement(jfileList, i); const char *str = env->GetStringUTFChars(jfileName, 0); filenames.push_back(std::string(str)); env->ReleaseStringUTFChars(jfileName, str); } // Open the files ImageProvider *image = ItkImageProvider::OpenFile(filenames); setImageReference(env, obj, image); // Fill in image information fields jclass cls = env->GetObjectClass(obj); jintArray jsizeArray = env->NewIntArray(3); jint *jsize = env->GetIntArrayElements(jsizeArray, NULL); for (int i = 0; i < 3; i++) jsize[i] = image->GetSize(i); env->ReleaseIntArrayElements(jsizeArray, jsize, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_dimensions", "[I"), jsizeArray); jfloatArray jspacingArray = env->NewFloatArray(3); jfloat *jspacing = env->GetFloatArrayElements(jspacingArray, NULL); for (int i = 0; i < 3; i++) jspacing[i] = image->GetPixelSpacing(i); env->ReleaseFloatArrayElements(jspacingArray, jspacing, 0); env->SetObjectField(obj, env->GetFieldID(cls, "m_pixelSpacing", "[F"), jspacingArray); env->SetDoubleField(obj, env->GetFieldID(cls, "m_minValue", "D"), image->GetMinValue()); env->SetDoubleField(obj, env->GetFieldID(cls, "m_maxValue", "D"), image->GetMaxValue()); env->SetBooleanField(obj, env->GetFieldID(cls, "m_isByteType", "Z"), image->IsByteType()); } catch (...) { status = 0; } return status; } // Java Image.dispose JNIEXPORT void JNICALL IMAGE_NATIVE(dispose) (JNIEnv *env, jobject obj) { jclass cls = env->GetObjectClass(obj); if (cls) { jfieldID fld = env->GetFieldID(cls, "m_nativeHandle", "J"); if (fld) { ImageProvider *image = reinterpret_cast(env->GetLongField(obj, fld)); if (image) { delete image; env->SetLongField(obj, fld, 0); } } } } // Java Image.getDICOMSeries JNIEXPORT jobjectArray JNICALL IMAGE_NATIVE(getDICOMSeries) (JNIEnv *env, jclass cls, jstring jpath) { jobjectArray dicomSeries; const char *directory = env->GetStringUTFChars(jpath, NULL); typedef itk::GDCMSeriesFileNames NameGeneratorType; NameGeneratorType::Pointer nameGenerator = NameGeneratorType::New(); nameGenerator->SetUseSeriesDetails(true); nameGenerator->AddSeriesRestriction("0008|0021"); nameGenerator->SetDirectory(directory); env->ReleaseStringUTFChars(jpath, directory); typedef std::vector SeriesIDContainer; SeriesIDContainer seriesUID = nameGenerator->GetSeriesUIDs(); const int len = seriesUID.size(); jclass stringArrCls = env->FindClass("Ljava/lang/String;"); if(stringArrCls == NULL){ return NULL; } dicomSeries = env->NewObjectArray(len, stringArrCls, NULL); for (int i = 0; i < len; i++){ jstring uid; uid = env->NewStringUTF(seriesUID[i].c_str()); env->SetObjectArrayElement(dicomSeries, i, uid); } env->DeleteLocalRef(stringArrCls); return (dicomSeries); } // Java Image.loadSlice JNIEXPORT void JNICALL IMAGE_NATIVE(loadSlice) (JNIEnv *env, jobject obj, jint firstDirection, jint secondDirection, jint slice, jlong buf) { void *buffer = reinterpret_cast(buf); if (buffer != NULL) { getImageReference(env, obj)->LoadSlice(firstDirection, secondDirection, slice, reinterpret_cast(buffer)); } else { throwNullPointerException(env, "The output buffer has not been allocated."); } } // Java Image.measureROI // -------------------------------------------------------------------------- JNIEXPORT void JNICALL IMAGE_NATIVE(measureROI) (JNIEnv *env, jobject obj, jint x, jint y, jint width, jint height, jint firstDirection, jint secondDirection, jint slice, jobject measurement) { double mean, stdDev; int numPixels; getImageReference(env, obj)->MeasureROI(x, y, width, height, firstDirection, secondDirection, slice, mean, stdDev, numPixels); jclass cls = env->GetObjectClass(measurement); env->SetDoubleField(measurement, env->GetFieldID(cls, "mean", "D"), mean); env->SetDoubleField(measurement, env->GetFieldID(cls, "standardDeviation", "D"), stdDev); env->SetIntField(measurement, env->GetFieldID(cls, "numPixels", "I"), numPixels); } // -------------------------------------------------------------------------- // Java Image.measureLineProfile // -------------------------------------------------------------------------- JNIEXPORT void JNICALL IMAGE_NATIVE(measureLineProfile) (JNIEnv *env, jobject obj, jint x1, jint y1, jint z1, jint x2, jint y2, jint z2, jobject measurement) { jclass cls = env->GetObjectClass(measurement); jarray arr = static_cast(env->GetObjectField(measurement, env->GetFieldID(cls, "values", "[D"))); int maxNumElems = env->GetArrayLength(arr); double *elems = env->GetDoubleArrayElements(static_cast(arr), NULL); int len = getImageReference(env, obj)->MeasureLineProfile(x1, y1, z1, x2, y2, z2, elems, maxNumElems); env->ReleaseDoubleArrayElements(static_cast(arr), elems, 0); env->SetIntField(measurement, env->GetFieldID(cls, "numValues", "I"), len); } // -------------------------------------------------------------------------- // Java Image.loadSliceWindowLevel // -------------------------------------------------------------------------- JNIEXPORT void JNICALL IMAGE_NATIVE(loadSliceWindowLevel) (JNIEnv *env, jobject obj, jint firstDirection, jint secondDirection, jint slice, jint startX, jint startY, jint width, jint height, jfloat offset, jfloat slope, jobject colormap, jboolean invert, jobject buf) { void *buffer = env->GetDirectBufferAddress(buf); long length = static_cast(env->GetDirectBufferCapacity(buf)); void *cmap = env->GetDirectBufferAddress(colormap); if (!buffer || (length == -1)) { throwNullPointerException(env, "The input buffer has not been allocated."); return; } if (!cmap) { throwNullPointerException(env, "No colormap specified."); return; } if (static_cast(env->GetDirectBufferCapacity(colormap) != 256 * 3)) { throwIllegalArgumentException(env, "Colormap is not the right size."); return; } getImageReference(env, obj)->LoadSlice(firstDirection, secondDirection, slice, startX, startY, width, height, offset, slope, reinterpret_cast(cmap), invert, reinterpret_cast(buffer)); } // -------------------------------------------------------------------------- // WindowLevel // -------------------------------------------------------------------------- inline GrayPixelType WindowLevel(IntermediatePixelType in_pixel, int offset, int fixSlope, bool invert) { in_pixel -= offset; GrayPixelType out_pixel; if (in_pixel <= 0) { out_pixel = 0; } else { in_pixel = (in_pixel * fixSlope) >> 16; if (in_pixel > 0xff) out_pixel = 0xff; else out_pixel = static_cast(in_pixel); } if (invert) out_pixel = 255 - out_pixel; return out_pixel; } // -------------------------------------------------------------------------- // Java Image.windowLevel // -------------------------------------------------------------------------- JNIEXPORT void JNICALL IMAGE_NATIVE(windowLevel) (JNIEnv *env, jobject obj, jlong inBuf, jobject outBuf, jint size, jint offset, jfloat slope, jobject colormap, jboolean invert) { IntermediatePixelType *input = reinterpret_cast(inBuf); GrayPixelType *cmap = reinterpret_cast(env->GetDirectBufferAddress(colormap)); GrayPixelType *output = reinterpret_cast(env->GetDirectBufferAddress(outBuf)); if (!input) { throwNullPointerException(env, "Input pointer is null."); return; } if (!cmap) { throwNullPointerException(env, "No colormap specified."); return; } if (!output) { throwNullPointerException(env, "Output buffer not allocated."); return; } if (static_cast(env->GetDirectBufferCapacity(colormap) != 256 * 3)) { throwIllegalArgumentException(env, "Colormap is not the right size."); return; } // Calculate fixed point representation for the slope int fixSlope = static_cast(static_cast(slope) * 65536.0f + 0.5f); GrayPixelType *rgb; for (int i = 0; i < size; i++) { GrayPixelType out_pixel = WindowLevel(*input++, offset, fixSlope, invert); rgb = cmap + (out_pixel * kNumComponents) + 2; *output++ = *rgb--; *output++ = *rgb--; *output++ = *rgb; *output++ = 255; } } // -------------------------------------------------------------------------- // Java Image.scaleToOutput // -------------------------------------------------------------------------- // Copy a part of one image into another image with scaling. inBuf, inWidth, // and inHeight describe the input image. Copying begins at xStart and yStart. // The output is hScale wider and vScale higher than the input. JNIEXPORT void JNICALL IMAGE_NATIVE(scaleToOutput) (JNIEnv *env, jobject obj, jlong inBuf, jobject outBuf, jint inWidth, jint inHeight, jint outWidth, jint outHeight, jint xStart, jint yStart, jfloat hScale, jfloat vScale, jint offset, jfloat slope, jobject colormap, jboolean invert, jboolean flipY, jboolean nearestNeighbor) { IntermediatePixelType *input = reinterpret_cast(inBuf); GrayPixelType *cmap = reinterpret_cast(env->GetDirectBufferAddress(colormap)); GrayPixelType *output = reinterpret_cast(env->GetDirectBufferAddress(outBuf)); if (!input) { throwNullPointerException(env, "Input pointer is null."); return; } if (!cmap) { throwNullPointerException(env, "No colormap specified."); return; } if (!output) { throwNullPointerException(env, "Output buffer not allocated."); return; } if (static_cast(env->GetDirectBufferCapacity(colormap) != 256 * 3)) { throwIllegalArgumentException(env, "Colormap is not the right size."); return; } // Calculate fixed point representation for the slope int fixSlope = static_cast(static_cast(slope) * 65536.0f + 0.5f); const int fixOne = static_cast(1.0f * 65536.0f + 0.5f); const int fixInHeight = static_cast(static_cast(inHeight) * 65536.0f + 0.5f); int xScale = static_cast((1.0f / hScale) * 65536.0f + 0.5f); int yScale = static_cast((1.0f / vScale) * 65536.0f + 0.5f); if (!nearestNeighbor) { // Bilinear interpolation for (int y = 0; y < outHeight; y++) { int jFloat = (y + yStart) * yScale; if (flipY) jFloat = fixInHeight - jFloat - fixOne; int j = jFloat >> 16; int dy = jFloat - (j << 16); int tDown; if (j < 0) j = 0; if (j >= inHeight) { j = tDown = inHeight - 1; } else { if (j == inHeight - 1) tDown = inHeight - 1; else tDown = j + 1; } IntermediatePixelType *tj = input + j * inWidth; IntermediatePixelType *tj1 = input + tDown * inWidth; for (int x = 0; x < outWidth; x++) { int iFloat = (x + xStart) * xScale; int i = iFloat >> 16; int dx = iFloat - (i << 16); int tRight; if (i < 0) i = 0; if (i >= inWidth) { i = tRight = inWidth - 1; } else { if (i == inWidth - 1) tRight = inWidth - 1; else tRight = i + 1; } /* // More accurate -- bilinear interpolation of the color values GrayPixelType *z11 = cmap + (WindowLevel(tj[i], offset, fixSlope, invert) * kNumComponents) + 2; GrayPixelType *z12 = cmap + (WindowLevel(tj1[i], offset, fixSlope, invert) * kNumComponents) + 2; GrayPixelType *z21 = cmap + (WindowLevel(tj[tRight], offset, fixSlope, invert) * kNumComponents) + 2; GrayPixelType *z22 = cmap + (WindowLevel(tj1[tRight], offset, fixSlope, invert) * kNumComponents) + 2; for (int c = 0; c < kNumComponents; c++) { int out_pixel = *z11 + ((dx * (*z21 - *z11)) >> 16) + ((dy * (*z12 - *z11)) >> 16) + ((dx * ((dy * (*z11 - *z12 - *z21 + *z22)) >> 16)) >> 16); if (out_pixel > 255) *output++ = 255; else if (out_pixel < 0) *output++ = 0; else *output++ = static_cast(out_pixel); z11--; z12--; z21--; z22--; } *output++ = 255; */ // Faster but less accurate -- bilinear interpolation of the grayscale value // followed by colormapping. This can introduce artifacts if the color map is // not a linear ramp (e.g., the jet colormap). int z11 = tj[i]; int z12 = tj1[i]; int z21 = tj[tRight]; int z22 = tj1[tRight]; int in_pixel = z11 + ((dx * (z21 - z11)) >> 16) + ((dy * (z12 - z11)) >> 16) + ((dx * ((dy * (z11 - z12 - z21 + z22)) >> 16)) >> 16); in_pixel -= offset; GrayPixelType out_pixel; if (in_pixel <= 0) { out_pixel = 0; } else { in_pixel = (in_pixel * fixSlope) >> 16; if (in_pixel > 0xff) out_pixel = 0xff; else out_pixel = static_cast(in_pixel); } if (invert) out_pixel = 255 - out_pixel; GrayPixelType *rgb = cmap + (out_pixel * kNumComponents) + 2; *output++ = *rgb--; *output++ = *rgb--; *output++ = *rgb; *output++ = 255; } } } else { // Nearest neighbor interpolation for (int y = 0; y < outHeight; y++) { int j = ((y + yStart) * yScale) >> 16; if (flipY) j = inHeight - j - 1; if (j >= inHeight) j = inHeight - 1; IntermediatePixelType *tj = input + j * inWidth; for (int x = 0; x < outWidth; x++) { int i = ((x + xStart) * xScale) >> 16; if (i >= inWidth) i = inWidth - 1; GrayPixelType *out_pixel = cmap + (WindowLevel(tj[i], offset, fixSlope, invert) * kNumComponents) + 2; *output++ = *out_pixel--; *output++ = *out_pixel--; *output++ = *out_pixel--; *output++ = 255; } } } } // -------------------------------------------------------------------------- // Java Image.getPixel // -------------------------------------------------------------------------- JNIEXPORT jdouble JNICALL IMAGE_NATIVE(getPixel) (JNIEnv *env, jobject obj, jfloat x, jfloat y, jfloat z) { ImageProvider *image = getImageReference(env, obj); return image->GetPixel(static_cast(x / image->GetPixelSpacing(0)), static_cast(y / image->GetPixelSpacing(1)), static_cast(z / image->GetPixelSpacing(2))); } // -------------------------------------------------------------------------- // getImageReference // -------------------------------------------------------------------------- ImageProvider *getImageReference(JNIEnv *env, jobject obj) { jclass cls = env->GetObjectClass(obj); if (cls) { jfieldID fld = env->GetFieldID(cls, "m_nativeHandle", "J"); if (fld) return reinterpret_cast(env->GetLongField(obj, fld)); } return NULL; } // -------------------------------------------------------------------------- // setImageReference // -------------------------------------------------------------------------- void setImageReference(JNIEnv *env, jobject obj, ImageProvider *image) { jclass cls = env->GetObjectClass(obj); if (cls) { jfieldID fld = env->GetFieldID(cls, "m_nativeHandle", "J"); if (fld) env->SetLongField(obj, fld, reinterpret_cast(image)); else throw std::invalid_argument("Object does not contain a field for the native handle"); } else throw std::invalid_argument("Could not get object class reference"); } // -------------------------------------------------------------------------- // throwException // -------------------------------------------------------------------------- bool throwException(JNIEnv *env, const char *type, const char *message) { jclass cls = env->FindClass(type); if (!cls) return false; env->ThrowNew(cls, message); return true; } // -------------------------------------------------------------------------- // throwNullPointerException // -------------------------------------------------------------------------- bool throwNullPointerException(JNIEnv *env, const char *message) { return throwException(env, "java/lang/NullPointerException", message); } // -------------------------------------------------------------------------- // throwIllegalArgumentException // -------------------------------------------------------------------------- bool throwIllegalArgumentException(JNIEnv *env, const char *message) { return throwException(env, "java/lang/IllegalArgumentException", message); }