From 5cc55990f339bd6527c4372831ebc7c043ca2ac3 Mon Sep 17 00:00:00 2001 From: Guillaume Raffy Date: Thu, 7 Dec 2023 17:51:36 +0100 Subject: [PATCH] added an option to allow jumping beads to considered as part of the granular media surface -> grasslooper v1.1 fixes #1 --- grassloper/main.py | 38 ++++++++++++++++++++++++++++---------- setup.py | 9 ++++++--- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/grassloper/main.py b/grassloper/main.py index 3d1f689..cd0791f 100755 --- a/grassloper/main.py +++ b/grassloper/main.py @@ -9,7 +9,8 @@ import argparse from improtools.imageprocessing import ImageProcessDebugger, IMovieProcessListener, NullMovieProcessListener, MovieProcessDebugger 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): """ @@ -91,8 +92,9 @@ def hdf5_to_trac_data(hdf5_file_path: Path): class SlopeFinder(): - def __init__(self, beads_radius: float): + def __init__(self, beads_radius: float, exclude_jumping_beads: bool): 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()): 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') is_non_jumping_bead_image = np.zeros(shape=labels_image.shape, dtype=np.uint8) 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 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 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]) contours, hierarchy = cv2.findContours(is_non_jumping_bead_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # print('num contours: ', len(contours)) - assert(len(contours) == 1) + assert len(contours) >= 1 contour = contours[0] for point in contour: x, y = point[0] @@ -180,8 +186,8 @@ def save_results(results_file_path: Path, fitted_lines: Dict[int, scipy.stats._s stderr_array[frame_index] = fitted_line.stderr intercept_stderr_array[frame_index] = fitted_line.intercept_stderr - f = h5py.File(results_file_path,'w') - f.attrs['version']='HDF5 file made with grassloper v %s' % GRASSLOPER_VERSION + f = h5py.File(results_file_path, 'w') + f.attrs['version'] = 'HDF5 file made with grassloper v %s' % GRASSLOPER_VERSION f.attrs['date'] = time.strftime("%d/%m/%Y") f.attrs['input_file'] = str(input_file_path) f.attrs['num_frames'] = num_frames @@ -194,12 +200,24 @@ def save_results(results_file_path: Path, fitted_lines: Dict[int, scipy.stats._s 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(): - 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('--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('--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') args = parser.parse_args() # python3 ./tractrac.git/Python/tractrac.py -f ./grassloper.git/samples/sample002.avi --output 1 -a --saveplot @@ -208,13 +226,13 @@ def main(): else: mo_pro_listener = NullMovieProcessListener() trac_data = hdf5_to_trac_data(args.part_pos_file) - slope_finder = SlopeFinder(beads_radius=args.particle_radius) - print(trac_data.num_frames) + slope_finder = SlopeFinder(beads_radius=args.particle_radius, exclude_jumping_beads=args.exclude_jumping_beads) + print(f'number of frames: {trac_data.num_frames}') fitted_lines = {} for frame_index in range(1, trac_data.num_frames + 1): mo_pro_listener.onImageProcessingStart() 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 mo_pro_listener.onImageProcessingEnd() diff --git a/setup.py b/setup.py index 09bb119..fa5c7c2 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,11 @@ -import setuptools +from setuptools import setup -setuptools.setup( +setup( 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'], install_requires=['improtools(>=1.0)', 'numpy', 'opencv-python', 'scipy', 'h5py'], entry_points={