added an option to allow jumping beads to considered as part of the granular media surface

-> grasslooper v1.1

fixes #1
This commit is contained in:
Guillaume Raffy 2023-12-07 17:51:36 +01:00
parent b7d4966cb9
commit 5cc55990f3
2 changed files with 34 additions and 13 deletions

View File

@ -9,7 +9,8 @@ import argparse
from improtools.imageprocessing import ImageProcessDebugger, IMovieProcessListener, NullMovieProcessListener, MovieProcessDebugger from improtools.imageprocessing import ImageProcessDebugger, IMovieProcessListener, NullMovieProcessListener, MovieProcessDebugger
from typing import Dict, Tuple from typing import Dict, Tuple
GRASSLOPER_VERSION = '1.0' GRASSLOPER_VERSION = '1.1'
def create_slope_image(image_width: int, image_height: int, p1_angle: float, p1_radius: float, bg_color: float = 0.0, fg_color: float = 1.0): def create_slope_image(image_width: int, image_height: int, p1_angle: float, p1_radius: float, bg_color: float = 0.0, fg_color: float = 1.0):
""" """
@ -91,8 +92,9 @@ def hdf5_to_trac_data(hdf5_file_path: Path):
class SlopeFinder(): class SlopeFinder():
def __init__(self, beads_radius: float): def __init__(self, beads_radius: float, exclude_jumping_beads: bool):
self.beads_radius = beads_radius self.beads_radius = beads_radius
self.exclude_jumping_beads = exclude_jumping_beads
def find_slope(self, trac_data: TracData, frame_index: int, mo_pro_listener: IMovieProcessListener = NullMovieProcessListener()): def find_slope(self, trac_data: TracData, frame_index: int, mo_pro_listener: IMovieProcessListener = NullMovieProcessListener()):
isbead_image = SlopeFinder.create_isbead_image(trac_data, frame_index, self.beads_radius) isbead_image = SlopeFinder.create_isbead_image(trac_data, frame_index, self.beads_radius)
@ -116,9 +118,13 @@ class SlopeFinder():
mo_pro_listener.onImage(labels_image, 'labels') mo_pro_listener.onImage(labels_image, 'labels')
is_non_jumping_bead_image = np.zeros(shape=labels_image.shape, dtype=np.uint8) is_non_jumping_bead_image = np.zeros(shape=labels_image.shape, dtype=np.uint8)
bead_area = math.pi * self.beads_radius * self.beads_radius bead_area = math.pi * self.beads_radius * self.beads_radius
min_area = bead_area * 1.1 if self.exclude_jumping_beads else 0.0
# print(f'self.exclude_jumping_beads = {self.exclude_jumping_beads}')
# print(f'min_area = {min_area}')
for label in range(1, num_labels): # ignore the first label, as it's the background for label in range(1, num_labels): # ignore the first label, as it's the background
area = stats[label][4] area = stats[label][4]
if area > bead_area * 1.1: # print(f'area = {area}')
if area > min_area:
is_non_jumping_bead_image[labels_image == label] = 255 is_non_jumping_bead_image[labels_image == label] = 255
mo_pro_listener.onImage(is_non_jumping_bead_image, 'non_jumping_beads') mo_pro_listener.onImage(is_non_jumping_bead_image, 'non_jumping_beads')
@ -127,7 +133,7 @@ class SlopeFinder():
surface_y.fill(is_non_jumping_bead_image.shape[0]) surface_y.fill(is_non_jumping_bead_image.shape[0])
contours, hierarchy = cv2.findContours(is_non_jumping_bead_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) contours, hierarchy = cv2.findContours(is_non_jumping_bead_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# print('num contours: ', len(contours)) # print('num contours: ', len(contours))
assert(len(contours) == 1) assert len(contours) >= 1
contour = contours[0] contour = contours[0]
for point in contour: for point in contour:
x, y = point[0] x, y = point[0]
@ -194,12 +200,24 @@ def save_results(results_file_path: Path, fitted_lines: Dict[int, scipy.stats._s
f.close() f.close()
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def main(): def main():
parser = argparse.ArgumentParser('grassloper', description='estimates the slope of granular surface', epilog='example: \n\tgrassloper --part-pos-file=./grassloper.git/samples/TracTrac/sample002_track.hdf5 --particle-radius=11.0 --debug-dir=\'./improdebug\' --results-file ./grassloper.git/samples/TracTrac/sample002_track_slopes.hdf5') parser = argparse.ArgumentParser('grassloper', description='estimates the slope of granular surface', epilog='example: \n\tgrassloper --part-pos-file=./grassloper.git/samples/TracTrac/sample002_track.hdf5 --particle-radius=11.0 --exclude-jumping-beads=true --debug-dir=\'./improdebug\' --results-file ./grassloper.git/samples/TracTrac/sample002_track_slopes.hdf5')
parser.add_argument('--part-pos-file', type=Path, default=None, required=True, help='the input file containing the particle positions') parser.add_argument('--part-pos-file', type=Path, default=None, required=True, help='the input file containing the particle positions')
parser.add_argument('--results-file', type=Path, default=None, required=True, help='the output file containing for each frame one line fitting the granular surface (hdf5 file format)') parser.add_argument('--results-file', type=Path, default=None, required=True, help='the output file containing for each frame one line fitting the granular surface (hdf5 file format)')
parser.add_argument('--part-pos-file-format', type=str, choices=['tractrac-hdf5'], default='tractrac-hdf5', help='the file format of the input file that contains particle positions') parser.add_argument('--part-pos-file-format', type=str, choices=['tractrac-hdf5'], default='tractrac-hdf5', help='the file format of the input file that contains particle positions')
parser.add_argument('--particle-radius', type=float, required=True, help='the radius of the particles in pixels (make sure that the radius is big enough to unsure that neighbouring particles touch each other)', default=11) parser.add_argument('--particle-radius', type=float, required=True, help='the radius of the particles in pixels (make sure that the radius is big enough to unsure that neighbouring particles touch each other)', default=11)
parser.add_argument('--exclude-jumping-beads', type=str2bool, default=True, required=True, help='if true, the isolated beads (supposedly because they are jumping) are not considered as being part of the granular media surface')
parser.add_argument('--debug-dir', type=Path, default=None, help='where to store image processing debug files') parser.add_argument('--debug-dir', type=Path, default=None, help='where to store image processing debug files')
args = parser.parse_args() args = parser.parse_args()
# python3 ./tractrac.git/Python/tractrac.py -f ./grassloper.git/samples/sample002.avi --output 1 -a --saveplot # python3 ./tractrac.git/Python/tractrac.py -f ./grassloper.git/samples/sample002.avi --output 1 -a --saveplot
@ -208,13 +226,13 @@ def main():
else: else:
mo_pro_listener = NullMovieProcessListener() mo_pro_listener = NullMovieProcessListener()
trac_data = hdf5_to_trac_data(args.part_pos_file) trac_data = hdf5_to_trac_data(args.part_pos_file)
slope_finder = SlopeFinder(beads_radius=args.particle_radius) slope_finder = SlopeFinder(beads_radius=args.particle_radius, exclude_jumping_beads=args.exclude_jumping_beads)
print(trac_data.num_frames) print(f'number of frames: {trac_data.num_frames}')
fitted_lines = {} fitted_lines = {}
for frame_index in range(1, trac_data.num_frames + 1): for frame_index in range(1, trac_data.num_frames + 1):
mo_pro_listener.onImageProcessingStart() mo_pro_listener.onImageProcessingStart()
line = slope_finder.find_slope(trac_data, frame_index=frame_index, mo_pro_listener=mo_pro_listener) line = slope_finder.find_slope(trac_data, frame_index=frame_index, mo_pro_listener=mo_pro_listener)
print('frame %d: %s' % (frame_index, str(line))) print(f'frame {frame_index}: {str(line)}')
fitted_lines[frame_index] = line fitted_lines[frame_index] = line
mo_pro_listener.onImageProcessingEnd() mo_pro_listener.onImageProcessingEnd()

View File

@ -1,8 +1,11 @@
import setuptools from setuptools import setup
setuptools.setup( setup(
name="grassloper", name="grassloper",
version="1.0", description="a tool to estimate the slope of a granular surface",
version="1.1",
author='Guillaume Raffy',
author_email='guillaume.raffy@univ-rennes.fr',
packages=['grassloper'], packages=['grassloper'],
install_requires=['improtools(>=1.0)', 'numpy', 'opencv-python', 'scipy', 'h5py'], install_requires=['improtools(>=1.0)', 'numpy', 'opencv-python', 'scipy', 'h5py'],
entry_points={ entry_points={