Hello there,
I've been going through attempting to implement some image processing techniques that require the use of creating a Laplacian Pyramid. Now I'm not exactly sure that MPSImageLaplacianPyramid is completly useable yet as the documentation for the page is not filled out, but I've found places that it requires taking the input of a gaussian pyramid. I am, however, getting very strange outputs that don't even look anything like what you would expect out of the level of one of the pyramids, further I believe that there may be some sort of pixel format restriction, but I am not sure.
I would like to take advantage of the speed of mipmapping to create the pyramids, as I'm sure anything I come up with is going to be far slower. Some things that I have noticed is that the pixel format seems to matter. When creating the Laplacian Pyramid, you take the difference of an upsampled lower level of the guassian with another level, hence negative values possibly could get clamped to 0, and so we get weird looking outputs. I can not for the life of me figure out if the filter is working as intended or not. Below is my code:
import Cocoa
import Metal
import MetalPerformanceShaders
import MetalKit
class ViewController: NSViewController, MTKViewDelegate {
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
func draw(in view: MTKView) {}
override func viewDidLoad() {
super.viewDidLoad()
//Metal Setup
let device = MTLCreateSystemDefaultDevice()!
let commandQueue = device.makeCommandQueue()!
let textureLoader = MTKTextureLoader(device: device)
let commandBuffer = commandQueue.makeCommandBuffer()!
let gauss = MPSImageGaussianPyramid(device: device, centerWeight: 0.375)
let lap = MPSImageLaplacianPyramid(device: device)
//Extract Image into MTLTexture
let url = Bundle.main.url(forResource: "flower", withExtension: "png")!
let options = [MTKTextureLoader.Option.textureUsage : NSNumber(value:MTLTextureUsage.shaderRead.rawValue |
MTLTextureUsage.shaderWrite.rawValue | MTLTextureUsage.pixelFormatView.rawValue),
MTKTextureLoader.Option.SRGB: false, MTKTextureLoader.Option.allocateMipmaps : true]
var texIn = try! textureLoader.newTexture(URL: url, options: options)
//View Setup
let metalView = MTKView(frame: CGRect(x: 0, y: 0, width: texIn.width, height: texIn.height))
metalView.framebufferOnly = false
metalView.device = device
metalView.isPaused = true
metalView.colorPixelFormat = .bgra8Unorm
view.addSubview(metalView)
//Intermediate Texture for Pyramid
var intD = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm,
width: Int(texIn.width), height: Int(texIn.height), mipmapped: true)
intD.usage = MTLTextureUsage(rawValue: ( MTLTextureUsage.shaderWrite.rawValue |
MTLTextureUsage.shaderRead.rawValue |
MTLTextureUsage.pixelFormatView.rawValue))
var intT = device.makeTexture(descriptor: intD)!
//Image to Pyramid, Laplacian requires a Guassian Pyramid as input
gauss.encode(commandBuffer: commandBuffer, inPlaceTexture: &texIn, fallbackCopyAllocator: nil)
lap.encode(commandBuffer: commandBuffer, sourceTexture: texIn, destinationTexture: intT)
//Grab a particular level for viewing
let blitEncoder = commandBuffer.makeBlitCommandEncoder()!
blitEncoder.copy(from: intT, sourceSlice: 0, sourceLevel: 0,
sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
sourceSize: MTLSize(width: texIn.width, height: texIn.height, depth: 1),
to: (metalView.currentDrawable?.texture)!,
destinationSlice: 0, destinationLevel: 0, destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
blitEncoder.endEncoding()
commandBuffer.present(metalView.currentDrawable!)
commandBuffer.commit()
commandBuffer.waitUntilCompleted()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}