-
-
Save reagames/5bab03f0706b2f7d2b6c79b72e6a1ca3 to your computer and use it in GitHub Desktop.
Python script for Raspberry Pi to run Seek Thermal Camera, with openCV and pygame output - based on Seek by Cynfab http://www.eevblog.com/forum/thermal-imaging/yet-another-cheap-thermal-imager-incoming/msg571129/#msg571129
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
########################################################################### | |
# | |
# | |
# This is a program to read thermal image data from the Seek PIR206 Thermal Camera | |
# This program is intended to be used with Raspberry Pi | |
# Pygame allows to output directly to framebuffer. | |
# Here I use small TFT display connected to GPIO and sending data to framebuffer fb1 for output to this display. | |
# I also use three simple GPIO buttons to have realtime control for image processing parameters | |
# This comes particularly useful e.g. when you use Seek at low temperature (you will need to lower down "lower threshold limit" | |
# And you can change color palettes with a click of button | |
# Menu use: middle button - cycle through menu items | |
# 1 - TFT screen backlight | |
# 2 - Lower threshold limit (lowest temperature reading to show on display) | |
# 3 - Upper threshold limit (lowest temperature reading which will be shown as 100% white (255) pixel | |
# 4 - Calibration adjuctment (how calibration impacts the ID3 image data | |
# 5 - Calibration delay in frames count | |
# 6 - Color palette mode (can use openCV palettes or Custom look up table (LUT) based palette | |
# You can add other modes with increasing UiMaxModes parameter | |
# In this version of code the calibration is done every N frames, where N is counterAct parameter, which can be adjusted realtime using buttons | |
# | |
# Raspberry Pi 3 runs this processing realtime, Pi Zero rather slowly | |
# You will need to have python 2.7 | |
# and PyUSB 1.0 (needs to be gotten as source from Github and installed as root) | |
# and PIL (Pillow fork, often in debian distros) | |
# and numpy (often in debian base distros) | |
# and scipy | |
# and OpenCV | |
# and pygame | |
# and maybe some other stuff | |
# You have to run this as root (sudo python) | |
# Many thanks to the folks at eevblog, especially (in no particular order) | |
# Cynfab (code including comments is heavily used here as this is a fork of original code) | |
# miguelvp, marshallh, mikeselectricstuff, sgstair, Fry-kun, frenky and many others | |
# Use this product as you like and at your own risk :) | |
########################################################################### | |
########################################################################### | |
# | |
#Based on beautiful code from cynfab. | |
#This is a port for fbtft-enabled Raspberry Pi to output directly to connected tft display. | |
#Particularly in this case it's a display with ST7735 controller. | |
#Pygame is used to pass image data stream to fbtft driver. | |
#Display is custom wired (as described in fbtft wiki for 1.8 Adafruit display but backlight pin is changed to apply PWM for dimming) | |
# | |
########################################################################### | |
#Core imports | |
import usb.core | |
import usb.util | |
import sys | |
import cv2 | |
from PIL import Image | |
import numpy | |
from numpy import array | |
from scipy.misc import toimage | |
from scipy import ndimage | |
import matplotlib | |
import math | |
#external module imports | |
import RPi.GPIO as GPIO | |
import time | |
#import fbtft driver | |
import pygame, sys, os | |
from pygame.locals import * | |
#Reload display driver | |
command = 'sudo modprobe -r fbtft_device' | |
p = os.system('sudo %s' % (command)) | |
print "display reset" | |
time.sleep(0.1) # delay for 0.1 seconds | |
#Initialize display | |
command = 'sudo modprobe fbtft_device custom name=adafruit18 width=128 height=160 rotate=90' | |
p = os.system('sudo %s' % (command)) | |
time.sleep(0.1) | |
print "display started" | |
#init pygame output | |
os.environ["SDL_FBDEV"] = "/dev/fb1" | |
pygame.init() | |
MAINSURF=pygame.display.set_mode((160, 128),0,32) | |
DISPLAYSURF=pygame.Surface((160, 128)) | |
pygame.mouse.set_visible(0) | |
# set up the colors BGR | |
BLACK = ( 0, 0, 0) | |
WHITE = (255, 255, 255) | |
BLUE = (0, 0, 255) | |
GREEN = ( 0, 255, 0) | |
RED = ( 255, 0, 0) | |
UIGREEN = (8, 150, 8 ) | |
UIRED = (220, 77 , 48) | |
UIBLUE = (20, 80, 160) | |
UIWHITE = (128, 128 , 128) | |
UIGRAY = (50, 50 , 50) | |
#init pygame timer | |
clock = pygame.time.Clock() | |
#global calibration array variables | |
imcalib = 0 | |
imgain = 0 | |
#frame variables | |
counter=0 | |
mode=0 | |
try: | |
import pygame.surfarray as surfarray | |
except ImportError: | |
raise ImportError, "pygame failed to initialize" | |
#pygame font init | |
pygame.font.init() | |
myfont = pygame.font.SysFont('Nimbus Sans', 15) | |
#GUI variables | |
uiMenuItem = 0 | |
uiSelector = 0 | |
uiTimer=0 | |
uiVisible = 0 | |
scl=1 | |
scl1=26 | |
uiCrosshair=0 | |
uiColormode=0 | |
uiColormodesMax=5 | |
uiMaxItems=7 | |
redlight=0 | |
pal=200 | |
killtimer=0 | |
actnum=0 | |
dbgX=292 | |
dbgY=0 | |
dc = 50 # duty cycle (0-100) for PWM backlight pin | |
#GPIO Pin definitons: make sure they are not mixed with tft connections | |
fwPin = 16 # Broadcom pin 16 - p36 R3 | |
midPin = 20 # Broadcom pin 20 - p38 R3 | |
revPin = 21 # Broadcom pin 17 - p40 R3 | |
ledPin = 26 # Broadcom pin 26 - p37 R3 | |
GPIO.cleanup() # cleanup all GPIO | |
GPIO.setmode(GPIO.BCM) # Broadcom pin-numbering scheme | |
GPIO.setup(ledPin, GPIO.OUT) # PWM pin set as output | |
pwm = GPIO.PWM(ledPin, 50) # Initialize PWM on pwmPin 50Hz frequency | |
pwm.start(dc) | |
#Setup GPIO buttons | |
GPIO.setup(fwPin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button pin set as input w/ pull-up | |
GPIO.setup(midPin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button pin set as input w/ pull-up | |
GPIO.setup(revPin, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button pin set as input w/ pull-up | |
#Thermal camera class | |
class Thermal: | |
def close(event): | |
print("closing...") | |
GPIO.cleanup() # cleanup all GPIO | |
sys.exit() # if you want to exit the entire thing | |
# 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 | |
# send_msg sends a message that does not need or get an answer | |
def send_msg(self,dev,bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None): | |
assert (dev.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength, timeout) == len(data_or_wLength)) | |
# alias method to make code easier to read | |
# receive msg actually sends a message as well. | |
def receive_msg(self,dev,bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None): | |
zz = dev.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength, timeout) # == len(data_or_wLength)) | |
return zz | |
# De-init the device | |
def deinit(self,dev): | |
msg = '\x00\x00' | |
for i in range(3): | |
self.send_msg(dev,0x41, 0x3C, 0, 0, msg) # 0x3c = 60 Set Operation Mode 0x0000 (Sleep) | |
# Camera initilization | |
def camerainit(self,dev): | |
try: | |
msg = '\x01' | |
self.send_msg(dev,0x41, 0x54, 0, 0, msg) # 0x54 = 84 Target Platform 0x01 = Android | |
except Exception as e: | |
self.deinit(dev) | |
msg = '\x01' | |
self.send_msg(dev,0x41, 0x54, 0, 0, msg) # 0x54 = 84 Target Platform 0x01 = Android | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x00\x00') # 0x3c = 60 Set operation mode 0x0000 (Sleep) | |
ret1 = self.receive_msg(dev,0xC1, 0x4E, 0, 0, 4) # 0x4E = 78 Get Firmware Info | |
#print ret1 | |
#array('B', [1, 3, 0, 0]) | |
#ret2 = self.receive_msg(dev,0xC1, 0x36, 0, 0, 12) # 0x36 = 54 Read Chip ID | |
#print ret2 | |
#array('B', [20, 0, 12, 0, 86, 0, 248, 0, 199, 0, 69, 0]) | |
self.send_msg(dev,0x41, 0x56, 0, 0, '\x20\x00\x30\x00\x00\x00') # 0x56 = 86 Set Factory Settings Features | |
#ret3 = self.receive_msg(dev,0xC1, 0x58, 0, 0, 0x40) # 0x58 = 88 Get Factory Settings | |
#print ret3 | |
#array('B', [2, 0, 0, 0, 0, 112, 91, 69, 0, 0, 140, 65, 0, 0, 192, 65, 79, 30, 86, 62, 160, 137, 64, 63, 234, 149, 178, 60, 0, 0, 0, 0, 0, 0, 0, 0, 72, 97, 41, 66, 124, 13, 1, 61, 206, 70, 240, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 66, 0, 0, 2, 67]) | |
self.send_msg(dev,0x41, 0x56, 0, 0, '\x20\x00\x50\x00\x00\x00') # 0x56 = 86 Set Factory Settings Features | |
#ret4 = self.receive_msg(dev,0xC1, 0x58, 0, 0, 0x40) # 0x58 = 88 Get Factory Settings | |
#print ret4 | |
#array('B', [0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 161, 248, 65, 63, 40, 127, 119, 60, 44, 101, 55, 193, 240, 133, 129, 63, 244, 253, 96, 66, 40, 15, 155, 63, 43, 127, 103, 186, 9, 144, 186, 52, 0, 0, 0, 0, 0, 0, 2, 67, 0, 0, 150, 67, 0, 0, 0, 0]) | |
self.send_msg(dev,0x41, 0x56, 0, 0, '\x0C\x00\x70\x00\x00\x00') # 0x56 = 86 Set Factory Settings Features | |
#ret5 = self.receive_msg(dev,0xC1, 0x58, 0, 0, 0x18) # 0x58 = 88 Get Factory Settings | |
#print ret5 | |
#array('B', [0, 0, 0, 0, 255, 255, 255, 255, 190, 193, 249, 65, 205, 204, 250, 65, 48, 42, 177, 191, 200, 152, 147, 63]) | |
self.send_msg(dev,0x41, 0x56, 0, 0, '\x06\x00\x08\x00\x00\x00') # 0x56 = 86 Set Factory Settings Features | |
#ret6 = self.receive_msg(dev,0xC1, 0x58, 0, 0, 0x0C) # 0x58 = 88 Get Factory Settings | |
#print ret6 | |
#array('B', [49, 52, 48, 99, 49, 48, 69, 52, 50, 78, 55, 49]) | |
self.send_msg(dev,0x41, 0x3E, 0, 0, '\x08\x00') # 0x3E = 62 Set Image Processing Mode 0x0008 Normal, 0x00 no shutter, other modes also possible, try at you own risk | |
#ret7 = self.receive_msg(dev,0xC1, 0x3D, 0, 0, 2) # 0x3D = 61 Get Operation Mode | |
#print ret7 | |
#array('B', [0, 0]) | |
self.send_msg(dev,0x41, 0x37, 0, 0, '\x00\x00') # 0x37 = 55 Toggle shutter | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x01\x00') # 0x3c = 60 Set Operation Mode 0x0001 (Run) | |
#ret8 = self.receive_msg(dev,0xC1, 0x3D, 0, 0, 2) # 0x3D = 61 Get Operation Mode | |
#print ret8 | |
#array('B', [1, 0]) | |
def read_frame(self,dev): # Send a read frame request | |
self.send_msg(dev,0x41, 0x53, 0, 0, '\xC0\x7E\x00\x00') # 0x53 = 83 Set Start Get Image Transfer | |
try: | |
data = dev.read(0x81, 0x3F60, 1000) | |
data += dev.read(0x81, 0x3F60, 1000) | |
data += dev.read(0x81, 0x3F60, 1000) | |
data += dev.read(0x81, 0x3F60, 1000) | |
except usb.USBError as e: | |
print "device error" | |
GPIO.cleanup() # cleanup all GPIO | |
sys.exit() | |
return data | |
def set_fast(self,dev): #set no shutter mode | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x00\x00') # 0x3c = 60 Set operation mode 0x0000 (Sleep) | |
time.sleep(0.05) | |
self.send_msg(dev,0x41, 0x3E, 0, 0, '\x00\x00') #send image processing mode change to No Shutter | |
time.sleep(0.05) | |
print "mode changed to NS" | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x01\x00') # 0x3c = 60 Set operation mode 0x0001 (Run) | |
return | |
def set_normal(self,dev): #set normal frame mode | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x00\x00') # 0x3c = 60 Set operation mode 0x0000 (Sleep) | |
time.sleep(0.01) | |
self.send_msg(dev,0x41, 0x3E, 0, 0, '\x08\x00') #send image processing mode change | |
self.send_msg(dev,0x41, 0x37, 0, 0, '\x00\x00') | |
time.sleep(0.01) | |
#print "mode changed to normal" | |
self.send_msg(dev,0x41, 0x3C, 0, 0, '\x01\x00') # 0x3c = 60 Set operation mode 0x0001 (Run) | |
return | |
################################### | |
#--------------------*************-------------------# | |
# Start of main routine | |
#--------------------*************-------------------# | |
# Main program starts here (you can tell I'm new to Python ;) | |
#Initialization | |
def initialize(self): | |
global dev, scl, scl1, scl2 | |
global calImage, calImagex, pal, snapshot, killtimer | |
# Default palette is "iron" | |
snapshot = 0 | |
# Set up device | |
dev = self.usbinit() | |
self.camerainit(dev) | |
# get a cal image so the data isn't null if/when we miss the first one | |
self.get_cal_image(dev) | |
# var to store adj scl1 value | |
# self.topadj=0 | |
#init fast frame mode | |
self.set_fast(dev) | |
# Start the update image routine with defined delay | |
self.UpdateImage(0.001) | |
# End of the initilization routine | |
#--------------------*************-------------------# | |
# This is actually the main loop | |
#--------------------*************-------------------# | |
def UpdateImage(self, delay, event=None): | |
global scl, scl1, dev, status, calImage, label, dc, pal, counter, mode, actnum | |
global uiMenuItem, uiSelector, uiTimer, uiVisible, uiMaxItems, redlight, uiCrosshair, dbgX, uiColormode, uiColormodesMax, killtimer | |
while 1: | |
#time.sleep(0.001) | |
self.image = self.get_image(dev) | |
#increase frame counter to enable mode change | |
counter+=1 | |
if mode==0: #ff mode | |
if counter>180: | |
self.set_normal(dev) | |
mode=1 | |
counter=0 | |
elif mode==1: | |
#if counter>0: | |
self.get_cal_image(dev) | |
self.set_fast(dev) | |
mode=0 | |
counter=0 | |
#go on with GPIO | |
if GPIO.input(midPin): # button is released | |
uiSelector=0 | |
killtimer=0 | |
else: | |
#shutdown function | |
killtimer+=1 | |
actnum = killtimer | |
if killtimer>=50: | |
killtimer=0 | |
print "shutdown" | |
command = 'sudo shutdown -h now' | |
p = os.system('sudo %s' % (command)) | |
if uiSelector==0: | |
uiSelector = 1 | |
#cycle through menu items | |
uiMenuItem += 1 | |
if uiMenuItem > uiMaxItems: | |
uiMenuItem=1 | |
uiTimer = 20 | |
print(uiMenuItem) | |
#Toggle menu visibility | |
if uiTimer>0: | |
uiTimer-=1 | |
uiVisible=1 | |
if uiTimer<=0: | |
uiVisible=0 | |
uiMenuItem=0 | |
#________GUI_________# | |
if uiVisible==1: | |
#button controls within menu items | |
# place text on framebuffer screen | |
text2 = myfont.render('%d' %(actnum), False, (0, 255, 0)) | |
newtextsurface=pygame.transform.rotate(text2,180) | |
MAINSURF.blit(newtextsurface,(20,50)) | |
if uiMenuItem==1: #LCD brightness | |
actnum = dc | |
#Control lines | |
pygame.draw.line(MAINSURF, UIGREEN, (10, 10), (100, 10),2)# | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 20), (100, 20),1) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 30), (100, 30),1) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 40), (100, 40),1) | |
pygame.draw.circle(MAINSURF, UIBLUE, (100-dc, 10), 3, 0)# | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl, 20), 2, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl1, 30), 2, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100- int(0.08 * dbgX), 40), 2, 0) | |
#Handle +/- button clicks | |
if GPIO.input(fwPin)==0: | |
uiTimer = 20 | |
dc+=1 | |
if dc>=95: | |
dc=95 | |
pwm.ChangeDutyCycle(dc) | |
actnum = dc | |
elif GPIO.input(revPin)==0: | |
uiTimer = 20 | |
dc-=1 | |
if dc<=2: | |
dc=2 | |
pwm.ChangeDutyCycle(dc) | |
actnum = dc | |
if uiMenuItem==2:#Low contrast threshold | |
actnum = scl | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 10), (100, 10),1) | |
pygame.draw.line(MAINSURF, UIGREEN, (10, 20), (100, 20),2) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 30), (100, 30),1) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 40), (100, 40),1) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-dc, 10), 2, 0) | |
pygame.draw.circle(MAINSURF, UIBLUE, (100-scl, 20), 3, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl1,30), 2, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100- int(0.08 * dbgX), 40), 2, 0) | |
#Handle +/- button clicks | |
if GPIO.input(fwPin)==0: | |
uiTimer = 20 | |
scl+=1 | |
if scl>=100: | |
scl=100 | |
actnum = scl | |
elif GPIO.input(revPin)==0: | |
uiTimer = 20 | |
scl-=1 | |
if scl<=1: | |
scl=1 | |
actnum = scl | |
if uiMenuItem==3:#High Contrast threshold | |
actnum = scl1 | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 10), (100, 10),1) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 20), (100, 20),1)# | |
pygame.draw.line(MAINSURF, UIGREEN, (10, 30), (100, 30),2) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 40), (100, 40),1) | |
#pygame.draw.line(DISPLAYSURF, UIWHITE, (10, 40), (110, 40),1) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-dc, 10), 2, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl, 20), 2, 0)# | |
pygame.draw.circle(MAINSURF, UIBLUE, (100-scl1, 30), 3, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100- int(0.08 * dbgX), 40), 2, 0) | |
#Handle +/- button clicks | |
if GPIO.input(fwPin)==0: | |
uiTimer = 20 | |
scl1+=1 | |
if scl1>=100: | |
scl1=100 | |
actnum = scl1 | |
elif GPIO.input(revPin)==0: | |
uiTimer = 20 | |
scl1-=1 | |
if scl1<=1: | |
scl1=1 | |
actnum = scl1 | |
if uiMenuItem==4:#calibration threshold | |
actnum = dbgX | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 10), (100, 10),1) | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 20), (100, 20),1)# | |
pygame.draw.line(MAINSURF, UIWHITE, (10, 30), (100, 30),1) | |
pygame.draw.line(MAINSURF, UIGREEN, (10, 40), (100, 40),2) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-dc, 10), 2, 0) | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl, 20), 2, 0)# | |
pygame.draw.circle(MAINSURF, UIWHITE, (100-scl1, 30), 2, 0) | |
pygame.draw.circle(MAINSURF, UIBLUE, (100- int(0.08 * dbgX), 40), 3, 0) | |
#Handle +/- button clicks | |
if GPIO.input(fwPin)==0: | |
uiTimer = 20 | |
dbgX+=4 | |
if dbgX>=800: | |
dbgX=800 | |
actnum = dbgX | |
elif GPIO.input(revPin)==0: | |
uiTimer = 20 | |
dbgX-=4 | |
if dbgX<=0: | |
dbgX=0 | |
actnum = dbgX | |
#Draw crosshair | |
pygame.draw.line(MAINSURF, UIRED, (58, 55), (62, 55),1) | |
pygame.draw.line(MAINSURF, UIRED, (60, 53), (60, 57),1) | |
if killtimer>10: | |
pygame.draw.circle(MAINSURF, UIGREEN, (60, 55), killtimer, 10) | |
if killtimer>40: | |
pygame.draw.circle(MAINSURF,UIRED, (60, 55), killtimer, 10) | |
#After all, do some display update | |
pygame.display.update() | |
#--------------------*************-------------------# | |
# End of main loop | |
#--------------------*************-------------------# | |
def get_cal_image(self,dev): | |
# Get the first cal image so calImage isn't null | |
global status, calImage, calImagex, calimgI, imcalib | |
status = 0 | |
while status != 1: | |
# Read a raw frame | |
ret9 = self.read_frame(dev) | |
status = ret9[20] | |
status1 = ret9[80] | |
#print (status , status1) | |
# 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) | |
#clamp values < 2000 to 2000 | |
im2arr = numpy.where(im2arr < 2000, 2000, im2arr) | |
im2arrF = im2arr.astype('float') | |
calImage = im2arrF | |
imcalib=calImage | |
return | |
def get_image(self,dev): | |
global calImage,calimgI, calImagex, status, scl, scl1, pal, snapshot, imcalib, imgain, actnum | |
global uiMenuItem, uiSelector, uiTimer, uiVisible, uiCrosshair, dbgX, uiColormode | |
global MAINSURF | |
status = 0 | |
# Wait for the next image frame, ID = 3 is a Normal frame | |
while status != 3: | |
# Read a raw frame | |
ret9 = self.read_frame(dev) | |
status = ret9[20] | |
#print status | |
# 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) | |
# clamp values < 2000 to 2000 | |
im2arr = numpy.where(im2arr < 2000, 2000, im2arr) | |
im2arrF = im2arr.astype('float') | |
# Clamp pixel 40 to 2000 so it doesn't cause havoc as it rises to 65535 | |
im2arrF[0,40] = 2000 | |
#Add the row 207 correction (maybe) >>Looks like it needs to be applied to just the cal frame<< | |
#self.add_207(im2arrF) | |
#Zero out column 207 | |
#im2arrF[:,206] = numpy.zeros(156) | |
#Save the calibration image | |
calImage = im2arrF | |
imcalib = calImage | |
print "calibrated" | |
#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") | |
imgx = Image.fromstring("F", (208,156), ret9, "raw", "F;16N") | |
#Convert the PIL Image to an unsigned numpy float array | |
im1arr = numpy.asarray(imgx) | |
#clamp values < 2000 to 2000 | |
im1arr = numpy.where(im1arr < 2000, 2000, im1arr) | |
im1arrF = im1arr.astype('float') | |
#Clamp pixel 40 to 2000 so it doesn't cause havoc as it rises to 65535 | |
im1arrF[0,40] = 2000 | |
#Subtract the most recent calibration image from the offset image data | |
#With both the cal and image as floats, the offset doesn't matter and | |
#the following image conversion scales the result to display properly | |
additionF = (im1arrF) + 200 + dbgX - calImage | |
#Crop or resize image to match display (pygame surface) area | |
#noiselessF = additionF[14:142, 24:184] | |
noiselessF = cv2.resize(additionF,(160, 128), interpolation = cv2.INTER_LINEAR) | |
bottom = scl | |
top = scl1 | |
display_min = bottom * 4 | |
display_max = top * 16 | |
noiselessF.clip(display_min, display_max, out=noiselessF) | |
noiselessF -= display_min | |
noiselessF //= (display_max - display_min + 1) / 256. | |
#convert and denoise image | |
noiselessF = noiselessF.astype(numpy.uint8) | |
noiselessF = ndimage.median_filter(noiselessF, 3) | |
#convert color and rotate | |
noiselessF = cv2.cvtColor(noiselessF,cv2.COLOR_GRAY2RGB) | |
noiselessF = numpy.rot90(noiselessF,1) | |
noiselessF = numpy.flipud(noiselessF) | |
#fill image to pygame surface | |
dst_ary = pygame.surfarray.pixels3d(DISPLAYSURF) | |
dst_ary[...] = noiselessF | |
del dst_ary | |
#DISPLAYSURF.unlock() | |
MAINSURF.blit(DISPLAYSURF,(0,0)) | |
#count fps | |
clock.tick() | |
fps = clock.get_fps() | |
print fps | |
# place text on framebuffer screen | |
text = myfont.render('FPS: %d' %(fps), False, (255, 0, 0)) | |
newtextsurface=pygame.transform.rotate(text,180) | |
MAINSURF.blit(newtextsurface,(118,110)) | |
App=Thermal() | |
App.initialize() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment