Skip to content

Commit 9a164ba

Browse files
authored
Working on #2
1 parent 381f9b6 commit 9a164ba

1 file changed

Lines changed: 51 additions & 34 deletions

File tree

DigiPyRo.py

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# DigiPyRo is a program with two main functions:
22
# 1. Digital rotation of movies.
33
# 2. Single-particle tracking.
4+
# All of its functionalities can be accessed through the GUI window which appears when DigiPyRo is run.
45
# See the README and instructables for further documentation, installation instructions and examples.
56

67

@@ -19,6 +20,11 @@
1920
### Helper Functions ###
2021
########################
2122

23+
## Helper Functions: Section 1 -- User-Interaction Functions
24+
## The majority of functions in this section relate to user-identification of the region of interest (ROI) which will be digitally rotated,
25+
## or the intialization of single-particle tracking
26+
27+
# Allows user to manually identify center of rotation
2228
def centerClick(event, x, y, flags, param):
2329
global center, frame
2430
clone = frame.copy()
@@ -34,6 +40,7 @@ def centerImg(img, x_c, y_c): # shifts image so that it is centered at (x_c, y_c
3440
shiftMatrix = np.float32([[1, 0, dx], [0, 1, dy]])
3541
return cv2.warpAffine(img, shiftMatrix, (width, height))
3642

43+
# User drags mouse and releases along a diameter of the particle to set an approximate size and location of particle for DPR to search for
3744
def locate(event, x, y, flags, param):
3845
global frame, particleStart, particleEnd, particleCenter, particleRadius
3946
clone = frame.copy()
@@ -48,6 +55,7 @@ def locate(event, x, y, flags, param):
4855
cv2.imshow('Locate Ball', frame)
4956
frame = clone.copy() # resets to original image
5057

58+
# User clicks points along the circumference of a circular ROI. This function records the points and calculates the best-fit circle through the points.
5159
def circumferencePoints(event, x, y, flags, param):
5260
global npts, center, frame, xpoints, ypoints, r, poly1, poly2
5361
if event == cv2.EVENT_LBUTTONDOWN:
@@ -75,7 +83,8 @@ def circumferencePoints(event, x, y, flags, param):
7583
cv2.circle(frame, center, r, (0,255,0), 1)
7684
cv2.imshow('CenterClick', frame)
7785
frame = clone.copy()
78-
86+
87+
# The same as "circumferencePoints", except this calculates a polygon ROI. The center is calculated as the "center of mass" of the polygon
7988
def nGon(event, x, y, flags, param):
8089
global npts, center, frame, xpoints, ypoints, r, poly1, poly2
8190
if event == cv2.EVENT_LBUTTONDOWN:
@@ -101,7 +110,7 @@ def nGon(event, x, y, flags, param):
101110
cv2.imshow('CenterClick', frame)
102111
frame = clone.copy()
103112

104-
113+
# Removes the most recently clicked point in the array of circle/polygon circumference points.
105114
def removePoint(orig):
106115
global npts, center, frame, xpoints, ypoints, r, poly1, poly2, custMask
107116
if npts == 0:
@@ -147,6 +156,7 @@ def removePoint(orig):
147156
cv2.circle(frame, center, r, (0,255,0), 1)
148157
cv2.imshow('CenterClick', frame)
149158

159+
# Calculates the center and radius of the best-fit circle through an array of points (by least-squares method)
150160
def calc_center(xp, yp):
151161
n = len(xp)
152162
circleMatrix = np.matrix([[np.sum(xp**2), np.sum(xp*yp), np.sum(xp)], [np.sum(xp*yp), np.sum(yp**2), np.sum(yp)], [np.sum(xp), np.sum(yp), n]])
@@ -161,6 +171,7 @@ def calc_center(xp, yp):
161171
diam = d**(0.5)
162172
return np.array([int(xc), int(yc), int(diam/2)])
163173

174+
# Adds diagnostic information, including time and physical/digital rotations to each frame of the movie
164175
def annotateImg(img, i):
165176
font = cv2.FONT_HERSHEY_TRIPLEX
166177

@@ -170,10 +181,6 @@ def annotateImg(img, i):
170181

171182
img[25:25+spinlab.shape[0], (width-25)-spinlab.shape[1]:width-25] = spinlab
172183

173-
#perStamp = 'Period (T): ' + str(round(per,1)) + ' s'
174-
#perLoc = (25, height-75)
175-
#cv2.putText(img, perStamp, perLoc, font, 1, (255, 255, 255), 1)
176-
#timestamp = 'Time: ' + str(round(((i/fps)/per),1)) + ' T'
177184
timestamp = 'Time: ' + str(round((i/fps),1)) + ' s'
178185
tLoc = (width - 225, height-25)
179186
cv2.putText(img, timestamp, tLoc, font, 1, (255, 255, 255), 1)
@@ -198,9 +205,10 @@ def annotateImg(img, i):
198205
cv2.putText(img, drpm, dLoc, font, 1, (255, 255, 255), 1)
199206
cv2.putText(img, crpm, cLoc, font, 1, (255, 255, 255), 1)
200207

208+
# Displays instructions on the screen for identifying the circle/polygon of interest
201209
def instructsCenter(img):
202210
font = cv2.FONT_HERSHEY_PLAIN
203-
line1 = 'Click on 3 or more points along the border of the circle'
211+
line1 = 'Click on 3 or more points along the border of the circle or polygon'
204212
line1Loc = (25, 50)
205213
line2 = 'around which the movie will be rotated.'
206214
line2Loc = (25, 75)
@@ -214,6 +222,7 @@ def instructsCenter(img):
214222
cv2.putText(img, line3, line3Loc, font, 1, (255, 255, 255), 1)
215223
cv2.putText(img, line4, line4Loc, font, 1, (255, 255, 255), 1)
216224

225+
# Displays instructions for drawing a circle around the ball.
217226
def instructsBall(img):
218227
font = cv2.FONT_HERSHEY_PLAIN
219228
line1 = 'Click and drag to create a circle around the ball.'
@@ -229,7 +238,28 @@ def instructsBall(img):
229238
cv2.putText(img, line2, line2Loc, font, 1, (255, 255, 255), 1)
230239
cv2.putText(img, line3, line3Loc, font, 1, (255, 255, 255), 1)
231240
cv2.putText(img, line4, line4Loc, font, 1, (255, 255, 255), 1)
241+
242+
## Helper Functions: Section 2 -- Mathematical Utility Functions
243+
244+
# 2nd-order central difference method for calculating the derivative of unevenly spaced data
245+
def calcDeriv(f, t):
246+
df = np.empty(len(f))
247+
df[0] = (f[1] - f[0]) / (t[1] - t[0])
248+
df[len(f)-1] = (f[len(f)-1] - f[len(f)-2]) / (t[len(f)-1] - t[len(f)-2])
249+
df[1:len(f)-1] = f[0:len(f)-2]*((t[1:len(f)-1] - t[2:len(f)]) / ((t[0:len(f)-2] - t[1:len(f)-1])*(t[0:len(f)-2] - t[2:len(f)]))) + f[1:len(f)-1]*(((2*t[1:len(f)-1]) - t[0:len(f)-2] - t[2:len(f)]) / ((t[1:len(f)-1] - t[0:len(f)-2])*(t[1:len(f)-1] - t[2:len(f)]))) + f[2:len(f)]*((t[1:len(f)-1] - t[0:len(f)-2]) / ((t[2:len(f)] - t[0:len(f)-2])*(t[2:len(f)] - t[1:len(f)-1])))
250+
return df
251+
252+
# Calculates a polynomial fit of degree "deg" though an array of data "y" with corresponding x values "x"
253+
def splineFit(x, y, deg):
254+
fit = np.polyfit(x, y, deg)
255+
yfit = np.zeros(len(y))
256+
for i in range(deg+1):
257+
yfit += fit[i]*(x**(deg-i))
258+
return yfit
232259

260+
261+
# The following functions assist in estimating the coefficient of friction of the user's table by fitting their data
262+
# to a damped harmonic oscillator. This functionality is not implemented in the current release of DigiPyRo
233263
def errFuncPolar(params, data):
234264
modelR = np.abs(params[0]*np.exp(-data[0]*params[3]*params[1])*np.cos((params[3]*data[0]*((1-(params[1]**2))**(0.5))) - params[2]))
235265
modelTheta = createModelTheta(data[0], params, data[2][0])
@@ -269,43 +299,26 @@ def createModelTheta(t, bestfit, thetai):
269299

270300
return theta
271301

272-
def calcDeriv2(f, t):
273-
return np.gradient(f) / np.gradient(t)
274-
275-
def calcDeriv(f, t):
276-
df = np.empty(len(f))
277-
df[0] = (f[1] - f[0]) / (t[1] - t[0])
278-
df[len(f)-1] = (f[len(f)-1] - f[len(f)-2]) / (t[len(f)-1] - t[len(f)-2])
279-
df[1:len(f)-1] = f[0:len(f)-2]*((t[1:len(f)-1] - t[2:len(f)]) / ((t[0:len(f)-2] - t[1:len(f)-1])*(t[0:len(f)-2] - t[2:len(f)]))) + f[1:len(f)-1]*(((2*t[1:len(f)-1]) - t[0:len(f)-2] - t[2:len(f)]) / ((t[1:len(f)-1] - t[0:len(f)-2])*(t[1:len(f)-1] - t[2:len(f)]))) + f[2:len(f)]*((t[1:len(f)-1] - t[0:len(f)-2]) / ((t[2:len(f)] - t[0:len(f)-2])*(t[2:len(f)] - t[1:len(f)-1])))
280-
return df
281-
282-
def splineFit(x, y, deg):
283-
fit = np.polyfit(x, y, deg)
284-
yfit = np.zeros(len(y))
285-
for i in range(deg+1):
286-
yfit += fit[i]*(x**(deg-i))
287-
return yfit
288-
289302
#####################
290303
### Main function ###
291304
#####################
292305

293306
def start():
294-
vid = cv2.VideoCapture(filenameVar.get())
307+
vid = cv2.VideoCapture(filenameVar.get()) # input video
295308

296-
global width, height, numFrames, fps, fourcc, video_writer, spinlab, npts
297-
npts = 0
298-
spinlab = cv2.imread('SpinLabUCLA_BW_strokes.png')
299-
width = int(vid.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
300-
height = int(vid.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT))
309+
global width, height, numFrames, fps, fourcc, video_writer, spinlab, npts # declare these variables as global so they can be used by helper functions without being explicitly passed as arguments
310+
npts = 0 # number of user-clicked points along circumference of circle/polygon
311+
spinlab = cv2.imread('SpinLabUCLA_BW_strokes.png') # spinlab logo to display in upper right corner of output video
312+
width = int(vid.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH))
313+
height = int(vid.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) # read the width and height of input video. output video will have matching dimensions
301314
fps = fpsVar.get()
302315
fileName = savefileVar.get()
303-
fourcc = cv2.cv.CV_FOURCC('m','p','4','v')
304-
video_writer = cv2.VideoWriter(fileName+'.avi', fourcc, fps, (width, height))
316+
fourcc = cv2.cv.CV_FOURCC('m','p','4','v') # codec for output video
317+
video_writer = cv2.VideoWriter(fileName+'.avi', fourcc, fps, (width, height)) # VideoWriter object for editing and saving the output video
305318

306-
spinlab = cv2.resize(spinlab,(int(0.2*width),int((0.2*height)/3)), interpolation = cv2.INTER_CUBIC)
319+
spinlab = cv2.resize(spinlab,(int(0.2*width),int((0.2*height)/3)), interpolation = cv2.INTER_CUBIC) # resize spinlab logo based on input video dimensions
307320

308-
global naturalRPM, physicalRPM, digiRPM, camRPM, dtheta, per, custMask
321+
global naturalRPM, physicalRPM, digiRPM, camRPM, dtheta, per, custMask # declare these variables as global so they can be used by helper functions without being explicitly passed as arguments
309322
naturalRPM = tableRPMVar.get()
310323
naturalOmega = (naturalRPM * 2*np.pi)/60
311324
physicalRPM = physRPMVar.get()
@@ -543,6 +556,10 @@ def start():
543556

544557
video_writer.release()
545558

559+
#######################
560+
### Create GUI menu ###
561+
#######################
562+
546563
root = Tk()
547564
root.title('DigiPyRo')
548565
startButton = Button(root, text = "Start!", command = start)

0 commit comments

Comments
 (0)