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:
		
							parent
							
								
									b7d4966cb9
								
							
						
					
					
						commit
						5cc55990f3
					
				|  | @ -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() | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										9
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										9
									
								
								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={ | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue