Skip to content
This repository was archived by the owner on May 20, 2020. It is now read-only.
106 changes: 92 additions & 14 deletions alertwildfire_live_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,33 @@
import db_manager
import hashlib
import random


import OCR
import dateutil
import itertools


def build_name_with_metadata(image_base_name,metadata):
"""reformats image name to include positional metadata
Args:
image_base_name (str): original image name containing only camera name and timestamp
metadata (dict): individual camera metadata pulled from alertwildfire_API.get_individual_camera_info
metadata (dict): individual camera metadata pulled from alertwildfire_API.get_individual_camera_info or OCR from image
Returns:
imgname (str): name of image with positionalmetadata
"""


cameraName_chunk = image_base_name[:-23]
metadata_chunk = 'p'+str(metadata['position']['pan'])+'_t'+str(metadata['position']['tilt'])+'_z'+str(metadata['position']['zoom'])
timeStamp_chunk = image_base_name[-23:-4]+'__'
cameraName_chunk = metadata['name']+"__"
metadata_chunk = 'p'+str(metadata['pan'])+'_t'+str(metadata['tilt'])+'_z'+str(metadata['zoom'])
if metadata['date']:
timeStamp_chunk = metadata['date'].replace('/','-')+'T'+metadata['time'].replace(':',';').split('.')[0]+'__'
else:
timeStamp_chunk = image_base_name[-23:-4]+'__'
fileTag=image_base_name[-4:]
imgname = cameraName_chunk+timeStamp_chunk+metadata_chunk+fileTag
return imgname




def capture_and_record(googleServices, dbManager, outputDir, camera_name):
"""requests current image from camera and uploads it to cloud
Args:
Expand All @@ -77,7 +82,7 @@ def capture_and_record(googleServices, dbManager, outputDir, camera_name):

imgPath = alertwildfire_API.request_current_image(outputDir, camera_name)
pull2 = alertwildfire_API.get_individual_camera_info(camera_name)
if pull1['position'] == pull1['position']:
if pull1['position'] == pull2['position']:
success = True
else:
pull1 = pull2
Expand All @@ -94,15 +99,88 @@ def capture_and_record(googleServices, dbManager, outputDir, camera_name):


image_base_name = pathlib.PurePath(imgPath).name
image_name_with_metadata = build_name_with_metadata(image_base_name,pull1)
#implement the ocr
vals = OCR.pull_metadata("Axis", filename = imgPath ).split()
logging.warning('values read by OCR %s',vals)
if len(vals) == 0:
logging.warning('OCR Failed image recorded using motor information')

metadata = {key:None for key in ["name", "date", "time","timeStamp","pan","tilt","zoom"]}
""" format
metadata = {
"name" : camera_name,
"date" : [elem for elem in vals if elem.count("/") == 2][0],
"time" : [elem for elem in vals if elem.count(":") == 2][0],
"timeStamp" : #unix timestamp
"pan" : float([elem for elem in vals if "X:" in elem][0][2:]),
"tilt" : float([elem for elem in vals if "Y:" in elem][0][2:]),
"zoom" : float([elem for elem in vals if "Z:" in elem][0][2:]),
}"""
"""
common error cases in OCR recognition of Axis metadata
X= x,x,x,x,x
Y= v, v,V,V
Z= z,7,2,2,2,2,2,2,z,2,2,2,2,2,2,2,2,2
.= -,:,_,(" "),(" "),(" "),(" "),(" "),(" "),(" "),(" ")
:= i, -,1,1,;,1,1,2,(" "),(" "),(" "),(" "),(" "),(" ")
-= ~,_,r,(","),7,r,7,7,(" "),V,v,r,7,r,(" "),(" ")
+=-
... more cases exist for natural letters
Note, cannot use cases where numbers and chars are mixed because there are likely inverse cases natural to the meta data i.e. Z:->2: which looks is found in 12:12:42
"""
cases = {"pan" :[''.join(elem) for elem in list(itertools.product(['X','x'],[':','.','_']))],
"tilt" :[''.join(elem) for elem in list(itertools.product(['Y','y','V','v'],[':','.','_']))],
"zoom" :[''.join(elem) for elem in list(itertools.product(['Z','z'],[':','.','_']))],
}
status = 'ocrworking'
try:
metadata["name"] = camera_name
metadata["date"] = [elem for elem in vals if elem.count("/") == 2][0]
metadata["time"] = [elem for elem in vals if elem.count(":") == 2][0]
dt = dateutil.parser.parse(metadata['date']+'T'+metadata['time'].split('.')[0])
metadata["timeStamp"] = time.mktime(dt.timetuple())
except Exception as e:
status ='ocrfailure'


for key in ["pan","tilt","zoom"]:
if status == 'ocrfailure':
break
for attempts in cases[key]:
try:
metadata[key] = float([elem for elem in vals if attempts in elem][0][2:])
break
except Exception as e:
try:
metadata[key] = float([elem for elem in vals if attempts in elem][0][3:]) * (pull1['position'][key]/np.absolute(pull1['position'][key]))
break
except Exception as e:
logging.warning('OCR Failed @ %s, attempt %s',key,attempts)
if metadata[key] == None:
status ='ocrfailure'

for key in metadata.keys():
if metadata[key] == None:
status = 'ocrfailure'
if status == 'ocrfailure':# revert to use of metdata
logging.warning('OCR Failed image recorded using motor information')
metadata = {
"name" : camera_name,
"date" : None,
"time" : None,
"pan" : pull1['position']['pan'],
"tilt" : pull1['position']['tilt'],
"zoom" : pull1['position']['zoom'],
}
metadata["timeStamp"] = img_archive.parseFilename(image_base_name)['unixTime']

image_name_with_metadata = build_name_with_metadata(image_base_name,metadata)
cloud_file_path = 'alert_archive/' + camera_name + '/' + image_name_with_metadata
goog_helper.uploadBucketObject(googleServices["storage"], settings.archive_storage_bucket, cloud_file_path, imgPath)


#add to Database
timeStamp = img_archive.parseFilename(image_base_name)['unixTime']
img_archive.addImageToArchiveDb(dbManager, camera_name, timeStamp, 'gs://'+settings.archive_storage_bucket, cloud_file_path, pull1['position']['pan'], pull1['position']['tilt'], pull1['position']['zoom'], md5)

#add to Database
img_archive.addImageToArchiveDb(dbManager, camera_name, metadata["timeStamp"], 'gs://'+settings.archive_storage_bucket, cloud_file_path, metadata['pan'], metadata['tilt'], metadata['zoom'], md5)



Expand Down Expand Up @@ -171,7 +249,7 @@ def test_System_response_time(googleServices, dbManager, trial_length = 10):


def main():
"""directs the funtionality of the process ie start a cleanup, record all cameras on 2min refresh, record a subset of cameras, manage multiprocessed recording of cameras
"""directs the funtionality of the process ie start a cleanup, record all cameras on (2min refresh/rotating,1min cadence/stationary, record a subset of cameras, manage multiprocessed recording of cameras
Args:
-c cleaning_threshold" (flt): time in hours to store data
-o cameras_overide (str): list of specific cameras to watch
Expand Down
227 changes: 227 additions & 0 deletions lib/OCR.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@

# Copyright 2018 The Fuego Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================


import sys
import os
fuegoRoot = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(fuegoRoot, 'lib'))
sys.path.insert(0, fuegoRoot)
import settings
settings.fuegoRoot = fuegoRoot
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.image import imread
from PIL import Image
import tempfile
import math
import logging
import string
import pytesseract
import time

#generalized OCR will attempt to compare img against all characters inclusive of
#UTF-8,etc. As both camera networks restrict their outputs to ASCII this improves the
#efficiency and accuracy of the OCR script by defining expected allowed characters
char_whitelist = string.digits
char_whitelist += string.ascii_lowercase
char_whitelist += string.ascii_uppercase
char_whitelist += string.punctuation.replace("'","").replace('"','')


def load_image( infilename ) :
"""loads an image file to an array
Args:
infilename: file path
Returns:
numpy array of image data
"""
im = imread(infilename)
return np.array(im)

def save_image( npdata, outfilename ) :
"""saves an image file from an array
Args:
npdata: (array) numpy array of image data
outfilename: (str)strfile path
Returns:
None
"""
outimg = Image.fromarray( npdata, "RGB" )
outimg.save( outfilename, format='JPEG' )


def ocr_crop(image,outputname = None,maxHeight=60):
"""saves an image file from an array
Args:
image (str): image path
opt outputname (str): save crop to address
opt maxHeight (int): maximum height to search for metadata else default 60
Returns:
npdata (array): numpy array of cropped image data
bottom (int): height of bottom of metadata from image bottom
top (int):height of top of metadata from image bottom
"""

cushionRows = 4
minLetterBrightness = int(.8*255) # % of max value of 255
minLetterSize = 12

img = Image.open(image)
assert maxHeight < img.size[1]
try:
imgCroppedGray = img.crop((1, img.size[1] - maxHeight, 2*minLetterSize, img.size[1])).convert('L')#possibility to apply to hprwen under simuliar parameters
croppedArray = np.array(imgCroppedGray)

except Exception as e:
logging.error('Error processing image: %s', str(e))
return

top = 0
bottom = 0
mode = 'find_dark_below_bottom'
for i in range(maxHeight):
row = maxHeight - i - 1
maxVal = croppedArray[row].max()
# logging.warning('Mode = %s: Top %d, Bottom %d, Max val for row %d is %d', mode, top, bottom, row, maxVal)
if mode == 'find_dark_below_bottom':
if maxVal < minLetterBrightness:
mode = 'find_bottom'
elif mode == 'find_bottom':
if maxVal >= minLetterBrightness:
mode = 'find_top'
bottom = row
elif mode == 'find_top':
if maxVal < minLetterBrightness:
possibleTop = row
if bottom - possibleTop > minLetterSize:
top = possibleTop
break

if not top or not bottom:
logging.error('Unable to locate metadata')
return

# row is last row with letters, so row +1 is first without letters, and add cushionRows
bottom = min(img.size[1] - maxHeight + bottom + 1 + cushionRows, img.size[1])
# row is first row without letters, so subtract cushionRows
top = max(img.size[1] - maxHeight + top - cushionRows, img.size[1] - maxHeight)

logging.warning('Top = %d, bottom = %d', top, bottom)
imgOut = img.crop((0, top, img.size[0], bottom))
img.close()
if outputname:
imgOut.save(outputname, format='JPEG')
npdata = np.array(imgOut)
return npdata, bottom, top


def cut_metadata(im, camera_type):
"""locates and cuts the metadata tag from image
Args:
im (str) : filepath
camera_type (str): {'hpwren','Axis','unknown'} defined type of image to remove metadata from.
Returns:
metadatastrip (array): numpy array containing only the presumed metadata line
"""

if camera_type == 'unknown':#needs update
logging.warning('unkown has not been implemented yet')
return

if camera_type == 'Axis':
#output = im[:-4]+"_cutout"+im[-4:]
maxHeight=60
metadatastrip, metabottom, metatop = ocr_crop(im,maxHeight=maxHeight)


return metadatastrip
if camera_type == 'hpwren':#uses first order central difference gradient in 1-D to determine edges of text
im = load_image( im )
index = 10
xview =10

while 30>index:
pt1up = np.sum(im[index-1,:xview,:])
pt1down =np.sum(im[index+1,:xview,:])
if np.abs(.5*pt1down-.5*pt1up)>160*xview:#(np.sum(im[index,:xview,:]) <1000) and (np.sum(im[index+1,:xview,:]) <1000):
index=math.ceil(index*1.5)#index+=3#add a buffer for lower than average chars like g,j,q,p...
break
index+=1
metadatastrip = im[:index,:,:]
return metadatastrip
return None











def ocr_core(filename=None, data=None):
"""
This function will handle the core OCR processing of images.
Args:
opt filename (str) : filepath
opt data (array): data
Returns:
text (str): string of OCR recognized data
"""
if filename:
text = pytesseract.image_to_string(load_image( filename ),config="-c tessedit_char_whitelist=%s_-." % char_whitelist)
elif type(data) == np.ndarray:
text = pytesseract.image_to_string(data,config="-c tessedit_char_whitelist=%s_-." % char_whitelist)
else:
logging.warning('Please feed in processable data to ocr_core of type filename or data')
return
return text



def pull_metadata(camera_type,filename = None, save_location=False):
""" function to separate metadata from image
Args:
opt filename (str) : filepath
camera_type (str): {'hpwren','Axis','unknown'} defined type of image to remove metadata from.
opt save_location (str): filepath to save metadata strip to
Returns:
vals (list): list of OCR recognized data
"""
if not filename:
logging.warning('specify data location of data itself')
return
tic=time.time()
metadata = cut_metadata(filename,camera_type)
logging.warning('time to complete cropping: %s',time.time()-tic)
try:
tic=time.time()
vals = ocr_core(data = metadata)
logging.warning('time to complete OCR: %s',time.time()-tic)
except Exception as e:
vals = ''
if save_location:
save_image(metadata,save_location)
logging.warning('metadata strip saved to location, %s',save_location)
return vals





Binary file added lib/test_OCR1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added lib/test_OCR2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading