3

Language: Swift 3

What I am using: Hardware: An IPhone 6 and FLIR One thermal camera Software: Xcode 8.1, Locked down FLIROne SDK (written and documented for obj-c and has little documentation at all)

What I am trying to do: Get the temperature of the center pixel from the returned camera stream

The problem: I have everything in the app I am building setup and working as expected not including getting this thermal data. I have been able to get temperature from the returned stream from the camera, however I can only get it for the 0,0 (top left) pixel. I have crosshairs in the center of the image view that the stream feeds into and I would like to get the pixel at this center point (the exact center of the image). I have been working on this for two days and I can't seem to figure out how to do this. The SDK does not allow you to specify which pixel to read from and from what I have read on online, you have to loop through each pixel and stop at the center one.

The below code will get the temperature from pixel 0,0 and display it correctly. I want to get temperature to read center pixel instead. temperature MUST be UInt16 to provide correct Kelvin readout (from what I understand at least), but I receive the radiometric data from Data! as UInt8. NOTE: NSData! does not work with these delegates. Attempting to use it (even in swift 2.3) causes the delegate to never fire. I have to use Data! for it to even run the delegate. I found it very odd that I couldn't use swift 2.3 because it only has NSData. If this is because I screwed something up please let me know.

func flirOneSDKDelegateManager(_ delegateManager: FLIROneSDKDelegateManager!, didReceiveRadiometricData radiometricData: Data!, imageSize size: CGSize) {

    let byteArray = radiometricData?.withUnsafeBytes{
        [UInt8](UnsafeBufferPointer(start: $0, count: (radiometricData?.count)!))
    }

    let temperature = UnsafePointer(byteArray!).withMemoryRebound(to: UInt16.self, capacity: 1){
        $0.pointee
    }

    debugPrint(temperature)

    DispatchQueue.main.async{
        self.tempLabel.text = NSString(format: "%.1f",self.convertToFarenheit(kelvin: temperature)) as String
    }
}

I am new to Swift so I am not sure if this is the best way to do this so if you have a better way please advise me on it.

Current solution: Gets the center pixel, but not dynamically (which is what I want)

    let byteArray = radiometricData?.withUnsafeBytes{
        [UInt8](UnsafeBufferPointer(start: $0, count: (radiometricData?.count)!))
    }

    let bytes:[UInt8] = [(byteArray?[74170])!,(byteArray?[74171])!]

    let temperature = UnsafePointer(bytes).withMemoryRebound(to: UInt16.self, capacity: 1){
        $0.pointee
    }
Nox
  • 1,358
  • 1
  • 13
  • 25

1 Answers1

1

assuming you try to read some UInt16 values from swift's Data

import Foundation

var data = Data([1,0,2,0,255,255,1]) // 7 bytes
var arr: [UInt16] = []

// in my data only 6 bytes could be represented as Int16 values, so i have to ignore the last one ...
for i in stride(from: 0, to: 2 * (data.count / 2) , by: MemoryLayout<UInt16>.stride) {
    arr.append(data.subdata(in: i..<(i+MemoryLayout<UInt16>.stride)).withUnsafeBytes { $0.pointee })
}

print(arr) // [1, 2, 65535]

or you could use something like

let arr0: [UInt16] = data.withUnsafeBytes { (p: UnsafePointer<UInt8>)->[UInt16] in
    let capacity = data.count / MemoryLayout<UInt16>.stride
    return p.withMemoryRebound(to: UInt16.self, capacity: capacity) {
        var arr = [UInt16]()
        for i in 0..<capacity {
            arr.append(($0 + i).pointee)
        }
        return arr
    }
}
print(arr0) // [1, 2, 65535]

or

extension Data {
    func scan<T>(offset: Int, bytes: Int) -> T {
        return self.subdata(in: offset..<(offset+bytes)).withUnsafeBytes { $0.pointee }
    }
}

let k: UInt16 = data.scan(offset: 2, bytes: MemoryLayout<UInt16>.stride)
print(k) // 2

or even better

extension Data {
    func scan<T>(from: Int)->T {
        return self.withUnsafeBytes { (p: UnsafePointer<UInt8>)->T in
            p.advanced(by: from).withMemoryRebound(to: T.self, capacity: 1) {
                $0.pointee
            }
        }
    }
}
let u0: UInt16 = data.scan(from: 2)
print(u0) // 2

or

    extension Data {
        func scan2<T>(from: Int)->T {
            return self.withUnsafeBytes { 
// Precondition: The underlying pointer plus offset is properly aligned for accessing T.
// Precondition: The memory is initialized to a value of some type, U, such that T is layout compatible with U.
                 UnsafeRawPointer($0).load(fromByteOffset: from, as: T.self)
            }
        }
    }

    let u2: UInt16 = data.scan2(from: 2)
    print(u2) // 2

what is the right offset in your Data, to read the value? it is hard to say from the information you provided in your question.

user3441734
  • 16,722
  • 2
  • 40
  • 59
  • On the SDK message boards I was given this wonky looking for loop with all kinds of wrong calculations on it. The one thing everyone said was that I had to loop through the pixels to get to the center. I'm not sure if I am right on this, but the offset, I would say, is 2. Because Uint16 is the required value for temp and data returns Uint8, it takes two 8 bits to construct the 16 bit temp. So tempAtPixel0 = bytes = [byteArray[0],byteArray[1]]. It looks like one of your answers is pretty close to something I attempted but couldn't do so I will try it out soon. – Nox Nov 10 '16 at 14:14
  • Looking through your solutions mostly provide how to convert from data to uint16 array. I basically need: How do I take the uint8 byte array and grab the pixel in the center of the UIImageView. I wanted a way to dynamically get the center pixel. I can hardcode the center pixel which is byte 74170 and 74171, but I wanted to do it dynamically in the event a new higher res camera is released. I like your final post that with the data extension though and seems to be a good way of dealing with getting the data from the array. – Nox Nov 10 '16 at 18:53
  • @Nox we don't know anything about the binary format of you data. Most probably the middle of your "image data" is in the middle of the Data structure. Let say the Data has 1000 bytes (0 is the offset of the first Int16 value, 2 is the offset of the second Int16 value ..., 998 is the offset of the 500-th value). In that case use offset 500, because 500 = 1000 / (size of Int16 in bytes). What is the actual size of your Data if the bytes 74170 and 74171 represents the "middle pixel"? If I am true, the size should be 2*74170 :-), is it? – user3441734 Nov 12 '16 at 17:57
  • Oddly enough the center was NOT byteArray.count/2. I'm not sure what caused it, but that gave me a pixel on the edge for some weird reason. The way I got those numbers was I used a test item that was small and had an average temp of 86 and then printed out every temperature over 84 and took the center from the retrieved data and found it to be those pixels. This is confirmed to be almost the exact center of a 320x240 thermal image and works on all devices I have. Newer versions may change that size. The dataset is 153600 and center is supposed to be 76800 unless I am doing something wrong here – Nox Nov 15 '16 at 13:37