#!/usr/bin/env python
# -*- coding: utf-8 -*-
# beeMonitor.py
# Bee Monitor - Detects Bees Using a Microphone to Listen for a Characteristic Buzzing Frequency
# Bees buzz at 190 Hz ... 250 Hz. Allowance made for doppler effects of moving bees.
# Designed by David Guidos, 2017
import sys
import numpy
import time
import json
import pyaudio
import pygame
import argparse
import os
from subprocess import check_call
# get command line arguments
#parser = argparse.ArgumentParser(description='Arguments for BeeDetector.')
#parser.add_argument('usePyGame', metavar='N', type=bool, nargs=1,
# help='Display grahical data?')
#args = parser.parse_args()
# functional set up
# TODO: set up as command line parameters
usePyGame = True # TODO: allow disabling
useNova = False
samplesPerPublish = 20 # audio samples before publishing to Hologram Nova
# audio set up
mic = None
FS=44100 # sampling frequency; standard CD rate
SAMPLES=1024 # approx 40 sample packets / sec. if not overlapped using a callback, processing lowers to approx. 20 / sec
MIC_DEVICE = 1 # TODO: enumerate to find mic device?
# bee detection variables
beeMinFFTIndex = 30
beeMaxFFTIndex = 37
# P Y G A M E
#
# set canvas parameters
size = width, height = 1100, 700
speed = [100, 100]
redColor = pygame.Color(255, 0, 0)
blueColor = pygame.Color(0, 0, 255)
greenColor = pygame.Color(0, 255, 0)
blackColor = pygame.Color(0, 0, 0)
whiteColor = pygame.Color(255, 255, 255)
grayColor = pygame.Color(150, 150, 150)
# A U D I O
#
def get_audioSample():
global mic
if mic is None:
pa = pyaudio.PyAudio()
mic = pa.open(format=pyaudio.paInt16, channels=1, rate=FS,
input_device_index = MIC_DEVICE, input=True,
frames_per_buffer=SAMPLES)
return numpy.fromstring(mic.read(SAMPLES), dtype=numpy.short)
def get_powerSpectrum(amplitudes):
return abs(numpy.fft.fft(amplitudes / 32768.0))[:SAMPLES/2]
def plot_sound(amplitudes):
# show title
screen.blit(soundLabel, (10, 10))
# determine canvas positions
yUsable = (height / 2) - 60
yRange = yUsable / 2
yBase = 10
x = -1
maxAmplitude = max(max(amplitudes), -min(amplitudes))
previousY = 0
for amplitude in amplitudes:
x += 1
#y = (-float(amplitude) / float(maxAmplitude)) * yRange # for automatic scaling
y = (-float(amplitude) / 4096.0) * yRange # fixed scaling
# plot this amplitude
#lineRect = pygame.draw.line(screen, blueColor, (x + 10, yRange + y + 10), (x + 10, yRange - y + 10), 1)
lineRect = pygame.draw.line(screen, blueColor, (x + 10, yBase + yRange + previousY), (x + 10, yBase + yRange + y), 1)
previousY = y
#print max(amplitudes)
def plot_powerSpectrum(powerArray):
# show title
screen.blit(spectrumLabel, (10, 330))
screen.blit(spectrumFrequencies, (10, height - 25))
# determine canvas positions
yUsable = (height / 2) - 60
yBase = height - 40
x = -1
freqIndex = powerArray.argmax(axis=0) # primary frequency
maximumPower = max(powerArray)
print "Freq Index: " + str(freqIndex)
for powerValue in powerArray:
x += 1
y = powerValue / maximumPower * yUsable
if (x == freqIndex):
# show power level for primary sample frequency in red
lineColor = redColor
else:
# normal power levels in black
lineColor = blackColor
# plot the power level for this sample value
lineRect = pygame.draw.line(screen, lineColor, (x + 10, yBase), (x + 10, yBase - y), 1)
def plot_detectionLevels(levels):
yUsable = (height / 3) - 60
yBase = height - 40
xBase = width - 500
x = -1
maximumLevel = 50 # max(levels)
lineColor = blueColor
previousY = -1
for level in levels:
x += 1
y = float(level) / float(maximumLevel) * float(yUsable)
if previousY == -1:
previousY = y
# plot the detection level for this sample value
lineRect = pygame.draw.line(screen, lineColor, (xBase + (x - 1) * 2, yBase - previousY), (xBase + x * 2, yBase - y), 1)
previousY = y
def detectBees(frequencyIndex, previousFrequencyIndex):
if (frequencyIndex >= beeMinFFTIndex) and (frequencyIndex <= beeMaxFFTIndex) and (previousFrequencyIndex >= beeMinFFTIndex) and (previousFrequencyIndex <= beeMaxFFTIndex):
beesDetected = 1
else:
beesDetected = 0
return beesDetected
# F L I R T H E R M A L I M A G E R O U T I N E S
currentThermalImageString = ""
previousThermalImageString = ""
def captureThermalImage():
global currentThermalImageString
global previousThermalImageString
imageFileName = 'IMG_0000.pgm'
check_call('./raspberrypi_capture')
pgm_string = pgmString(imageFileName)
if pgm_string != "":
previousThermalImageString = currentThermalImageString
currentThermalImageString = pgm_string
os.remove(imageFileName)
def pgmString(pgmFileName):
pgm_string = ""
with open(pgmFileName, "r") as pgmFile:
pgm_string = pgmFile.read()
return pgm_string
def drawPGM((x0, y0)):
if currentThermalImageString != "":
pgmList = currentThermalImageString.split()
hdrP2 = pgmList[0]
xSize =int(pgmList[1])
ySize =int(pgmList[2])
maxValue = int(pgmList[3])
# double size in both x and y directions
for n in range(4, len(pgmList)):
x = (n - 4) % xSize
y = int((n -4) / xSize)
x = x * 2
y = y * 2
grayLevel = pgmList[n]
if grayLevel > 255:
grayLevel = 255
c = (grayLevel, grayLevel, grayLevel)
screen.set_at((x0 + x, y0 + y), c)
screen.set_at((x0 + x + 1, y0 + y), c)
screen.set_at((x0 + x, y0 + y + 1), c)
screen.set_at((x0 + x + 1, y0 + y + 1), c)
screen.blit(thermalLabel, (x0, y0 + ySize * 2 + 10))
def thermalImageAnomalyDetected():
# check for significant thermal activity
variationCount = 0
if currentThermalImageString != "" and previousThermalImageString != "":
currentList = currentThermalImageString.split()
previousList = previousThermalImageString.split()
for n in range(4, len(currentList)):
if abs(int(currentList[n]) - int(previousList[n])) > 8:
variationCount += 1
return (variationCount > 128)
# H O L O G R A M N O V A
def sendAlert(message):
alertCommand = 'sudo hologram send "' + message + '"'
os.system(alertCommand)
def sendBeeActivity():
alertCommand = 'sudo hologram send "Bee Activity Level:' + str(currentBees) + '"'
os.system(alertCommand)
# M A I N
# init the game engine
if usePyGame:
pygame.init( )
# display title on canvas and clear the display
pygame.display.set_caption("Bee Detector")
screen = pygame.display.set_mode(size)
screen.fill(whiteColor)
gameFont = pygame.font.SysFont("monospace", 15)
gameFont2 = pygame.font.SysFont("monospace", 30)
# create text
soundLabel = gameFont.render("Sound data", 1, blackColor)
spectrumLabel = gameFont.render("Power spectrum", 1, blackColor)
spectrumFrequencies = gameFont.render("0Hz 5kHz 10kHz 15kHz 20kHz 25kHz 30kHz 35kHz 40kHz 45kHz", 1, blueColor)
thermalLabel = gameFont.render("Thermal Image", 1, blackColor)
# create bee image
beeImage = pygame.image.load("bee.png")
# render the surface
pygame.display.flip()
#start = time.time()
abort = False
currentBees = 0
detectionLevels = []
previousFrequencyIndex = 0
sampleCount = 0
beesDetectedCount = 0
while not abort:
try:
amplitudes = get_audioSample()
power = get_powerSpectrum(amplitudes)
primaryFrequencyIndex = power.argmax(axis=0)
# detect bees
beesDetected = detectBees(primaryFrequencyIndex, previousFrequencyIndex)
beesDetectedCount += beesDetected
previousFrequencyIndex = primaryFrequencyIndex
sampleCount += 1
# update bee detection level
if beesDetected == 1:
currentBees += 2
else:
currentBees -= 1
if currentBees < 0:
currentBees = 0
print ("Bee Detection Index: " + str(currentBees))
# put current bee detection level into the detection levels array
# limits the size to 200 elements
detectionLevels.append(currentBees)
if len(detectionLevels) > 200:
detectionLevels.pop(0)
# display bee level
if usePyGame:
beesDetectedLabel = gameFont.render("Bee Detection Index: ", 1, blackColor)
beesDetectedValue = gameFont2.render(str(currentBees), 1, redColor)
except (IOError):
#print("IOError: " + str(IOError))
continue
if usePyGame:
# clear the canvas
screen.fill(whiteColor)
# show bees detected
screen.blit(beesDetectedLabel, (600, 330))
screen.blit(beesDetectedValue, (800, 320))
# show bee image
beeImageScaled = pygame.transform.scale(beeImage, (5 * currentBees + 25, 5 * currentBees + 25))
screen.blit(beeImageScaled, (600, 360))
# display the sound data
plot_sound(amplitudes)
# display the sound spectrum
plot_powerSpectrum(power)
# display the detection level history
plot_detectionLevels(detectionLevels)
# display the thermal image
captureThermalImage()
drawPGM((900, 550))
if thermalImageAnomalyDetected():
# send alert using Holgram Nova
sendAlert("Thermal anomaly detected!")
# update the display
pygame.display.flip()
# check whether to publish the sample data
if sampleCount >= samplesPerPublish:
# send bee activity level
if currentBees > 50:
sendBeeActivity()
#abort = True
Comments