88import numpy as np
99from plantcv .plantcv import params , outputs , fatal_error , deprecation_warning
1010from plantcv .plantcv ._debug import _debug
11- from plantcv .plantcv ._helpers import _rgb2gray
11+ from plantcv .plantcv ._helpers import _rgb2gray , _cv2_findcontours , _object_composition
1212
1313
1414def _is_square (contour , min_size ):
@@ -83,8 +83,8 @@ def _draw_color_chips(rgb_img, new_centers, radius):
8383 return labeled_mask , debug_img
8484
8585
86- def detect_color_card (rgb_img , label = None , ** kwargs ):
87- """Automatically detect a color card.
86+ def _color_card_detection (rgb_img , ** kwargs ):
87+ """Algorithm to automatically detect a color card.
8888
8989 Parameters
9090 ----------
@@ -103,16 +103,9 @@ def detect_color_card(rgb_img, label=None, **kwargs):
103103
104104 Returns
105105 -------
106- numpy.ndarray
107- Labeled mask of chips.
106+ list
107+ Labeled mask of chips, debug img, detected chip areas, chip heights, chip widths, bounding box mask
108108 """
109- # Set lable to params.sample_label if None
110- if label is None :
111- label = params .sample_label
112- deprecation_warning (
113- "The 'label' parameter is no longer utilized, since color chip size is now metadata. "
114- "It will be removed in PlantCV v5.0."
115- )
116109 # Get keyword arguments and set defaults if not set
117110 min_size = kwargs .get ("min_size" , 1000 ) # Minimum size for _is_square chip filtering
118111 radius = kwargs .get ("radius" , 20 ) # Radius of circles to draw on the color chips
@@ -149,14 +142,15 @@ def detect_color_card(rgb_img, label=None, **kwargs):
149142 # Draw filtered contours on debug img
150143 debug_img = np .copy (rgb_img )
151144 cv2 .drawContours (debug_img , filtered_contours , - 1 , color = (255 , 50 , 250 ), thickness = params .line_thickness )
145+ # Find the bounding box of the detected chips
146+ x , y , w , h = cv2 .boundingRect (np .vstack (filtered_contours ))
147+
148+ # Draw the bound box rectangle
149+ boundind_mask = cv2 .rectangle (np .zeros (rgb_img .shape [0 :2 ]), (x , y ), (x + w , y + h ), (255 ), - 1 ).astype (np .uint8 )
150+
152151 # Initialize chip shape lists
153152 marea , mwidth , mheight = _get_contour_sizes (filtered_contours )
154153
155- # Create dataframe for easy summary stats
156- chip_size = np .median (marea )
157- chip_height = np .median (mheight )
158- chip_width = np .median (mwidth )
159-
160154 # Concatenate all contours into one array and find the minimum area rectangle
161155 rect = np .concatenate ([[np .array (cv2 .minAreaRect (i )[0 ]).astype (int )] for i in filtered_contours ])
162156 rect = cv2 .minAreaRect (rect )
@@ -181,6 +175,84 @@ def detect_color_card(rgb_img, label=None, **kwargs):
181175 # Create labeled mask and debug image of color chips
182176 labeled_mask , debug_img = _draw_color_chips (debug_img , new_centers , radius )
183177
178+ return labeled_mask , debug_img , marea , mheight , mwidth , boundind_mask
179+
180+
181+ def mask_color_card (rgb_img , ** kwargs ):
182+ """Automatically detect a color card and create bounding box mask of the chips detected.
183+
184+ Parameters
185+ ----------
186+ rgb_img : numpy.ndarray
187+ Input RGB image data containing a color card.
188+ **kwargs
189+ Other keyword arguments passed to cv2.adaptiveThreshold and cv2.circle.
190+
191+ Valid keyword arguments:
192+ adaptive_method: 0 (mean) or 1 (Gaussian) (default = 1)
193+ block_size: int (default = 51)
194+ radius: int (default = 20)
195+ min_size: int (default = 1000)
196+
197+ Returns
198+ -------
199+
200+ numpy.ndarray
201+ Binary bounding box mask of the detected color card chips
202+ """
203+ _ , _ , _ , _ , _ , bounding_mask = _color_card_detection (rgb_img , ** kwargs )
204+
205+ if params .debug is not None :
206+ # Find contours
207+ cnt , cnt_str = _cv2_findcontours (bin_img = bounding_mask )
208+
209+ # Consolidate contours
210+ obj = _object_composition (contours = cnt , hierarchy = cnt_str )
211+ bb_debug = cv2 .drawContours (np .copy (rgb_img ), [obj ], - 1 , (255 , 0 , 255 ), params .line_thickness )
212+
213+ # Debug image handling
214+ _debug (visual = bb_debug , filename = os .path .join (params .debug_outdir , f'{ params .device } _color_card.png' ))
215+
216+ return bounding_mask
217+
218+
219+ def detect_color_card (rgb_img , label = None , ** kwargs ):
220+ """Automatically detect a color card.
221+
222+ Parameters
223+ ----------
224+ rgb_img : numpy.ndarray
225+ Input RGB image data containing a color card.
226+ label : str, optional
227+ modifies the variable name of observations recorded (default = pcv.params.sample_label).
228+ **kwargs
229+ Other keyword arguments passed to cv2.adaptiveThreshold and cv2.circle.
230+
231+ Valid keyword arguments:
232+ adaptive_method: 0 (mean) or 1 (Gaussian) (default = 1)
233+ block_size: int (default = 51)
234+ radius: int (default = 20)
235+ min_size: int (default = 1000)
236+
237+ Returns
238+ -------
239+ numpy.ndarray
240+ Labeled mask of chips.
241+ """
242+ # Set lable to params.sample_label if None
243+ if label is None :
244+ label = params .sample_label
245+ deprecation_warning (
246+ "The 'label' parameter is no longer utilized, since color chip size is now metadata. "
247+ "It will be removed in PlantCV v5.0."
248+ )
249+
250+ labeled_mask , debug_img , marea , mheight , mwidth , _ = _color_card_detection (rgb_img , ** kwargs )
251+ # Create dataframe for easy summary stats
252+ chip_size = np .median (marea )
253+ chip_height = np .median (mheight )
254+ chip_width = np .median (mwidth )
255+
184256 # Save out chip size for pixel to cm standardization
185257 outputs .add_metadata (term = "median_color_chip_size" , datatype = float , value = chip_size )
186258 outputs .add_metadata (term = "median_color_chip_width" , datatype = float , value = chip_width )
0 commit comments