Repository: scipy/scipy

Issue:
binary_opening/binary_closing missing arguments
As a comparison, [`binary_dilation`]( https://docs.scipy.org/doc/scipy-0.19.0/reference/generated/scipy.ndimage.binary_dilation.html#scipy.ndimage.binary_dilation ) provides `mask`, `border_value`, and `brute_force`. While the purpose of `brute_force` is unclear ( https://github.com/scipy/scipy/issues/4586 ), the other two have clear meanings and value for these operations.


Discussion:
I'd like to take this up and work on it. Looking at the code and the docs, it seems simple enough. The basic erosion and dilation operations are being called with default values for `mask`, `border_value`, and `brute_force`. Arguments can be added to the function definition with these default values.

Resolving patch:
diff --git a/scipy/ndimage/morphology.py b/scipy/ndimage/morphology.py
--- a/scipy/ndimage/morphology.py
+++ b/scipy/ndimage/morphology.py
@@ -507,7 +507,7 @@ def binary_dilation(input, structure=None, iterations=1, mask=None,
 
 
 def binary_opening(input, structure=None, iterations=1, output=None,
-                   origin=0):
+                   origin=0, mask=None, border_value=0, brute_force=False):
     """
     Multi-dimensional binary opening with the given structuring element.
 
@@ -535,6 +535,17 @@ def binary_opening(input, structure=None, iterations=1, output=None,
         By default, a new array is created.
     origin : int or tuple of ints, optional
         Placement of the filter, by default 0.
+    mask : array_like, optional
+        If a mask is given, only those elements with a True value at
+        the corresponding mask element are modified at each iteration.
+    border_value : int (cast to 0 or 1), optional
+        Value at the border in the output array.
+    brute_force : boolean, optional
+        Memory condition: if False, only the pixels whose value was changed in
+        the last iteration are tracked as candidates to be updated in the
+        current iteration; if true all pixels are considered as candidates for
+        update, regardless of what happened in the previous iteration.
+        False by default.
 
     Returns
     -------
@@ -606,14 +617,14 @@ def binary_opening(input, structure=None, iterations=1, output=None,
         rank = input.ndim
         structure = generate_binary_structure(rank, 1)
 
-    tmp = binary_erosion(input, structure, iterations, None, None, 0,
-                         origin)
-    return binary_dilation(tmp, structure, iterations, None, output, 0,
-                           origin)
+    tmp = binary_erosion(input, structure, iterations, mask, None,
+                         border_value, origin, brute_force)
+    return binary_dilation(tmp, structure, iterations, mask, output,
+                           border_value, origin, brute_force)
 
 
 def binary_closing(input, structure=None, iterations=1, output=None,
-                   origin=0):
+                   origin=0, mask=None, border_value=0, brute_force=False):
     """
     Multi-dimensional binary closing with the given structuring element.
 
@@ -641,6 +652,17 @@ def binary_closing(input, structure=None, iterations=1, output=None,
         By default, a new array is created.
     origin : int or tuple of ints, optional
         Placement of the filter, by default 0.
+    mask : array_like, optional
+        If a mask is given, only those elements with a True value at
+        the corresponding mask element are modified at each iteration.
+    border_value : int (cast to 0 or 1), optional
+        Value at the border in the output array.
+    brute_force : boolean, optional
+        Memory condition: if False, only the pixels whose value was changed in
+        the last iteration are tracked as candidates to be updated in the
+        current iteration; if true al pixels are considered as candidates for
+        update, regardless of what happened in the previous iteration.
+        False by default.
 
     Returns
     -------
@@ -735,10 +757,10 @@ def binary_closing(input, structure=None, iterations=1, output=None,
         rank = input.ndim
         structure = generate_binary_structure(rank, 1)
 
-    tmp = binary_dilation(input, structure, iterations, None, None, 0,
-                          origin)
-    return binary_erosion(tmp, structure, iterations, None, output, 0,
-                          origin)
+    tmp = binary_dilation(input, structure, iterations, mask, None,
+                          border_value, origin, brute_force)
+    return binary_erosion(tmp, structure, iterations, mask, output,
+                          border_value, origin, brute_force)
 
 
 def binary_hit_or_miss(input, structure1=None, structure2=None,


Test patch:
diff --git a/scipy/ndimage/tests/test_ndimage.py b/scipy/ndimage/tests/test_ndimage.py
--- a/scipy/ndimage/tests/test_ndimage.py
+++ b/scipy/ndimage/tests/test_ndimage.py
@@ -4578,3 +4578,25 @@ def test_dilation_scalar_size(self):
         result = ndimage.grey_dilation(self.array, size=3)
         assert_array_almost_equal(result, self.dilated3x3)
 
+class TestBinaryOpeningClosing:
+
+    def setup_method(self):
+        a = numpy.zeros((5,5), dtype=bool)
+        a[1:4, 1:4] = True
+        a[4,4] = True
+        self.array = a
+        self.sq3x3 = numpy.ones((3,3))
+        self.opened_old = ndimage.binary_opening(self.array, self.sq3x3,
+                                                 1, None, 0)
+        self.closed_old = ndimage.binary_closing(self.array, self.sq3x3,
+                                                 1, None, 0)
+
+    def test_opening_new_arguments(self):
+        opened_new = ndimage.binary_opening(self.array, self.sq3x3, 1, None,
+                                            0, None, 0, False)
+        assert_array_equal(opened_new, self.opened_old)
+
+    def test_closing_new_arguments(self):
+        closed_new = ndimage.binary_closing(self.array, self.sq3x3, 1, None,
+                                            0, None, 0, False)
+        assert_array_equal(closed_new, self.closed_old)