I made a FLIR photon 320 do the exact same thing when I tried to substitute a non-athermalized Thorlabs germanium lens for the factory lens.
Can't find any FLIR Photon 320 teardown to see what is inside this thing, but if we take even 2/3 of 320 resolution, so we get about 213 so close to this Seek has, but of course we still could have great night eye with this Photon 320 while these imaginery as it were takem during the day with nice contrast
Looking inside this thing probably will show much more solid shutter construction.
Just looking for other Flir's thermal cams teardowns to see how do they deal with calibration.
In Flir E4 it is done very well-nothing to compare to Seek weak dongle design...
While one of my friends wanted add thermal vision to their security system, so probably will try hack Sick Thermal to make software corrections of this gradient on calibrations frames-while image which camera see is static (camera is not moving) maybe it will be possible to distinguish dog and cat from humans
Fry-Kun,
My version developed in a very similar manner to yours, mine is a bit more complex, without some of the error checking
and with less python knowledge some parts are a bit brute force.
Here is what I have now.
It does real time view and expands the image to 640 x 480.
Still lots to do, colorize, and pixil 207 at least
cheers ...ken...
btw no black frames any more, that was a problem with mine too.;>))
# You will need to have python 2.7 (3+ may work)
# and PyUSB 1.0
# and PIL 1.1.6 or better
# and numpy
# and scipy
# and Tkinter
# and ImageMagick
# and other stuff when I get this working
# You will probably have to run this as root unless you get your udev/mdev rules
# set up to allow the Seek device to be used by other than root.
# Many thanks to the folks at eevblog, especially (in no particular order)
# miguelvp, marshallh, mikeselectricstuff, sgstair, Fry-kun and many others
# for the inspiration to figure this out
# This is not a finished product and you can use it if you like. Don't be
# suprised if there are bugs as I am NOT a programmer..... ;>))
# There is also a lot of test code sprinkled about which probably doesn't work.
# not all these are needed, must trim as required when this application is done
import usb.core
import usb.util
import sys
from PIL import Image, ImageTk
import numpy
from scipy.misc import toimage
import Image
from numpy import array
import Tkinter
class App(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# defs
def usbinit(self):
# find our Seek Thermal device 289d:0010
dev = usb.core.find(idVendor=0x289d, idProduct=0x0010)
# was it found?
if dev is None:
raise ValueError('Device not found')
# set the active configuration. With no arguments, the first
# configuration will be the active one
dev.set_configuration()
# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
ep = usb.util.find_descriptor(
intf,
# match the first OUT endpoint
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
assert ep is not None
return dev
#
def camerainit(self,dev):
# Deinit the device
msg= '\x00\x00'
assert dev.ctrl_transfer(0x41, 0x3C, 0, 0, msg) == len(msg)
assert dev.ctrl_transfer(0x41, 0x3C, 0, 0, msg) == len(msg)
assert dev.ctrl_transfer(0x41, 0x3C, 0, 0, msg) == len(msg)
# Setup device
#msg = x01
assert dev.ctrl_transfer(0x41, 0x54, 0, 0, 0x01)
# Some day we will figure out what all this init stuff is and
# what the returned values mean.
msg = '\x00\x00'
assert dev.ctrl_transfer(0x41, 0x3C, 0, 0, msg) == len(msg)
ret1 = dev.ctrl_transfer(0xC1, 0x4E, 0, 0, 4)
ret2 = dev.ctrl_transfer(0xC1, 0x36, 0, 0, 12)
#print ret1
#print ret2
#
msg = '\x20\x00\x30\x00\x00\x00'
assert dev.ctrl_transfer(0x41, 0x56, 0, 0, msg) == len(msg)
ret3 = dev.ctrl_transfer(0xC1, 0x58, 0, 0, 0x40)
#print ret3
#
msg = '\x20\x00\x50\x00\x00\x00'
assert dev.ctrl_transfer(0x41, 0x56, 0, 0, msg) == len(msg)
ret4 = dev.ctrl_transfer(0xC1, 0x58, 0, 0, 0x40)
#print ret4
#
msg = '\x0C\x00\x70\x00\x00\x00'
assert dev.ctrl_transfer(0x41, 0x56, 0, 0, msg) == len(msg)
ret5 = dev.ctrl_transfer(0xC1, 0x58, 0, 0, 0x18)
#print ret5
#
msg = '\x06\x00\x08\x00\x00\x00'
assert dev.ctrl_transfer(0x41, 0x56, 0, 0, msg) == len(msg)
ret6 = dev.ctrl_transfer(0xC1, 0x58, 0, 0, 0x0C)
#print ret6
#
msg = '\x08\x00'
assert dev.ctrl_transfer(0x41, 0x3E, 0, 0, msg) == len(msg)
ret7 = dev.ctrl_transfer(0xC1, 0x3D, 0, 0, 2)
#print ret7
#
msg = '\x08\x00'
assert dev.ctrl_transfer(0x41, 0x3E, 0, 0, msg) == len(msg)
msg = '\x01\x00'
assert dev.ctrl_transfer(0x41, 0x3C, 0, 0, msg) == len(msg)
ret8 = dev.ctrl_transfer(0xC1, 0x3D, 0, 0, 2)
#print ret8
#####################################################################
# build a matrix of "patent pixils" that match the Seek Imager
# this may be useful later
def dots(self):
dotsF = numpy.zeros((156,208))
dotsI = dotsF.astype('uint8')
k = 10
for i in range(0,155,1):
for j in range(k,206,15):
# print i,j
dotsI[i,j] = 255
k = k - 4
if k < 0: k = k + 15
return dotsI
# display it to see if it matches the Seek black dot hex pattern
# zz = Image.fromstring("I", (208,156), dotsI, "raw", "I;8")
# toimage(zz).show()
# print dotsI
#####################################################################
def printIMG(self,img):
global Label1, Label2
# print "img min= ", img.min()
# print "img max= ", img.max()
j = img.min()
k = img.max()
Label1.configure( text="Img min %d" % j)
Label2.configure( text="Img max %d" % k)
def printCAL(self,cal):
global Label3, Label4
# print "cal min= ", cal.min()
# print "cal max= ", cal.max()
j = cal.min()
k = cal.max()
Label3.configure( text="Cal min %d" % j)
Label4.configure( text="Cal max %d" % k)
def printSUM(self,add):
global Label5, Label6
# print "sum min =", add.min()
# print "sum max =", add.max()
j = add.min()
k = add.max()
Label5.configure( text="Sum min %d" % j)
Label6.configure( text="Sum max %d" % k)
def print3(self):
print additionF.shape
print additionF.dtype
print additionF.max()
print additionF.min()
print additionF.mean()
def read_frame(self,dev): # Send a read frame request
msg = '\xC0\x7E\x00\x00'
assert dev.ctrl_transfer(0x41, 0x53, 0, 0, msg) == len(msg)
# For some reason, we need to read the result in 4 blocks, setting the
# read length to 4*0x3F60 returns only 0x3F60 bytes. Why?? maybe it is a
# PyUSB thing, since it seems to work elsewhere.
data = dev.read(0x81, 0x3F60, 1000)
data += dev.read(0x81, 0x3F60, 1000)
data += dev.read(0x81, 0x3F60, 1000)
data += dev.read(0x81, 0x3F60, 1000)
return data
def add_207(self,imgF): # Add the data from the 207 row to each pixil
# or not depending on the testing some of the following may be commented out.
# there are a different # of black dots in each row so the divisor
# needs to change for each row according to what is in the dot_numbers.txt file.
# this may not be the best way to do this. The code below does not do this now.
for i in range(0,156,1):
for j in range(0,205,1):
if imgF[i,j] == 0:
# if dotsI[i,j] == 255:
imgF[i,j] = 0 # this should be all the "patent pixils" as well as the dead ones
else:
imgF[i,j] = imgF[i,j]+imgF[i,206]/14
return imgF
def initialize(self):
global dev, label, Label1, Label2, Label3, Label4, Label5, Label6
dev = self.usbinit()
self.camerainit(dev)
self.grid()
# self.entry = Tkinter.Entry(self)
# self.entry.grid(column=0,row=0,sticky='EW')
# button = Tkinter.Button(self,text=u"Click me !")
# button.grid(column=1,row=0)
Label1 = Tkinter.Label(self,text="Label1")
Label2 = Tkinter.Label(self,text="Label2")
Label3 = Tkinter.Label(self,text="Label3")
Label4 = Tkinter.Label(self,text="Label4")
Label5 = Tkinter.Label(self,text="Label5")
Label6 = Tkinter.Label(self,text="Label6")
Label1.grid(row=2,column=1)
Label2.grid(row=3,column=1)
Label3.grid(row=2,column=2)
Label4.grid(row=3,column=2)
Label5.grid(row=2,column=3)
Label6.grid(row=3,column=3)
label = Tkinter.Label(self,text="your image here", compound="top")
label.grid(column=0,row=1,columnspan=2,sticky='EW')
self.grid_columnconfigure(0,weight=1)
self.resizable(True,True)
self.iteration=0
global calImage
self.calimage = self.get_cal_image(dev)
self.UpdateImage(10)
def UpdateImage(self, delay, event=None):
# this is merely so the display changes even though the image doesn't
global dev, status, calImage, ImageFinal, label
self.iteration += 1
self.image = self.get_image(dev)
ImageFinal = self.image
label.configure(image=ImageFinal, text="Frames captured %s" % self.iteration)
# reschedule to run again in 10 ms
self.after(delay, self.UpdateImage, 10)
def get_cal_image(self,dev):
# Get the first cal image so calImage isn't null
global status, calImage
status = 0
# Wait for the cal frame
while status != 1:
# 1 is a Calibration frame
# Read a raw frame
ret9 = self.read_frame(dev)
status = ret9[20]
# 6 is a pre-calibration frame (whatever that means)
# 4, 9, 8, 7, 5, 10 other... who knows.
# See https://www.eevblog.com/forum/testgear/yet-another-cheap-thermal-imager-incoming/msg545910/#msg545910
# for examples.
# Convert the raw 16 bit calibration data to a PIL Image
calimgI = Image.frombytes("F", (208,156), ret9, "raw", "F;16")
# Convert the PIL Image to an unsigned numpy float array
im2arr = numpy.asarray(calimgI)
im2arrF = im2arr.astype('float')
# im2arrI16 = im2arr.astype('uint16')
calImage = im2arrF
return
def get_image(self,dev):
# this is where you get your image and convert it to
# a Tk PhotoImage. For demonstration purposes I'll
# just return a static image
global calImage, status
status = 0
# Wait for the next image frame
while status != 3:
# 3 is a Normal frame
# Read a raw frame
ret9 = self.read_frame(dev)
status = ret9[20]
# check for a new cal frame, if so update the cal image
if status == 1:
# Convert the raw 16 bit calibration data to a PIL Image
calimgI = Image.frombytes("F", (208,156), ret9, "raw", "F;16")
# Convert the PIL Image to an unsigned numpy float array
im2arr = numpy.asarray(calimgI)
im2arrF = im2arr.astype('float')
# Save the calibration image
calImage = im2arrF
# If this is normal image data
# Convert the raw 16 bit thermal data to a PIL Image
imgx = Image.fromstring("F", (208,156), ret9, "raw", "F;16")
# Convert the PIL Image to an unsigned numpy float array
im1arr = numpy.asarray(imgx)
im1arrF = im1arr.astype('float')
# Add the row 207 correction
# im1arrF_207 = self.add_207(im1arrF)
self.printIMG(im1arrF)
self.printCAL(calImage)
# Subtract the most recent calibration image from the offset image data
additionI16 = ((im1arrF) + 5000) - (calImage)
self.printSUM(additionI16)
# imxx = Image.fromarray(numpy.uint8(cm.gray(img)*255))
imxx = toimage(additionI16).resize((640, 480),Image.ANTIALIAS)
image = ImageTk.PhotoImage(imxx)
return image
if __name__ == "__main__":
app=App(None)
app.title('Kens test')
app.mainloop()
Hello miguelvp. I spent many lenses for testing thermal imager. 90 percent probability that you just threw the money on the lens. Focus is very large. The diameter of the smallest. Not enough for the sensor sensitivity. I already have a negative experience. I spent $ 500 on a different lens. Often data datasheet does not coincide with the real. Make calculations aperture lenses. Then take the other with a large aperture or equal. Otherwise will not work. On a photo a good selection of lenses.
I get it. For macro may work. I did lenses to increase range. Greater focus and small diameter does not work. When I get the imager, the lens will change. May be able to increase the range.
Fry-Kun,
My version developed in a very similar manner to yours, mine is a bit more complex, without some of the error checking
and with less python knowledge some parts are a bit brute force.
Here is what I have now.
It does real time view and expands the image to 640 x 480.
Still lots to do, colorize, and pixil 207 at least
cheers ...ken...
btw no black frames any more, that was a problem with mine too.;>))
LOL, was just going through the code and changed the offset to +8000 (you originally had it at +800) and that got rid of black frames too
It looks like in time calibration frames changes differences between subsequent calibration frames shows this nasty non uniform Seek sensor readings.
Attached those three choosen raw 16bit calibration frames 1,6,13 from one of router files provided before there-this one:
$ md5sum sts_router_frames.raw
52d19a3324a800fb15245f77542a6dec sts_router_frames.raw
However, this raw calibration data looks strange and histograms shows that senor values goes not high but down, but this is without correction from 207th column, while this image processing was done in
imagej2 on those 16bit raw PNGs attached, so now it's time to automate this calibration frames analysis process with OpenCV support and lets create 3D animation in OpenGL how this sensor values changes in time
BTW: Probably I've found where temperature is stored, but it will require setup with thermometers, while there were no information what ambient temperature was when this dongle was switched on, so will have to wait when this Sick Thermal thing will land in my PC Linux USB slot soon and next in
Angstrom distribution to watch for night ghosts
I did order a ZnSe lens 101.6 mm (4") focus length and it probably will take another week or two to get here.
I have a couple of those cheap $14 ZnSe 20mm x f50mm lenses. They are pretty good as macro extention for Seek Thermal.
I've attached with and without screenshots of the board with MC in small QFP and a sot32 LDO at the top right corner. You can easily distinct each 0.5mm pitch pins! And even tiny 0402 resistor at the right side of the MC that serves as a current limiter for LED.
Not bad for $200 cam!
Not bad for $200 cam!
$214 + shippments costs so closser to $250 with this mod
But of course there is no reason for those PCB orange hot spots on the top and temp rise in the left top corner, which is masked by this HUGE black overlay with 17*C below-this is of course this what we already know
This is only proof that this cam has hardware issue inside and it must be not stable sensor temperature which leads to non uniform calibration images and affects thermal images taken by this cam.
However, I'll see what hapends if we apply this calibration frame correction using different approximation methods
At $200, nobody can complain about the camera. The biggest limiting factor probably is the lens assembly, followed by software. They had to build it down to a price, and the lens took a hit to the budget. Also, the software they provide is good enough to play with, but it misses the mark. The output imagery from a sensor of this resolution should be near quality of a 320x240 imager. Not as sharp, but it should be resolving a lot more than it does. Bad pixels aside, I think it boils down to data getting lost in the post processing. We have some talented people working on alternative software and the images coming out are already better. I'm actually getting excited about miguelvp's software. He's getting along, has some commercial potential.
$214 + shippments costs so closser to $250 with this mod
Both seek and the lens are free shipped to US, so it is $215 indeed
Thermal gradient is still a big issue, I know. On mine it's less than 1C at cold start, and max out to 4C after ~4-5 minutes.
my $15 lens is free shipping as well as the camera was and no taxes either, so I will corroborate DEHiCKA's total bill.
Edit: plus the 3d lens housing which I'm getting for free from a coworker even if I offered to pay, it's such a small part that he doesn't care and helps him keep the printer calibrated
Also, the software they provide is good enough to play with, but it misses the mark.
Yep, maybe it is good to find a cat, but one have to be carefull to confound with a bad dog due to not great image quality
There is no any temperature scale vs used thermal LUT, so a lot of things on its todo list
Anyway we have some calibration frame data ploted in 3D, and now we are ready to make gradient compensation, while it is visible that calibration shutter temperature reading in Seek Thermal sensor is not flat (those spikes in corners are only results of additonal 1 pixel size boundary so not important there).
Lets see what hapends after another ones corners gradients corrections
Eneuro is that just a heightmap? What exactly does that show?
@efahrenholz This is normalized sensor output based on average values of 207th column...
Just reloaded my statistics memory, so a few matrix operations should compute quite fast needed parameters of choosen approximation model, however input matrix have 32448 rows and at least 6 parameters, but hopefully this system is optimized for 1 Hz output frame rate, so it should be easy task for modern multicore PC, while thermal data will be send via encrypted and compressed SSH channel to our "inteligence" servers using wifi/ethernet, so probably 128KB/s between sensor and server will be fine with compression enabled
Close up lenses are used to provide detailed images of PCB's etc.
I've put up a simple holder for the 20mm ZnSe lenses over at Thingiverse. It's just a friction fit which depends on the asymmetry of the camera face to hold it in place, but it seems to do the trick. Should do well enough to keep handy in the toolbag. http://www.thingiverse.com/thing:525605
@RAWebb, I did get a friend to print it for me but it doesn't fit, maybe his printer is not calibrated and ended up smaller.
The inner clearance on the part he gave me is only 2 cm where the camera housing is 2.1 cm.
Could you tell me what the inner diameter is supposed to be?
Thanks.
Edit: I can always cut some slots in three places so that it will make it grab the housing, so I should be able to still use it.
I measured the seek lens at its widest being 21.35mm it tapers, so the external lens should be around 21.4mm inner diameter to assure a snug fit.
Yay! My SEEK has just arrived
I am in bed ill so can't play.
? I am in UK so can someone please post the latest version of the APP for Android so that I can side load it to my Moto G.
Packaging is very impressive but they missed an opportunity by not including a simple manual that explained thermal imaging to newbies. They need to know about emissivity at least.
It may sound daft but I had not realised just how small this unit is.... Beautiful and well designed case. Top marks to Seek on that front.
Aurora
I'll wait for my Seek Thermal. I would be grateful for the file apk.
1.4.0.2
http://apk-dl.com/root/apk/2014/11/3/com.tyriansystems.SeekThermal_1.4.0.2_[www.apk-dl.com].apk
Or search apk-dl.com for "com.tyriansystems.SeekThermal" for the latest version
Thank you !!!
Has anyone measured the distance from the lens to the matrix? And what is the diameter of the lens. I will make the lens.
Many thanks to you both from me as well
Time to play me thinks
Aurora
I honestly google searched "what is thermal emissivity of fur", but I couldn't find my answer
Which setting should I pick to get the most accurate temp reading? Thanks for your help