MPSImageHistogram

Hi,


I'm checking out the new MetalPerformanceShaders which were presented in WWDC 607. 😎


Are there some examples showning how to use them? I couldn't find any and the code documentation isn't (to me) exaustive enough.


I've been playing with MPSImageHistogram on some test images (100x100px with just a few color stripes) and there are a few things that just don't look right.


The docs don't mention exactly how data is written to the output buffer, but from what I could deduce the bin count are stored as UInt32. So far so good.


The initializer of MPSHistogramInfo states that the numberOfHistogramEntries are the number of histogram bins for each channel. However it seems you need to multiply this number by 256 to actually get the desidered bin number. This unecessarily increases the size of the histogram buffer. Has anyone noticed the same?


Allocated size a part this works pretty well from 2 to 128 actual bins. 256 bins don't work, with numbers coming out gibberish from the result buffer.


Could anyone review the following code for errors? Thanks!


        // colorPixelFormat = .BGRA8Unorm
        let commandBuffer = commandQueue.commandBuffer()

        let binsPerChannel = 128 // ok from 2-128, ko 256
        let hSize = 256 * 128
        let channels = 4
        let bytesPerBin = 4

        var histogramInfo = MPSHistogramInfo(numberOfHistogramEntries: 256 * binsPerChannel,
                                                    histogramForAlpha: ObjCBool(channels == 4),
                                                        minPixelValue: vector_float4(0.0, 0.0, 0.0, 0.0),
                                                        maxPixelValue: vector_float4(255.0, 255.0, 255.0, 255.0))

        let histFilter = MPSImageHistogram(device: view.device!,
                                    histogramInfo: &histogramInfo)

        let histogramBuffer = view.device?.newBufferWithLength(hSize * bytesPerBin * channels, options: MTLResourceOptions.OptionCPUCacheModeDefault)
        histFilter.encodeToCommandBuffer(commandBuffer, sourceTexture: sourceTexture, histogram: histogramBuffer!, histogramOffset: 0)

        commandBuffer.commit()
        commandBuffer.waitUntilCompleted()

        var binData = UnsafePointer<UInt32>(histogramBuffer!.contents())
        for i in 0..<hSize * channels {
            if binData[i] != 0 {
                print("\(binData[i]) \(i)")
            }
        }


I'm using Xcode7, iOS9 beta 3 on an iPad Air2.

Replies

The numberOfHistogramEntries in MPSImageHistogramInfo is the number of histogram bins per channel. If you want 256 bins for each channel then you should specify numberOfHistogramEntries as 256. The histogram data is stored as a uint32_t for each channel * number of channels i.e. first all the histogram values for "R" are stored followed by the histogram values for "G" and then "B" and "A". The size of the histogram buffer should be numberOfHistogramEntries * sizeof(uint32_t) * number of channels in your image.


Can you try these changes and let us know if 256-bins is still not working for your case?

I'm using a test image which looks like this:

__________

| | | | |

| | | | |

| | | | |

|__|__|__|__|


It's a 100x100 image with 4 vertical stripes (25 pixel wide each), from left to right in RGB:

  • 255, 0, 0
  • 180, 0, 0
  • 100, 0, 0
  • 40, 0, 0


Here are some results calculating the histogram on all 4 channels.

1) 128 bins with the 256 multiplication factor

  • bytesPerBin = 4
  • numberOfHistogramEntries = 256 * 128
  • newBufferWithLength = numberOfHistogramEntries * channels * bytesPerBin


// R
2500 20
2500 50
2500 90
2500 128
// G
10000 32768
// B
10000 65536
// A
10000 98432


The result is correct

As is my previous code the result are also consistent for any bin number from 2 to 128 (power of 2).


2) 128 bins without the 256 multiplication factor

  • bytesPerBin = 4
  • numberOfHistogramEntries = 128
  • newBufferWithLength = numberOfHistogramEntries * channels * bytesPerBin


The histogramBuffer is all zeros. The result is not correct


3) 256 bins with the 256 multiplication factor

  • bytesPerBin = 4
  • numberOfHistogramEntries = 256 * 256
  • newBufferWithLength = numberOfHistogramEntries * channels * bytesPerBin


// R
19976 0
24 1
2496 40
4 41
// G?

2496 100
4 101
// B?

2500 181
// A?

12500 257


The result is not correct.


4) 256 bins without the 256 multiplication factor

  • bytesPerBin = 4
  • numberOfHistogramEntries = 256
  • newBufferWithLength = numberOfHistogramEntries * channels * bytesPerBin


7500 0
2500 1
10000 256
10000 512
10000 769


The result is not correct. It's as if it would be a 2 bins resul although, even it it was the case, the result is not correct (bins count should be 5000 and 5000).

t.camin,


FYI, try 256 for number of histogram entries, however, maxPixelValue should be float4( 1, 1, 1, 1 )


var histogramInfo = MPSHistogramInfo( numberOfHistogramEntries: 256, histogramForAlpha: false,

minPixelValue: float4( 0, 0, 0, 0 ), maxPixelValue: float4( 1, 1, 1, 1 ) )

histogram = MPSImageHistogram( device: device, histogramInfo: &histogramInfo )

histogramBuffer = device.newBufferWithLength( 256 * 4 * sizeof( Int32 ), options:[] )


let vData = UnsafePointer<UInt32>(histogramBuffer.contents())

for j in 0..<(256*4) {

if ( vData[j] != 0 )

{

print( "vData \(j) \(vData[j])" )

}

}


appears to give the correct results.

The minPixelValue and maxPixelValues should be normalized color values. For a RGBA8Unorm image, minPixelValue should be (0, 0, 0, 0) and the maxPixelValue should be (1, 1, 1, 1). I noticed that in the code you had listed above you were using maxPixelValue of (255, 255, 255, 255). Let me know if this change and the changes suggested in my previous reply helps with fixing your issue with incorrect histogram results.