Skip to content

Instantly share code, notes, and snippets.

@tomscholz
Last active March 23, 2023 11:07
Show Gist options
  • Save tomscholz/e340003779491e91661bb79c26160186 to your computer and use it in GitHub Desktop.
Save tomscholz/e340003779491e91661bb79c26160186 to your computer and use it in GitHub Desktop.

Software to Draw Bee Flight Paths I wrote the software tracks.py that identifies and draws the bee flight paths one weekend. It is a primitive and experimental command-line program that requires Python (version 2.7 or newer, but will need fixes for Python 3), the Python Image Library, and the Numpy Python module. It only operates on images, and cannot read video formats. To convert video to a sequence of images I use ffmpeg. All of these packages run on Mac, Windows and Linux.

Here is a command example:

  ffmpeg -i beevideo.mp4 image-%04d.png
  python tracks.py image-%04d.png --start 1 --end 300 --animate
  ffmpeg -i track-%04d.png beetracks.mp4

The first command turns the video into individual image frames, the second command draws bee flight lines, and the third command assembles the images with flight lines back into a video. The result beetracks.mp4 is a video with traced lines added for bee flight paths. An image file called tracks.png is also written that shows all the flight paths superposed on the background image.

Software Help Here is the help output from the tracks.py program.

./tracks.py --help

usage: tracks.py [-h] [--start i] [--end j] [--window n] [--smooth n] [--threshold level] [--spacing d] [--color {same,solid,time}] [--nolines] [--animate] [--ave file] [--var file] [--out file] [--vidout file_pat] [--verbose n] image_pattern

This program displays the flight paths of bees exiting and entering a hive by analyzing a sequence of video image frames. It can combine the video frame images to make a single images showing lines tracking each bee, and it can make a copy of the video frames with lines added that trace the bee flight paths.

positional arguments: image_pattern File name pattern for input images.

optional arguments: -h, --help show this help message and exit --start i First image frame file suffix number. Default 1. --end j Last image frame file suffix number. Default 60. --window n Number of frames in moving average for background image. Default 61. --smooth n
Difference images (image minus average image) are smoothed where each smoothing step sums adds to each pixel 1/4 times the four closest neighbor pixels and divides by 2. Default 8. --threshold level
Minimum color variation from background for detection. Variation is sqrt(rr + gg + b*b) where r,g,b = color difference. Color component range is usually 0-255. Default 10.0. --spacing d
Minimum number of pixels between detected maxima in difference images. A maximum position is removed if some higher maximum is closer than this number of pixels. Default 12.0. --color {same,solid,time}

                    How the color is chosen for coloring spots on the images where
                    variation is above the threshold.  Choices are "same", "solid", or "time".
                    Solid colors high variation pixels red.  Time colors
                    pixels by the frame number where highest variation
                    was seen proceeding from first to last frame using rainbow colors
                    red, orange, yellow, green, cyan, blue.  Same means colors
                    the spot using the color from the image having largest variation from average.
                    This superimposes the bees from all frames in one image. Default same.

--nolines
Draw line tracks tracing bee paths on average image. Green lines are used for accelerating motions, usually bees flying away from hive, otherwise red. Bee locations for each frame are shown with yellow dots, with a yellow square at the end of tracks where bees are in the final image frame. --animate
Output each image frame with colored lines added for bee tracks. The line shows the start of the bee track up to the position in the current frame. The current end of a track (current bee location) is marked with a yellow square. For frames after a bee track ends, the track line will be shortened from where the track starts by N segments if the current frame is N frames beyond the end frame of the track. This makes the tracks gradually disappear instead of suddenly vanishing. --ave file File to save average of images with no smoothing or windowing. Default None. --var file File to save variation of images with no smoothing or windowing. Default None. --out file File for image of all tracks superimposed on average image. Default tracks.png. --vidout file_pat File pattern for output frames with bee track lines. Default track-%04d.png. --verbose n Print diagnostic output for values 1 or larger. Default 0.

This program reads image files (e.g. png, jpeg, ppm formats) and cannot read video formats directly. To convert video to individual images frames you can use the ffmpeg program with a command like "ffmpeg -i beevideo.mp4 image-%04d.png". This program writes images. To assemble a video with animated tracks use "ffmpeg -i track-%04d.png track.mp4"

This program requires Python (tested with version 2.7.5), and two Python libraries: the Python Image Library (tested with version 1.1.7) for reading and writing images, and numpy (tested with version 1.6.2) for doing the image analysis. The Python Image Library is currently (2014) not available for Python 3. I tested on Mac OS 10.6 and 10.9. The code should work on Linux and Windows as Python, PIL, numpy and ffmpeg are available on all 3 operating systems.

The method for finding bee tracks is to compute an average image which represents the static background of the video (hive box, plants, ...) then compare each video frame to the average looking for differences which are flying bees. A bee is identified by looking for pixel positions in frames that differ from the average background by more than a specified threshold. The difference image is first smoothed to reduce false detections due to noise. Then local maxima in the difference image are found. Weaker maxima are eliminated if they are too close to another maxima. This avoids having one bee produce several maxima in a given image frame. Then maxima in consecutive frames and close in space are joined to form a path. The average image uses a moving window, typically consisting of 30 frames before and 30 frames after the analyzed image frame or 1 second before and after for video at 30 frames / second. A short time average is desirable to avoid changes in brightness due to moving clouds. The method for tracking a bee from frame to frame is very basic. It starts at a bee position in one frame and looks for the nearest bee position in the next frame. From that next frame position it looks back to see if the nearest position in the current frame is the position we started with, if not it rejects that connection. It also does not accept bee motions from one frame to the next greater than 4 times the motion for that bee in the previous frame. If a next frame position for a bee is not found it will look ahead one more frame, to account for cases where a bee was not seen in a frame most likely because it was against a similar color background. Paths are extended forward and backward in time and a bee position can only be used in one path. If an extrapolated path goes outside the image frame, then the nearest position in the next frame is not used and the path terminates. No momentum requirement that prevents the bee from reversing direction too fast is imposed. There is currently no code that rejects small plant motions -- so they are often seen as bee tracks when the wind blows small leaves or flowers.

This code was tested with video at 640 x 480 pixels (small to speed up processing) with the camera about 2 meters from the hive entrance. The camera is on a tripod. Hand-held video is unlikely to work due to small camera motions. This code does not do any image stabilization. Example output is shown at

http://sonic.net/~goddard/home/bees/bees.html

This program was just a weekend experiment, so its algorithm is primitive, and the implementation is sloppy and slow. It will probably require fiddling with parameters such as the threshold and spacing to work with videos with different resolution, lighting, or distance from bees.

#!/usr/bin/env python
# -----------------------------------------------------------------------------
# Parse command-line arguments to track bees in video images.
#
def track():
doc = '''
This program displays the flight paths of bees exiting and entering a hive by analyzing
a sequence of video image frames. It can combine the video frame images to make a single
images showing lines tracking each bee, and it can make a copy of the video frames with
lines added that trace the bee flight paths.
'''
details = '''
This program reads image files (e.g. png, jpeg, ppm formats) and cannot read video formats
directly. To convert video to individual images frames you can use the ffmpeg program
with a command like "ffmpeg -i beevideo.mp4 image-%04d.png". This program writes images.
To assemble a video with animated tracks use "ffmpeg -i track-%04d.png track.mp4"
This program requires Python (tested with version 2.7.5), and two Python libraries:
the Python Image Library (tested with version 1.1.7) for reading and writing images,
and numpy (tested with version 1.6.2) for doing the image analysis. The Python Image
Library is currently (2014) not available for Python 3. I tested on Mac OS 10.6 and 10.9.
The code should work on Linux and Windows as Python, PIL, numpy and ffmpeg are available
on all 3 operating systems.
The method for finding bee tracks is to compute an average image which represents the
static background of the video (hive box, plants, ...) then compare each video frame to
the average looking for differences which are flying bees. A bee is identified by looking
for pixel positions in frames that differ from the average background by more than a specified
threshold. The difference image is first smoothed to reduce false detections due to noise.
Then local maxima in the difference image are found. Weaker maxima are eliminated if they
are too close to another maxima. This avoids having one bee produce several maxima in a given
image frame. Then maxima in consecutive frames and close in space are joined to form a path.
The average image uses a moving window, typically consisting of 30 frames before and 30 frames
after the analyzed image frame or 1 second before and after for video at 30 frames / second.
A short time average is desirable to avoid changes in brightness due to moving clouds.
The method for tracking a bee from frame to frame is very basic. It starts at a bee position
in one frame and looks for the nearest bee position in the next frame. From that next frame
position it looks back to see if the nearest position in the current frame is the position
we started with, if not it rejects that connection. It also does not accept bee motions
from one frame to the next greater than 4 times the motion for that bee in the previous frame.
If a next frame position for a bee is not found it will look ahead one more frame, to account
for cases where a bee was not seen in a frame most likely because it was against a similar
color background. Paths are extended forward and backward in time and a bee position can
only be used in one path. If an extrapolated path goes outside the image frame, then the
nearest position in the next frame is not used and the path terminates. No momentum requirement
that prevents the bee from reversing direction too fast is imposed. There is currently no
code that rejects small plant motions -- so they are often seen as bee tracks when the wind
blows small leaves or flowers.
This code was tested with video at 640 x 480 pixels (small to speed up processing) with
the camera about 2 meters from the hive entrance. The camera is on a tripod. Hand-held video
is unlikely to work due to small camera motions. This code does not do any image stabilization.
Example output is shown at
http://sonic.net/~goddard/home/bees/bees.html
This program was just a weekend experiment, so its algorithm is primitive, and the implementation
is sloppy and slow. It will probably require fiddling with parameters such as the threshold and
spacing to work with videos with different resolution, lighting, or distance from bees.
'''
import argparse
p = argparse.ArgumentParser(description = doc, epilog = details,
formatter_class=argparse.RawTextHelpFormatter)
p.add_argument('image_file_pattern', metavar = 'image_pattern',
help = 'File name pattern for input images.')
p.add_argument('--start', dest = 'first_frame', metavar = 'i',
type = int, default = 1,
help = 'First image frame file suffix number. Default %(default)s.')
p.add_argument('--end', dest = 'last_frame', metavar = 'j',
type = int, default = 60,
help = 'Last image frame file suffix number. Default %(default)s.')
p.add_argument('--window', dest = 'averaging_window', metavar = 'n',
type = int, default = 61,
help = 'Number of frames in moving average for background image. Default %(default)s.')
p.add_argument('--smooth', dest = 'smoothing_steps', metavar = 'n',
type = int, default = 8, help =
'''
Difference images (image minus average image) are smoothed where
each smoothing step sums adds to each pixel 1/4 times the four closest
neighbor pixels and divides by 2. Default %(default)s.
''')
p.add_argument('--threshold', dest = 'threshold', metavar = 'level',
type = float, default = 10.0, help =
'''
Minimum color variation from background for detection.
Variation is sqrt(r*r + g*g + b*b) where r,g,b = color difference.
Color component range is usually 0-255. Default %(default)s.
''')
p.add_argument('--spacing', dest = 'maxima_spacing', metavar = 'd',
type = float, default = 12.0, help =
'''
Minimum number of pixels between detected maxima in difference images.
A maximum position is removed if some higher maximum is closer
than this number of pixels. Default %(default)s.
''')
p.add_argument('--color', dest = 'color_mode', choices = ('same', 'solid', 'time'),
default = 'same', help =
'''
How the color is chosen for coloring spots on the images where
variation is above the threshold. Choices are "same", "solid", or "time".
Solid colors high variation pixels red. Time colors
pixels by the frame number where highest variation
was seen proceeding from first to last frame using rainbow colors
red, orange, yellow, green, cyan, blue. Same means colors
the spot using the color from the image having largest variation from average.
This superimposes the bees from all frames in one image. Default %(default)s.
''')
p.add_argument('--nolines', dest = 'draw_lines',
action = 'store_false', default = True, help =
'''
Draw line tracks tracing bee paths on average image. Green lines are
used for accelerating motions, usually bees flying away from hive, otherwise red.
Bee locations for each frame are shown with yellow dots, with a yellow square
at the end of tracks where bees are in the final image frame.
''')
p.add_argument('--animate', dest = 'animate', action = 'store_true',
default = False, help =
'''
Output each image frame with colored lines added for bee tracks.
The line shows the start of the bee track up to the position in the current frame.
The current end of a track (current bee location) is marked with a yellow square.
For frames after a bee track ends, the track line will be shortened
from where the track starts by N segments if the current frame is N frames beyond
the end frame of the track. This makes the tracks gradually disappear instead
of suddenly vanishing.
''')
p.add_argument('--ave', dest = 'average_image_file', metavar = 'file', help =
'File to save average of images with no smoothing or windowing. Default %(default)s.')
p.add_argument('--var', dest = 'variation_image_file', metavar = 'file', help =
'File to save variation of images with no smoothing or windowing. Default %(default)s.')
p.add_argument('--out', dest = 'tracks_image_file', metavar = 'file', default = 'tracks.png', help =
'File for image of all tracks superimposed on average image. Default %(default)s.')
p.add_argument('--vidout', dest = 'animation_image_file_pattern', metavar = 'file_pat',
default = 'track-%04d.png', help =
'File pattern for output frames with bee track lines. Default %(default)s.')
p.add_argument('--verbose', dest = 'verbose', metavar = 'n', type = int, default = 0,
help = 'Print diagnostic output for values 1 or larger. Default %(default)s.')
a = p.parse_args()
image_files = [a.image_file_pattern % i for i in range(a.first_frame, a.last_frame+1)]
if a.average_image_file or a.variation_image_file:
image_average_and_variation(image_files, a.average_image_file, a.variation_image_file)
if a.tracks_image_file or a.animate:
anim_pat = a.animation_image_file_pattern if a.animate else None
ti = tracks_image(image_files, a.averaging_window, a.threshold,
a.smoothing_steps, a.maxima_spacing,
a.color_mode, a.draw_lines, anim_pat, a.verbose)
if a.tracks_image_file:
ti.save(a.tracks_image_file)
# -----------------------------------------------------------------------------
# Color average image in regions where maximum variation from average is above a threshold.
# Can use "same", "solid", or "time" to color pixels with variation above threshold.
# If show_tracks is true then draw tracks between maxima in variation above threshold.
#
def tracks_image(image_files, averaging_window, threshold, smoothing_steps = 8, maxima_spacing = 10,
color_mode = 'same', draw_lines = True, animation_image_files = 'track-%04d.png',
verbose = 0):
c, f, vmax, maxima = variation(image_files, averaging_window, threshold,
smoothing_steps, maxima_spacing, verbose)
mask = (vmax >= threshold)
vari = composite_image(c, f, mask, color_mode)
if draw_lines:
plist = trace_paths(maxima, vari.size, verbose)
if verbose:
print ('Drawing tracks')
draw_tracks(vari, plist, maxima, len(image_files))
if animation_image_files:
animate_tracks(image_files, plist, animation_image_files, verbose)
return vari
# -----------------------------------------------------------------------------
# Compute maximum color variation among all images from average image
# at each pixel and record color of maximum variation. Variation is
# computed as sum of squares of RGB change. For each image compute positions
# of maximum variation after smoothing. Exclude positions that are close
# to maximum positions in same image.
#
def variation(image_files, averaging_window, threshold,
smoothing_steps, maxima_spacing, verbose = 0):
from PIL import Image
from numpy import zeros, float32, int32, empty, array
from numpy import subtract, multiply, sum, putmask, sqrt
# Initialize averaging window image data.
n = len(image_files)
ia = averaging_window/2 # Window images after current image
ib = (averaging_window-1) - ia # Window images before current image
ie = max(ia - n, 0) # Window images beyond end of image list
imr = [array(Image.open(p)) for p in image_files[:ia+1-ie]] # Read images
im = [None]*ib + imr + [None]*ie # Window images
im0 = imr[0]
asum = im0.astype(int32) # Sum of window images.
for a in imr[1:]:
asum += a
size = im0.shape[:2]
vmax = zeros(size, float32) # Maximum variation
imax = zeros(size, int32) # Image index of max variation
c = im0.copy() # Color for largest variation
da = empty(im0.shape, float32) # Temporary array
sda2 = empty(size, float32) # Temporary array
maxima = [] # Maximum points for each image.
for i,file in enumerate(image_files):
# Compute window image average
off_end = max(0,ib-i) + max(0,ia-(n-1-i))
ic = averaging_window - off_end
avef = asum / float(ic)
# Compute maximum variation per pixel.
a = im[ib]
subtract(a, avef, da)
sda = smooth_image(da, smoothing_steps)
multiply(sda, sda, sda)
sum(sda, axis = 2, out = sda2)
v = (sda2 > vmax)
putmask(imax, v, i)
for k in (0,1,2):
putmask(c[:,:,k], v, a[:,:,k])
putmask(vmax, v, sda2)
# Compute maxima of image variation.
mp = maximum_positions(sda2, threshold*threshold, maxima_spacing)
if verbose >= 1:
print 'Image %d has %d maxima' % (i, len(mp))
maxima.append(mp)
# Read next image and update image sum.
il = i+ia+1
if il < n:
l = array(Image.open(image_files[il]))
asum += l
else:
l = None
if not im[0] is None:
asum -= im[0]
im = im[1:] + [l]
vmax = sqrt(vmax)
return c, imax, vmax, maxima
# -----------------------------------------------------------------------------
# Color masked image region either a constant color, or color
# coded by frame number f, or colored from maximum variation image c.
#
def composite_image(c, f, mask, color_mode):
if color_mode in ('time', 'solid'):
if color_mode == 'time':
fmin, fmax = f.min(), f.max()
colors = ((1,0,0), (1,0.5,0), (1,1,0), (0,1,0), (0,1,1), (0,0,1))
cmap = color_map(colors, fmax-fmin+1, c.dtype, offset = fmin)
vc = cmap[f]
elif color_mode == 'solid':
from numpy import empty
vc = empty(c.shape, c.dtype)
vc[:,:] = (255,0,0) # red
var = c.copy()
from numpy import putmask
for k in range(c.shape[2]):
putmask(var[:,:,k], mask, vc[:,:,k])
elif color_mode == 'same':
var = c
else:
raise ValueError('Unknown color mode "%s"' % color_mode)
from PIL import Image
vari = Image.fromarray(var)
return vari
# -----------------------------------------------------------------------------
#
def color_map(colors, size, dtype, rgb_max = 255, offset = 0):
n = len(colors)
import numpy
cmap = numpy.zeros((size+offset, 3), dtype)
for i in range(size):
j = float((n-1)*i)/size
j0 = int(j)
f = j - j0
c = tuple(int(rgb_max * ((1-f)*c0 + f*c1))
for c0,c1 in zip(colors[j0], colors[j0+1]))
cmap[i,:] = c
return cmap
# -----------------------------------------------------------------------------
#
def image_average_and_variation(image_files, ave_file = None, var_file = None):
from PIL import Image
import numpy
asum = asum2 = None
for file in image_files:
pi = Image.open(file)
pa = numpy.array(pi)
paf = pa.astype(numpy.float32)
if asum is None:
asum = paf
asum2 = paf*paf
else:
asum += paf
asum2 += paf*paf
n = len(image_files)
asum /= n
aave = asum.astype(pa.dtype)
asum2 /= n
var = numpy.sqrt(asum2 - asum*asum)
avar = var.astype(pa.dtype)
if ave_file:
avei = Image.fromarray(aave)
avei.save(ave_file)
if var_file:
vari = Image.fromarray(avar)
vari.save(var_file)
return aave, avar
# -----------------------------------------------------------------------------
#
def maximum_positions(a, threshold, spacing = 0):
m = (a[:,:] >= threshold)
from numpy import logical_and, array
logical_and(m[:-1,:], a[:-1,:] > a[1:,:], m[:-1,:])
logical_and(m[1:,:], a[1:,:] > a[:-1,:], m[1:,:])
logical_and(m[:,:-1], a[:,:-1] > a[:,1:], m[:,:-1])
logical_and(m[:,1:], a[:,1:] > a[:,:-1], m[:,1:])
y,x = m.nonzero()
points = array((x,y)).transpose()
p = remove_nearby_points(points, spacing, a)
return p
# -----------------------------------------------------------------------------
#
def remove_nearby_points(points, dmin, a):
if dmin <= 0:
return points
remove = set()
ap = a[points[:,1],points[:,0]]
s = ap.argsort()
n = len(points)
for i in range(n):
if not s[i] in remove:
for j in range(i+1,n):
if not s[j] in remove:
step = points[s[j]]-points[s[i]]
if (step*step).sum() < dmin*dmin:
remove.add(s[j])
from numpy import array, int32
p = array(tuple(points[i] for i in range(n) if not i in remove), int32)
return p
# -----------------------------------------------------------------------------
#
def smooth_image(a, n = 1):
from numpy import float32, empty, multiply, add
dtype = a.dtype
f = a.astype(float32)
s = empty(a.shape, float32)
st, sb, sr, sl = s[1:,:], s[:-1,:], s[:,1:], s[:,:-1]
ft, fb, fr, fl = f[1:,:], f[:-1,:], f[:,1:], f[:,:-1]
for i in range(n):
multiply(f, 4.0, s)
add(st, fb, st)
add(sb, ft, sb)
add(sr, fl, sr)
add(sl, fr, sl)
multiply(s, 1.0/8, f)
return f.astype(dtype)
# -----------------------------------------------------------------------------
#
def trace_paths(maxima, bounds, verbose):
plist = []
used = set()
n = len(maxima)
from numpy import array
for f in range(n-1):
if verbose:
print ('Connecting paths in frame %d' % f)
pl = len(maxima[f])
for i in range(pl):
if not (f,i) in used:
x,y = maxima[f][i]
p = [(f,x,y,i)]
trace_path(p, maxima, bounds, used)
if len(p) >= 3:
plist.append(array(p)[:,:3])
else:
for fxyi in p:
used.remove((fxyi[0],fxyi[3]))
return plist
# -----------------------------------------------------------------------------
#
def trace_path(path, maxima, bounds, used):
for dir in (1,-1):
while (trace_step(path, maxima, bounds, used, dir) or
(len(path) >= 3 and
trace_step(path, maxima, bounds, used, 2*dir))):
continue
# -----------------------------------------------------------------------------
#
def trace_step(path, maxima, bounds, used, dir = 1):
f,x,y,i = path[-1] if dir > 0 else path[0]
used.add((f,i))
pf = maxima[f]
fn = f + dir
if fn < 0 or fn >= len(maxima):
return False
pfn = maxima[fn]
if len(path) == 1:
k = nearest(pf[i], pfn)
if (fn,k) in used:
return False
i2 = nearest(pfn[k], pf)
if i2 != i:
return False
else:
fp,xp,yp,ip = path[-2] if dir > 0 else path[1]
pfp = maxima[fp]
step = (pf[i]-pfp[ip])*(float(dir)/(f-fp))
xt,yt = pt = pf[i]+step
if xt < 0 or xt >= bounds[0] or yt < 0 or yt >= bounds[1]:
return False
k = nearest(pt, pfn)
if (fn,k) in used:
return False
e = pfn[k]-pt
if length(e) > 4*length(step):
return
if length(e) > 0.5*length(step):
i2 = nearest(pfn[k]-step, pf)
if i2 != i:
return False
txy = (fn, pfn[k][0], pfn[k][1], k)
if dir > 0:
path.append(txy)
else:
path.insert(0,txy)
return True
# -----------------------------------------------------------------------------
#
def length(v):
from math import sqrt
return sqrt((v*v).sum())
# -----------------------------------------------------------------------------
#
def nearest(p, plist):
d = plist - p
d *= d
i = d.sum(axis = 1).argmin()
return i
# -----------------------------------------------------------------------------
#
def draw_tracks(vari, plist, maxima, n):
from PIL import ImageDraw
d = ImageDraw.Draw(vari)
for p in plist:
color = '#00ff00' if accelerating(p) else '#ff0000'
xy = list(p[:,1:])
fs, fe = p[0][0], p[-1][0]
extend_path_ends(xy, fs > 0, fe < n-1, vari.size)
d.line(tuple(tuple(p) for p in xy), fill = color)
if fe == n-1:
xe,ye = xy[-1]
d.rectangle((xe-1,ye-1,xe+2,ye+2), fill = '#ffff00')
for points in maxima:
for x,y in points:
d.point((x,y), fill = '#ffff00')
# -----------------------------------------------------------------------------
#
def animate_tracks(image_files, plist, output = 'track-%04d.png', verbose = 0):
paths = [(p[0][0], p[-1][0], accelerating(p), p) for p in plist]
from numpy import concatenate
from PIL import Image, ImageDraw
from os.path import dirname, join
for i,f in enumerate(image_files):
if verbose:
print ('Drawing lines on image %d' % i)
im = Image.open(f)
dir = dirname(f)
d = None
for fs, fe, accel, p in paths:
if i > fs and i <= fe + (fe-fs):
color = '#00ff00' if accel else '#ff0000'
if d is None:
d = ImageDraw.Draw(im)
ds,de = (fs + (i-fe), fe) if i > fe else (0, i)
xy = [(x,y) for t,x,y in p if t >= ds and t <= de]
extend_path_ends(xy, i <= fe, i > fe, im.size)
d.line(tuple(tuple(p) for p in xy), fill = color)
if i == de:
xe,ye = xy[-1]
d.rectangle((xe-1,ye-1,xe+2,ye+2), fill = '#ffff00')
for x,y in xy:
d.point((x,y), fill = '#ffff00')
im.save(join(dir, output % i))
# -----------------------------------------------------------------------------
#
def accelerating(txy):
return segment_speed(txy, len(txy)-2) > segment_speed(txy, 0)
# -----------------------------------------------------------------------------
#
def segment_speed(txy, i):
step = txy[i+1] - txy[i]
from math import sqrt
v = sqrt((step[1:]*step[1:]).sum())/step[0]
return v
# -----------------------------------------------------------------------------
#
def extend_path_ends(xy, start, end, size):
if len(xy) < 3:
return
if start:
p = extend_path_end(xy[2], xy[1], xy[0], size)
if p:
xy.insert(0, p)
if end:
p = extend_path_end(xy[-3], xy[-2], xy[-1], size)
if p:
xy.append(p)
# -----------------------------------------------------------------------------
#
def extend_path_end(p0, p1, p2, size, pad = 1.2, maxe = 2.0):
x0, y0 = p0
x1, y1 = p1
x2, y2 = p2
dx, dy = x2-x1, y2-y1
from math import sqrt
d2 = sqrt(dx*dx + dy*dy)
d1 = sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0))
f = d2/d1 if d1 > 0 else 1
f *= pad
f = min(maxe,f)
x, y = x2+int(f*dx), y2+int(f*dy)
w,h = size
if x < 0 or x >= w or y < 0 or y >= h:
return (x,y)
return None
# -----------------------------------------------------------------------------
#
track()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment