IntensityWindowingImageFilter not working as expected with multiframe instances (JAVA)

i have recorded my problem:

As you can see, weasis is clearly displaying the image with [128, 256] and my software shows everything in white with same configuration.

This is the function that applys the filter:

public static Image applyWCWW (Image image, double wc, double ww) {
        IntensityWindowingImageFilter filter = new IntensityWindowingImageFilter();
        filter.setOutputMinimum(0);
        filter.setOutputMaximum(255);
        filter.setWindowMinimum(wc - (ww/2));
        filter.setWindowMaximum(wc + (ww/2));
        return filter.execute(image);
    }

This function is working normally for singleframe instances. The only difference between the two types of image is the extraction.

//for singleframe instances
    public static Image axisSlice (Image pixeldata, long[] sliceSize, int[] sliceIndex) {
        VectorUInt32 vectorSize = new VectorUInt32(sliceSize);
        VectorInt32 vectorIndex = new VectorInt32(sliceIndex);
        return SimpleITK.extract(pixeldata, vectorSize, vectorIndex);
    }

    //for multiframe instances
    public static Image axisSlice (Image pixeldata, int index) {
        ExtractImageFilter filter = new ExtractImageFilter();
        filter.setSize(new VectorUInt32(new long[]{pixeldata.getSize().get(0), pixeldata.getSize().get(1), 0, 0}));
        filter.setIndex(new VectorInt32(new int[]{0, 0, index, 0}));
        return filter.execute(pixeldata);
    }

Hello @Salu_Ramos,

If you suspect the issue is with the slice extraction, then save the slice to file (i.e. WriteImage(axis_slice, "slice.nrrd")) and open it with ITK-SNAP. If the image looks like what you expect then the problem is with the display code, if it doesn’t then the problem is with the extraction.

2 Likes

After removing post processing (intensitywindowing), this is the result:

apparently the extraction already has intensitywindowing and I’m applying for the second time?

Hello @Salu_Ramos,

Extraction just does extraction and does not change the image content. If you used ITK-SNAP to view the image, it is doing the window/level operation. This just indicates that the problem is with your intensity-windowing code.

1 Like

itk-snap isn’t opening any multiframe instance:
image

my extraction image (with no defaultwindowing) is identical to that displayed in other software such as radiant and weasis with default windowing.

When I don’t apply defaultwindowing the extraction appears as expected, and when i apply defaultwindowing it becomes white.

I’ve already made sure it’s not a problem with the display code.

Hi @zivy ,

i implemented a Pixel Value and HU annotation to help me solve this problem.

as you can see:

WindowWidth = 256
WindowCenter/WindowLength = 128
WindowMax = -45.5
WindowMin = 85
Mouse Position HU = 78
Mouse Position Pixel Value = 255

So if HU is 78 and viewing range is between -45 and 85, why is everything white?

I discovered that when: wc*2+ww > 2^bitsAllocated
image becomes full white. Look the video.

the code that applies the filter is this below:

public static Image applyWCWW (Image image, double wc, double ww) {
        IntensityWindowingImageFilter filter = new IntensityWindowingImageFilter();
        filter.setOutputMinimum(0);
        filter.setOutputMaximum(255);
        ww = Math.max(ww, 0);
        double windowMin = wc - (ww/2);
        double windowMax = wc + (ww/2);
        System.out.println("viewing range (HU): from " + windowMin + " to " + windowMax);
        filter.setWindowMinimum(windowMin);
        filter.setWindowMaximum(windowMax);
        return filter.execute(image);
    }

Does this give you any idea what could be going on?

Hello @Salu_Ramos,

Likely you are seeing results of overflow. See if converting the following Python code to Java solves the problem:

def applyWCWW (image, wc, ww):
    minmaxFilter = sitk.MinimumMaximumImageFilter()
    minmaxFilter.Execute(image)
    # clamp the min, max values based on the user request and image content,
    # the lowest possible value for windowMin is the minimal value in the image
    # the highest possible value for windowMax is the maximal value in the image
    windowMin = max(wc - (ww/2), minmaxFilter.GetMinimum())
    windowMax = min(wc + (ww/2), minmaxFilter.GetMaximum())

    intensityWindowingFilter = sitk.IntensityWindowingImageFilter()
    intensityWindowingFilter.SetOutputMinimum(0)
    intensityWindowingFilter.SetOutputMaximum(255)    
    return intensityWindowingFilter.Execute(image)
3 Likes

Hi @zivy! It is good to see you again.

i managed to fix this today this way:

public static Image applyItkWCWW(Image image, double wc, double ww) {
        IntensityWindowingImageFilter filter = new IntensityWindowingImageFilter();
        filter.setOutputMinimum(0);
        filter.setOutputMaximum(255);
        double windowMin = wc - (ww/2);
        double windowMax = wc + (ww/2);

        long[] pixelRange = PixelIDValueEnumMap.getPixelIDByName(image.getPixelID()).getPixelRange();
        double windowRangeMin = pixelRange[0];
        double windowRangeMax = pixelRange[1];

        filter.setWindowMinimum(Math.max(windowMin, windowRangeMin));
        filter.setWindowMaximum(Math.min(windowMax, windowRangeMax));
        return filter.execute(image);
    }
public enum PixelIDValueEnumMap {
    Unknown("sitkUnknown", new long[]{-32768, 32767}, (image, vector) -> "unknown"),
    UInt8("sitkUInt8", new long[]{0, 255}, (image, vector) -> String.valueOf(image.getPixelAsUInt8(vector))),
    Int8("sitkInt8", new long[]{-128, 127}, (image, vector) -> String.valueOf(image.getPixelAsInt8(vector))),
    UInt16("sitkUInt16", new long[]{0, 65535}, (image, vector) -> String.valueOf(image.getPixelAsUInt16(vector))),
    Int16("sitkInt16", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsInt16(vector))),
    UInt32("sitkUInt32", new long[]{0, 4_294_967_295L}, (image, vector) -> String.valueOf(image.getPixelAsUInt32(vector))),
    Int32("sitkInt32", new long[]{-2_147_483_648L, 2_147_483_647L}, (image, vector) -> String.valueOf(image.getPixelAsInt32(vector))),
    UInt64("sitkUInt64", new long[]{0, 9_223_372_036_854_775_807L}, (image, vector) -> String.valueOf(image.getPixelAsUInt64(vector))),
    Int64("sitkInt64", new long[]{-9_223_372_036_854_775_807L, 9_223_372_036_854_775_807L}, (image, vector) -> String.valueOf(image.getPixelAsInt64(vector))),
    Float32("sitkFloat32", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsComplexFloat32(vector))),
    Float64("sitkFloat64", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsComplexFloat64(vector))),
    ComplexFloat32("sitkComplexFloat32", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsComplexFloat32(vector))),
    ComplexFloat64("sitkComplexFloat64", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsComplexFloat64(vector))),
    VectorUInt8("sitkVectorUInt8", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorUInt8(vector))),
    VectorInt8("sitkVectorInt8", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorInt8(vector))),
    VectorUInt16("sitkVectorUInt16", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorUInt16(vector))),
    VectorInt16("sitkVectorInt16", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorInt16(vector))),
    VectorUInt32("sitkVectorUInt32", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorUInt32(vector))),
    VectorInt32("sitkVectorInt32", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorInt32(vector))),
    VectorUInt64("sitkVectorUInt64", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorUInt64(vector))),
    VectorInt64("sitkVectorInt64", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorInt64(vector))),
    VectorFloat32("sitkVectorFloat32", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorFloat32(vector))),
    VectorFloat64("sitkVectorFloat64", new long[]{-32768, 32767}, (image, vector) -> String.valueOf(image.getPixelAsVectorFloat64(vector))),
    LabelUInt8("sitkLabelUInt8", new long[]{-32768, 32767}, (image, vector) -> "LabelUInt8"),
    LabelUInt16("sitkLabelUInt16", new long[]{-32768, 32767}, (image, vector) -> "LabelUInt16"),
    LabelUInt32("sitkLabelUInt32", new long[]{-32768, 32767}, (image, vector) -> "LabelUInt32"),
    LabelUInt64("sitkLabelUInt64", new long[]{-32768, 32767}, (image, vector) -> "LabelUInt64");

    @Getter private final String name;
    @Getter private final long[] pixelRange;
    @Getter private final GetPixel getPixelLambda;

    PixelIDValueEnumMap(String name, long[] pixelRange, GetPixel getPixelLambda) {
        this.name = name;
        this.pixelRange = pixelRange;
        this.getPixelLambda = getPixelLambda;
    }

    public static PixelIDValueEnumMap getPixelIDByName (PixelIDValueEnum pixelID) {
        String pixelIDString = pixelID.toString();
        for (PixelIDValueEnumMap pixelIDMap : PixelIDValueEnumMap.values()) {
            if (pixelIDMap.name.equals(pixelIDString)) {
                return pixelIDMap;
            }
        }
        return PixelIDValueEnumMap.Unknown;
    }

}

After I solved it I saw your answer, it’s basically your way but done manually lol.
Thanks!