parent
6cd5c7a760
commit
b7280f4aa9
@ -0,0 +1,29 @@
|
||||
// For licensing see accompanying LICENSE.md file.
|
||||
// Copyright (C) 2022 Apple Inc. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct AlphasCumprodCalculation {
|
||||
public var sqrtAlphasCumprod: Float
|
||||
public var sqrtOneMinusAlphasCumprod: Float
|
||||
|
||||
public init(
|
||||
sqrtAlphasCumprod: Float,
|
||||
sqrtOneMinusAlphasCumprod: Float
|
||||
) {
|
||||
self.sqrtAlphasCumprod = sqrtAlphasCumprod
|
||||
self.sqrtOneMinusAlphasCumprod = sqrtOneMinusAlphasCumprod
|
||||
}
|
||||
|
||||
public init(
|
||||
alphasCumprod: [Float],
|
||||
timesteps: Int = 1_000,
|
||||
steps: Int,
|
||||
strength: Float
|
||||
) {
|
||||
let tEnc = Int(strength * Float(steps))
|
||||
let initTimestep = timesteps - timesteps / steps * (steps - tEnc) + 1
|
||||
self.sqrtAlphasCumprod = alphasCumprod[initTimestep].squareRoot()
|
||||
self.sqrtOneMinusAlphasCumprod = (1 - alphasCumprod[initTimestep]).squareRoot()
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
// For licensing see accompanying LICENSE.md file.
|
||||
// Copyright (C) 2022 Apple Inc. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Accelerate
|
||||
import CoreML
|
||||
|
||||
@available(iOS 16.0, macOS 13.0, *)
|
||||
extension CGImage {
|
||||
|
||||
typealias PixelBufferPFx1 = vImage.PixelBuffer<vImage.PlanarF>
|
||||
typealias PixelBufferP8x3 = vImage.PixelBuffer<vImage.Planar8x3>
|
||||
typealias PixelBufferIFx3 = vImage.PixelBuffer<vImage.InterleavedFx3>
|
||||
typealias PixelBufferI8x3 = vImage.PixelBuffer<vImage.Interleaved8x3>
|
||||
|
||||
public enum ShapedArrayError: String, Swift.Error {
|
||||
case wrongNumberOfChannels
|
||||
case incorrectFormatsConvertingToShapedArray
|
||||
case vImageConverterNotInitialized
|
||||
}
|
||||
|
||||
public static func fromShapedArray(_ array: MLShapedArray<Float32>) throws -> CGImage {
|
||||
|
||||
// array is [N,C,H,W], where C==3
|
||||
let channelCount = array.shape[1]
|
||||
guard channelCount == 3 else {
|
||||
throw ShapedArrayError.wrongNumberOfChannels
|
||||
}
|
||||
|
||||
let height = array.shape[2]
|
||||
let width = array.shape[3]
|
||||
|
||||
// Normalize each channel into a float between 0 and 1.0
|
||||
let floatChannels = (0..<channelCount).map { i in
|
||||
|
||||
// Normalized channel output
|
||||
let cOut = PixelBufferPFx1(width: width, height:height)
|
||||
|
||||
// Reference this channel in the array and normalize
|
||||
array[0][i].withUnsafeShapedBufferPointer { ptr, _, strides in
|
||||
let cIn = PixelBufferPFx1(data: .init(mutating: ptr.baseAddress!),
|
||||
width: width, height: height,
|
||||
byteCountPerRow: strides[0]*4)
|
||||
// Map [-1.0 1.0] -> [0.0 1.0]
|
||||
cIn.multiply(by: 0.5, preBias: 1.0, postBias: 0.0, destination: cOut)
|
||||
}
|
||||
return cOut
|
||||
}
|
||||
|
||||
// Convert to interleaved and then to UInt8
|
||||
let floatImage = PixelBufferIFx3(planarBuffers: floatChannels)
|
||||
let uint8Image = PixelBufferI8x3(width: width, height: height)
|
||||
floatImage.convert(to:uint8Image) // maps [0.0 1.0] -> [0 255] and clips
|
||||
|
||||
// Convert to uint8x3 to RGB CGImage (no alpha)
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
|
||||
let cgImage = uint8Image.makeCGImage(cgImageFormat:
|
||||
.init(bitsPerComponent: 8,
|
||||
bitsPerPixel: 3*8,
|
||||
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: bitmapInfo)!)!
|
||||
|
||||
return cgImage
|
||||
}
|
||||
|
||||
public var plannerRGBShapedArray: MLShapedArray<Float32> {
|
||||
get throws {
|
||||
guard
|
||||
var sourceFormat = vImage_CGImageFormat(cgImage: self),
|
||||
var mediumFormat = vImage_CGImageFormat(
|
||||
bitsPerComponent: 8 * MemoryLayout<UInt8>.size,
|
||||
bitsPerPixel: 8 * MemoryLayout<UInt8>.size * 4,
|
||||
colorSpace: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue)),
|
||||
let width = vImagePixelCount(exactly: self.width),
|
||||
let height = vImagePixelCount(exactly: self.height)
|
||||
else {
|
||||
throw ShapedArrayError.incorrectFormatsConvertingToShapedArray
|
||||
}
|
||||
|
||||
var sourceImageBuffer = try vImage_Buffer(cgImage: self)
|
||||
|
||||
var mediumDesination = try vImage_Buffer(width: Int(width), height: Int(height), bitsPerPixel: mediumFormat.bitsPerPixel)
|
||||
|
||||
let converter = vImageConverter_CreateWithCGImageFormat(
|
||||
&sourceFormat,
|
||||
&mediumFormat,
|
||||
nil,
|
||||
vImage_Flags(kvImagePrintDiagnosticsToConsole),
|
||||
nil)
|
||||
|
||||
guard let converter = converter?.takeRetainedValue() else {
|
||||
throw ShapedArrayError.vImageConverterNotInitialized
|
||||
}
|
||||
|
||||
vImageConvert_AnyToAny(converter, &sourceImageBuffer, &mediumDesination, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole))
|
||||
|
||||
var destinationA = try vImage_Buffer(width: Int(width), height: Int(height), bitsPerPixel: 8 * UInt32(MemoryLayout<Float>.size))
|
||||
var destinationR = try vImage_Buffer(width: Int(width), height: Int(height), bitsPerPixel: 8 * UInt32(MemoryLayout<Float>.size))
|
||||
var destinationG = try vImage_Buffer(width: Int(width), height: Int(height), bitsPerPixel: 8 * UInt32(MemoryLayout<Float>.size))
|
||||
var destinationB = try vImage_Buffer(width: Int(width), height: Int(height), bitsPerPixel: 8 * UInt32(MemoryLayout<Float>.size))
|
||||
|
||||
var minFloat: [Float] = [-1.0, -1.0, -1.0, -1.0]
|
||||
var maxFloat: [Float] = [1.0, 1.0, 1.0, 1.0]
|
||||
|
||||
vImageConvert_ARGB8888toPlanarF(&mediumDesination, &destinationA, &destinationR, &destinationG, &destinationB, &maxFloat, &minFloat, .zero)
|
||||
|
||||
let redData = Data(bytes: destinationR.data, count: Int(width) * Int(height) * MemoryLayout<Float>.size)
|
||||
let greenData = Data(bytes: destinationG.data, count: Int(width) * Int(height) * MemoryLayout<Float>.size)
|
||||
let blueData = Data(bytes: destinationB.data, count: Int(width) * Int(height) * MemoryLayout<Float>.size)
|
||||
|
||||
let imageData = redData + greenData + blueData
|
||||
|
||||
let shapedArray = MLShapedArray<Float32>(data: imageData, shape: [1, 3, 512, 512])
|
||||
|
||||
return shapedArray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
// For licensing see accompanying LICENSE.md file.
|
||||
// Copyright (C) 2022 Apple Inc. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import CoreML
|
||||
|
||||
@available(iOS 16.0, macOS 13.0, *)
|
||||
/// Encoder, currently supports image2image
|
||||
public struct Encoder {
|
||||
|
||||
public enum Error: String, Swift.Error {
|
||||
case latentOutputNotValid
|
||||
case batchLatentOutputEmpty
|
||||
}
|
||||
|
||||
/// VAE encoder model + post math and adding noise from schedular
|
||||
var model: MLModel
|
||||
|
||||
/// Create decoder from Core ML model
|
||||
///
|
||||
/// - Parameters
|
||||
/// - model: Core ML model for VAE decoder
|
||||
public init(model: MLModel) {
|
||||
self.model = model
|
||||
}
|
||||
|
||||
/// Prediction queue
|
||||
let queue = DispatchQueue(label: "encoder.predict")
|
||||
|
||||
/// Batch encode latent samples into images
|
||||
/// - Parameters:
|
||||
/// - image: image used for image2image
|
||||
/// - diagonalNoise: random noise for `DiagonalGaussianDistribution` operation
|
||||
/// - noise: random noise for initial latent space based on strength argument
|
||||
/// - alphasCumprodStep: calculations using the scheduler traditionally calculated in the pipeline in pyTorch Diffusers library.
|
||||
/// - Returns: The encoded latent space as MLShapedArray
|
||||
public func encode(
|
||||
image: CGImage,
|
||||
diagonalNoise: MLShapedArray<Float32>,
|
||||
noise: MLShapedArray<Float32>,
|
||||
alphasCumprodStep: AlphasCumprodCalculation
|
||||
) throws -> MLShapedArray<Float32> {
|
||||
let sample = try image.plannerRGBShapedArray
|
||||
let sqrtAlphasCumprod = MLShapedArray(scalars: [alphasCumprodStep.sqrtAlphasCumprod], shape: [1, 1])
|
||||
let sqrtOneMinusAlphasCumprod = MLShapedArray(scalars: [alphasCumprodStep.sqrtOneMinusAlphasCumprod], shape: [1, 1])
|
||||
|
||||
let dict: [String: Any] = [
|
||||
"sample": MLMultiArray(sample),
|
||||
"diagonalNoise": MLMultiArray(diagonalNoise),
|
||||
"noise": MLMultiArray(noise),
|
||||
"sqrtAlphasCumprod": MLMultiArray(sqrtAlphasCumprod),
|
||||
"sqrtOneMinusAlphasCumprod": MLMultiArray(sqrtOneMinusAlphasCumprod),
|
||||
]
|
||||
let featureProvider = try MLDictionaryFeatureProvider(dictionary: dict)
|
||||
|
||||
let batch = MLArrayBatchProvider(array: [featureProvider])
|
||||
|
||||
// Batch predict with model
|
||||
let results = try queue.sync { try model.predictions(fromBatch: batch) }
|
||||
|
||||
let batchLatents: [MLShapedArray<Float32>] = try (0..<results.count).compactMap { i in
|
||||
let result = results.features(at: i)
|
||||
guard
|
||||
let outputName = result.featureNames.first,
|
||||
let output = result.featureValue(for: outputName)?.multiArrayValue
|
||||
else {
|
||||
throw Error.latentOutputNotValid
|
||||
}
|
||||
print("output.shape: \(output.shape)")
|
||||
return MLShapedArray(output)
|
||||
}
|
||||
|
||||
guard let latents = batchLatents.first else {
|
||||
throw Error.batchLatentOutputEmpty
|
||||
}
|
||||
|
||||
return latents
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue