Skip to content

Commit 14231f9

Browse files
authored
Merge pull request #12 from mvgorcum/pyinstall
fix a bug when not detecting filetype, replace old qtgui objects
2 parents 5b71b8a + 130bd8f commit 14231f9

File tree

6 files changed

+75
-30
lines changed

6 files changed

+75
-30
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ A GUI using pyqtgraph and pyqt5 is available, as well as a standalone script. Th
1919

2020
## Prerequisites
2121
If you don't use the precomiled releases nor `pip install` it you'll need:
22-
The script requires numpy, pandas, scipy, pyqt5, opencv-python, scikit-image, imageio, shapely, pyqtgraph >=0.11.0, toml, h5py, json, and appdirs.
22+
The script requires numpy, pandas, scipy, pyqt5, opencv-python, fast-histogram, imageio, shapely, pyqtgraph >=0.11.0, openpyxl, toml, h5py, json, and appdirs.
2323

2424
## Install and running
2525
To install the program run `pip install .` in the sessile.drop.analysis folder.

README.md.backup

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Python sessile drop analysis
2+
[![Element Android Matrix room #Sessile.Drop.Analysis:matrix.vgorcum.com](https://img.shields.io/matrix/Sessile.Drop.Analysis:matrix.vgorcum.com.svg?label=%23Sessile.Drop.Analysis:matrix.vgorcum.com&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#Sessile.Drop.Analysis:matrix.vgorcum.com)
3+
4+
Made by Mathijs van Gorcum during his PhD at the Physics of Fluids group of the University of Twente.
5+
6+
Python script to analyse sessile drops by measuring contact angle, drop volume and contact line speed.
7+
This script analyses an image sequence (in the form of an avi, a tiffstack or a folder containing the images) and finds the contact angle, drop volume and the contact line speed.
8+
The script assumes a black and white image of the drop on the surface, where the drop is black, and the background is white.
9+
The script will ask for the file (or a file, in the case of a folder containing images), a crop (to increase calculation speed, and cut off any irrelevant parts) and a baseline.
10+
We use a subpixel edge detection, either fast, with a linear interpolation between two pixels around the edge, or slow by fitting an error function around the edge.
11+
To find the contact line position and the contact angle the detected edge is fitted with a 3rd order polynomial fit, and the slope of the baseline is also used to calculate the contact angles.
12+
Note that the drop volume assume cylindrical symmetry and if there is a needle present, the volume of the needle is added.
13+
14+
A GUI using pyqtgraph and pyqt5 is available, as well as a standalone script. The old standalone script is probably beneficial when using an IDE like spyder and you want to be able to customize the inner workings of the script.
15+
16+
## Screenshot
17+
18+
![](Screenshot.png)
19+
20+
## Prerequisites
21+
If you don't use the precomiled releases nor `pip install` it you'll need:
22+
The script requires numpy, pandas, scipy, pyqt5, opencv-python, fast_histogram, imageio, shapely, pyqtgraph >=0.11.0, openpyxl, toml, h5py, json, and appdirs.
23+
24+
## Install and running
25+
To install the program run `pip install .` in the sessile.drop.analysis folder.
26+
To run the program after installing simply run `drop_analysis` in the terminal.
27+
28+
To compile a binary, pyinstaller is required, use the following:
29+
`pyinstaller --name drop_analysis --additional-hooks-dir hooks --exclude-module matplotlib --icon drop_analysis/data/icon.ico --onefile pyinst_entrypoint.py`
30+
31+
## Some details
32+
* The code is written for Python 3.8
33+
* The edge detection uses only a horizontal subpixel correction, and when fitting the errorfunction, 40 pixels left and right of the edge are used.
34+
* To find the contact angle and contact point a polyfit is used, but the fit is made flipping the x and y coordinates, because polyfits don't perform well for vertical lines (ie at contact angles of 90 degrees).
35+
* In the non-gui script, the variable k is used to set the amount of pixels used in the polyfit, by default set at 70.
36+
* The thresh variable is the threshold level, for the fast edge detection the value is used explicitly while for the error function fitting the value is only used to find an approximate edge, to fit the errorfunction around.
37+
* The contact line speed is calculated using a linear regression of the contact line position.
38+
* The non-gui script calculates the speed in pixels/frame, and the volume in pixels^3, so be sure to convert it.
39+
40+
## Contributing
41+
Feel free to send pull requests, critique my awful code or point out any issues.
42+
43+
## License
44+
This project is licensed under the GPLv3 license - see the [LICENSE](https://github.com/mvgorcum/Sessile.drop.analysis/blob/master/LICENSE) file for details

drop_analysis/FrameSupply.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import imageio
88
import numpy as np
99
from time import sleep
10-
from PyQt5 import QtGui
10+
from PyQt5 import QtGui, QtWidgets
1111
import datetime
1212
import h5py
1313
import json
@@ -256,9 +256,9 @@ def _aquire(self):
256256
firstrec=True
257257

258258
if not self.cap.isOpened():
259-
errorpopup=QtGui.QMessageBox()
259+
errorpopup=QtWidgets.QMessageBox()
260260
errorpopup.setText('Error opening video stream')
261-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
261+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
262262
errorpopup.exec_()
263263
self.cap.release()
264264
self.is_running = False

drop_analysis/gui.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ def openCall(self):
119119
mimetype=mimetypes.guess_type(VideoFile)[0]
120120
self.MeasurementResult=pd.DataFrame(columns=['thetaleft', 'thetaright', 'contactpointleft','contactpointright','volume','time'])
121121
self.PlotItem.clear()
122-
if any(mimetype in key for key in filetypemap):
122+
if mimetype is None or not any(mimetype in key for key in filetypemap):
123+
errorpopup=QtWidgets.QMessageBox()
124+
errorpopup.setText('Unkown filetype')
125+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
126+
errorpopup.exec_()
127+
else:
123128
self.FrameSource=filetypemap[mimetype](VideoFile)
124129
self.FrameSource.start()
125130
FrameWidth,FrameHeight=self.FrameSource.getframesize()
@@ -128,11 +133,6 @@ def openCall(self):
128133
self.BaseLine.setPos([FrameWidth*.2,FrameHeight*.7])
129134
firstframe,_=self.FrameSource.getfirstframe()
130135
self.VideoItem.setImage(firstframe,autoRange=True)
131-
else:
132-
errorpopup=QtGui.QMessageBox()
133-
errorpopup.setText('Unkown filetype')
134-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
135-
errorpopup.exec_()
136136

137137

138138
def StartStop(self):
@@ -256,9 +256,9 @@ def SaveResult(self):
256256

257257
self.MaybeSave=False
258258
else:
259-
errorpopup=QtGui.QMessageBox()
259+
errorpopup=QtWidgets.QMessageBox()
260260
errorpopup.setText('Nothing to save')
261-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
261+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
262262
errorpopup.exec_()
263263

264264
def configSettings(self):
@@ -274,14 +274,14 @@ def SaveVideo(self):
274274
SaveFileName=SaveFileName+'.h5'
275275
Path(self.FrameSource.bufferpath).rename(SaveFileName)
276276
else:
277-
errorpopup=QtGui.QMessageBox()
277+
errorpopup=QtWidgets.QMessageBox()
278278
errorpopup.setText('No video has been recorded')
279-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
279+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
280280
errorpopup.exec_()
281281
else:
282-
errorpopup=QtGui.QMessageBox()
282+
errorpopup=QtWidgets.QMessageBox()
283283
errorpopup.setText("Can't save video while recording is running")
284-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
284+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
285285
errorpopup.exec_()
286286

287287
def ExportVideo(self):
@@ -290,9 +290,9 @@ def ExportVideo(self):
290290
exportsource=FrameSupply.Hdf5Reader(self.FrameSource.bufferpath)
291291
exportsource.start()
292292
elif self.CameraToggleButton.isChecked() or not Path(self.FrameSource.bufferpath).exists():
293-
errorpopup=QtGui.QMessageBox()
293+
errorpopup=QtWidgets.QMessageBox()
294294
errorpopup.setText('No video has been recorded')
295-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
295+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
296296
errorpopup.exec_()
297297
else:
298298
exportsource=self.FrameSource
@@ -320,8 +320,8 @@ def ExportVideo(self):
320320
progress.setValue(totalframes)
321321
writer.release()
322322
else:
323-
errorpopup=QtGui.QMessageBox()
323+
errorpopup=QtWidgets.QMessageBox()
324324
errorpopup.setText("Can't export video while recording is running")
325-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
325+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
326326
errorpopup.exec_()
327327

drop_analysis/settings.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ def _tryframerate(self):
5858
try:
5959
cap = cv2.VideoCapture(0)
6060
except:
61-
errorpopup=QtGui.QMessageBox()
61+
errorpopup=QtWidgets.QMessageBox()
6262
errorpopup.setText('Error opening camera, is the Live Camera disabled?')
63-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
63+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
6464
errorpopup.exec_()
6565
return {}
6666
if cap.isOpened():
@@ -70,9 +70,9 @@ def _tryframerate(self):
7070
self.config['opencvcamera']['framerate']=actualframerate
7171

7272
else:
73-
errorpopup=QtGui.QMessageBox()
73+
errorpopup=QtWidgets.QMessageBox()
7474
errorpopup.setText('Error opening camera, is the Live camera disabled?')
75-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
75+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
7676
errorpopup.exec_()
7777

7878
def _saveconfig(self):
@@ -89,7 +89,7 @@ def _saveconfig(self):
8989
toml.dump(self.config,configfile)
9090

9191
def _changebufferpath(self):
92-
SaveFileName, _ =QtGui.QFileDialog.getSaveFileName(self,'Buffer file location', '', "Buffer file (*.h5)")
92+
SaveFileName, _ =QtWidgets.QFileDialog.getSaveFileName(self,'Buffer file location', '', "Buffer file (*.h5)")
9393
if SaveFileName=='':
9494
return
9595
if Path(SaveFileName).suffix =='':
@@ -112,9 +112,9 @@ def detect_resolutions():
112112
try:
113113
cap = cv2.VideoCapture(0)
114114
except:
115-
errorpopup=QtGui.QMessageBox()
115+
errorpopup=QtWidgets.QMessageBox()
116116
errorpopup.setText('Error opening camera, is the Live Camera disabled?')
117-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
117+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
118118
errorpopup.exec_()
119119
return {}
120120
if cap.isOpened():
@@ -125,8 +125,8 @@ def detect_resolutions():
125125
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
126126
resolutions[str(width)+"x"+str(height)] = [width,height]
127127
else:
128-
errorpopup=QtGui.QMessageBox()
128+
errorpopup=QtWidgets.QMessageBox()
129129
errorpopup.setText('Error opening camera, is the Live camera disabled?')
130-
errorpopup.setStandardButtons(QtGui.QMessageBox.Ok)
130+
errorpopup.setStandardButtons(QtWidgets.QMessageBox.Ok)
131131
errorpopup.exec_()
132132
return resolutions

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ install_requires =
2020
scipy
2121
pyqt5
2222
opencv-python-headless
23-
scikit-image
23+
openpyxl
24+
fast-histogram
2425
imageio
2526
shapely
2627
pyqtgraph >=0.11.0

0 commit comments

Comments
 (0)