The dandelion’s pallid tube
Astonishes the grass,
And winter instantly becomes
An Infinite Alas —The tube uplifts a signal Bud
And then a shouting Flower, —
The Proclamation of the Suns
That septulture is o’er.– Emily Dickinson
The yellow flowers and the delicate and beautiful inflorescence of Dandelion catch the attention of both romantic and curious souls. The aerial consistency of the fine silk decorated seeds that glance to the sunlight as crystalline material became the favorite subject of the inspired photographers and the toy of amused children. Besides the grace of its forms, other interesting and the curious secret is hidden in its phloem fluids. In fact, if you cut one of the stems of the plant, a milky, sticky liquid will flow out of the wound resection. This latex is going to polymerize at 30-35 oC in a few minutes in a yellow-brown quite solid mass. Around the year 1982, I have annotated this observation but I could not find in my later notes further follow-ups study on the topics. It was a casual observation but I didn’t know at that time that this latex is indeed very useful. A variety of the Taraxacum (Taraxacum koksaghyz, Russian Dandelion) was used in Russian and American to produce a replacement of the natural rubber from Brazil during WWII that was in shortage because of the war. Nowadays, many studies are in progress to exploit the lattice of Taraxacum and also Taraxacum brevicorniculatumas a convenient replacement of the rubber plant lattice. A recent study has shown the presence of rubber particles in the lattice of these plants in 32% proportion composed prevalently by poly(cis-1,4-isoprene) at >95% of purity (www.biomedcentral.com/1471-2091/11/11). The brownish lattice condensate that, as I reported in my note, forms after exposing for several minutes the latex to the air, is caused by the presence of the polyphenol oxidase (PPO) enzyme that produces the fast coagulation of the latex by catalyzing the oxidation of polyphenols. Genetic engineer the plant, it is possible to reduce the amount of PPO in the latex making economically competitive the use of this resource for the production of latex.
My fascination for this common weed is not going to end with the useful properties of its latex. The dandelion plant cached again my attention very recently. This time my interest was directed to analyze the mechanics of the inflorescence: the formation of seeds and their dispersion. Indeed, the reproduction and proliferation processes are very complex and not completely understood even for the simpler weed model as the Arabidopsis thaliana. However, the observation of these phenomena from the amateur point of view is a delicious opportunity to learn an interesting aspect of physics photography, and computer science.
The Taraxacum officinale is a common species in all of Europe. The pant can have stems up to 40 cm high. The yellow flowers are called florets and are held by a cup-like bract called calyculus composed of 12 to 18 green lanceolate shaped segments with dark grey-purple apices acuminate. The number of florets can vary from 40 to more than 100. The fruits, called cypselae are oblanceolate in shape and 2 to 3 mm long with slender beaks. They are attached to white silky pappi, which form the ~6 mm wide parachutes. (Figure 2).
You can collect the flower by cutting them on the stem 15-20 cm below them and then inserting the cut stem in a test tube with water. Normally after a few hours, the sepals close the corona of florets. Later, the florets start to wither and dry out and after a couple of days the bundle of the parched petals falls down leaving a white wisp of thin white hair of the silky pappi enclosed in the sepal tuft (see Figure 3). This process can be filmed in time-lapse with little effort using the consumer technological resources available in each house: the mobile phone. In my case, I have used the camera of an iPhone 4s using the software for time-lapse Lapse-it. The same program is also available for Android devices. The setup for a mini photo studio is simple, you just need a LED light to keep uniformly illuminated the subject and white background behind the test tube with the flower.
Analysis of the Dandelion inflorescence opening
The analysis of a time-lapse video of a Dandelion inflorescence was a valid motivation to learn how to use the library Python-OpenCV for video analysis. I have recorded the opening of a Dandelion collected in the garden using an iPhone4 equipped with an app for time-lapse (Lapse-it). The video recording was the first test and it was made without much care about the lighting and background. It turns out to be a nice and interesting video tough with no uniform background and a variable illumination. Still, I decided to perform a video analysis of the inflorescence opening to explore the capability of the OpenCV image processing and eventually to better understand the dynamics of the process. The results of this analysis are reported in the video below. There are still bugs in the code that need to be removed. For example, the clock it does not match the reported time in seconds. Working is in progress tough very slowly as the dandelion’s movements !
Implementation of the Dandelion Video analysis using Python and OpenCV
OpenCV stands for Open Source Computer Vision Library. It is an open-source computer vision and machine learning software library. It is distributed under a BSD license that allows utilization and modifies the code. The library has a huge collection of optimized algorithms that can be used to identify objects, track moving objects, extract 3D models of objects, produce 3D point clouds from stereo cameras, stitch images together to produce a high-resolution image of an entire scene, and for many more. The library has a very easy-to-use Python interface and supports all commonly used OS. The best use with python is in combination with Jupyter.
The task required the following steps:
- Separate the subject from the messy background of my workshop.
- Adjust the video to align the stem of the dandelion with the vertical ass of the video.
- Add visual aids to follow the movement of the opening corolla.
- Extract features from the subject that allow to track its dynamics.
- Plot the numerical description of this feature.
- Add time indicators.
The code that I have implemented is in the appendix. I am sure that it can be written in a more professional way and suggestions to improve it are always welcome.
Acknowledgments
In this project, I have adapted many examples posted in the python OpenCV tutorials. However, I could not solve some tasks without the help of many bloggers that generously share their knowledge and experience on python and OpenCV. I would like to thank in particular the users of stackoverflow.com for sharing their very useful posts.
APPENDIX
In this appendix, you can find the python source code as implemented in the Jupyter notebook. The code is provided as some readers can find it useful for experimenting.
%matplotlib inline
import numpy as np
import cv2
from matplotlib import pyplot as plt
The following cell contains the definition of functions used to draw the elements on the mask overlaying the image.
- Draw_Clock: draw the clock on the right side of the screen
- Draw_Line: draw to dotted cross line on the dandelion
- Draw_half_circle_no_round: draw half a portion of a circle. It is used to create the mask for the analysis of the dandelion opening.
- Plot_Axes:
- Logistic: define a logistic function to overla with the calculated experimental data
# Colors (B, G, R)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
def Draw_Clock():
#
# Draw clock
#
cx=200 # x pos. of clock center
cy=200 # y pos. of clock center
crad=150 # clock radius
cv2.circle(dst, (cx,cy),crad, (255,255,255),1 )
cv2.circle(dst, (cx,cy),10, (255,255,255),2 )
for t in range(-180,180,6):
a=t*np.pi/180
x=int(crad*np.cos(a))+cx
y=int(crad*np.sin(a))+cy
if t%30==0:
x1=int((crad-15)*np.cos(a))+cx
y1=int((crad-15)*np.sin(a))+cy
tick=2
else:
x1=int((crad-10)*np.cos(a))+cx
y1=int((crad-10)*np.sin(a))+cy
tick=1
cv2.line(dst,(x,y),(x1,y1),(255,255,0),tick)
# Draw clock handle
x=int((crad-30)*np.cos(angr))+cx
y=int((crad-30)*np.sin(angr))+cy
cv2.line(dst,(cx,cy),(x,y),(255,255,0),3)
font = cv2.FONT_HERSHEY_SIMPLEX
text="Hours"
cv2.putText(dst, text, (25,50),font, 1,(255,255,255))
def drawline(img,pt1,pt2,color,thickness=1,style='dotted',gap=20):
dist =((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)**.5
pts= []
for i in np.arange(0,dist,gap):
r=i/dist
x=int((pt1[0]*(1-r)+pt2[0]*r)+.5)
y=int((pt1[1]*(1-r)+pt2[1]*r)+.5)
p = (x,y)
pts.append(p)
if style=='dotted':
for p in pts:
cv2.circle(img,p,thickness,color,-1)
else:
s=pts[0]
e=pts[0]
i=0
for p in pts:
s=e
e=p
if i%2==1:
cv2.line(img,s,e,color,thickness)
i+=1
def draw_half_circle_no_round(image):
#
# This function was taken from stackoverflow
#
height, width = image.shape[0:2]
# Ellipse parameters
radius = 350
center =(xc,370)
axes = (radius, radius)
angle = 0
startAngle = 180
endAngle = 450
# When thickness == -1 -> Fill shape
thickness = -1
# Draw black half circle
cv2.ellipse(image, center, axes, angle, startAngle, endAngle, BLACK, thickness)
axes = (radius - 10, radius - 10)
# Draw a bit smaller white half circle
cv2.ellipse(image, center, axes, angle, startAngle, endAngle, WHITE, thickness)
def plotaxes(image):
#
# Plot the axes of the graphs
#
cv2.rectangle(image,(20,20),(380,700),(255,255,255),2)
cv2.line(dst,(37,650),(360,650),(0,255,255),2)
cv2.line(dst,(37,650),(37,400),(0,255,255),2)
cv2.line(dst,(37,500),(360,500),(0,255,255),2)
cv2.rectangle(image,(37,400),(360,650),(0,255,255),1)
for t in range(0,330):
if t%10==0:
cv2.line(image,(37+t,650),(37+t,660),(0,255,255),1)
cv2.line(image,(37+t,500),(37+t,510),(0,255,255),1)
if t%20==0:
cv2.line(image,(37+t,650),(37+t,660),(0,255,255),2)
for t in range(0,260):
if t%10==0:
cv2.line(image,(27,650-t),(37,650-t),(0,255,255),1)
if t%20==0:
cv2.line(image,(27,650-t),(37,650-t),(0,255,255),2)
def calchROI(ref):
#
# extract a ROI from the image containing a portion of the flower stem
#
stem=trf[600:650,700:900]
# calculate the row-averaged histogram of the ROI along the its columns
profile = np.zeros(200)
maxy =-1
maxx= 0
inx=int(cc/dx)
for i in range(0,200):
# Average the profile for the green channel
profile[i]=sum(stem[0:50,i,2])/50.
# Calculate the maximum value of the profile for the green channel
if maxy<profile[i]:
maxx = i
maxy=profile[i]
#
# subtract the reference value to the calculate one
#
prof[inx]=int(maxx-32)/dy1
#
# Calculate the average of the ROI histogram of the visible image
#
avhist[inx]=(int((sum(hist_mask[200:255])/681923.)/dy))
#
# Use as vale from the first frame as reference value
#
if cc==0:
ref=avhist[inx]
#
# Subtract the reference value to the calculated average
#
avhist[inx]-=ref
def plotHist():
for x in range(1,inx):
cv2.circle(dst, (37+x,650-avhist[x]),2, (0,0,255),-1 )
cv2.circle(dst, (37+x,650-int(ydata[x])),1, (255,0,0),-1 )
cv2.circle(dst, (37+x,500-prof[x]),2, (255,255,0),-1 )
def protractor():
#
# Draw the protractor scale
#
for t in range(-180,180):
a=t*np.pi/180
x=int(300*np.cos(a))+xc
y=int(300*np.sin(a))+370
if t%10==0:
x1=int(335*np.cos(a))+xc
y1=int(335*np.sin(a))+370
tick=2
else:
x1=int(325*np.cos(a))+xc
y1=int(325*np.sin(a))+370
tick=1
cv2.line(dst,(x,y),(x1,y1),(0,0,0),tick)
cv2.circle(dst, (xc,370),300, (0,0,255),1 )
cv2.circle(dst, (xc,370),325, (255,255,255),50 )
drawline(dst,(xc-300,370),(xc+300,370),(0,0,255),2,'dotted',10)
drawline(dst,(xc,70),(xc,670),(255,0,0),2,'dotted',10)
def printsecs():
font = cv2.FONT_HERSHEY_SIMPLEX
text="Secs: "+str(cc*cns)
cv2.putText(dst, text, (400,50),font, 1,(255,255,255))
def func(x, a, b, c):
return a/(1+np.exp(-b*(x-c)))
Define the geometric parameters for the object in the mask. The variable is described in the code. In [3]:
#
# Variable assignments
#
nf=3446 # number of frames
shift=39/nf #shift
cc=0
count=0
ref=0
# Plot scaling
dx=nf/323.
dy=0.2/240
dy1=40./100.
fr=184./(2*nf)
ns=304*60
cns=ns/nf
#
# Circle mask
#
xc=440+350
yc=370
radius=350
#
# Initialize the two array to store the profiles
#
avhist = np.zeros(400,np.uint8)
prof=np.zeros(400,np.uint8)
#
# Calculate the logistic function to overlap with
# the profile of the color distribution
#
xdata = np.linspace(0, 400, 400)
ydata = func(xdata, 135.7, 0.037, 156.8)
rows=0
cols=0
inx=0
# Open the video file
cap = cv2.VideoCapture('IMG_2116.m4v')
ret, frame = cap.read()
#
# create the mask for the video
#
# Create a circular mask with center the subject.
# Extract from the frame using only the first 1200 pixel rows
part = frame[:,:1200]
rows,cols,cl = part.shape
img_mask = np.zeros((rows,cols,3), np.uint8)
cv2.circle(img_mask, (xc,yc),radius, (255,255,255),-1)
#
# Create mask for histogram
#
mask = np.zeros((rows,cols,3), np.uint8)
draw_half_circle_no_round(mask)
#
# Loop on the frames
#
while(cap.isOpened()):
ret, frame = cap.read()
if ret==True:
if cc%2 ==0:
# Reshape the frame
part = frame[:,:1200]
#
# Rotate the frame using an affine trasfromation to better align the
# step of the flower with the y axis
#
M = np.float32([[1,0,230],[0,1,0]])
trf = cv2.warpAffine(part,M,(cols,rows))
#
# Apply masks
#
dst=img_mask&trf
anaimg=mask&trf
hist_mask = cv2.calcHist([anaimg],[0],None,[256],[0,256])
#
# Calculate and plot ROI histograms
#
calchROI(ref)
plotHist()
#
# Draw the protractor scale
#
protractor()
#
# draw clock
#
ang=(fr*cc)-90
angr=ang*np.pi/180
drawclock()
#
# Write the lapsed seconds
#
printsecs()
#
# plot axes
#
plotaxes(dst)
cc+=1
cv2.imshow('frame',dst)
#
# Save the single frames
#
name = "FRAMES/frame%d.jpg"%count
count+=1
cv2.imwrite(name, dst)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()