Repository: scikit-image/scikit-image

Issue:
Return False from LineModelND and CircleModel when inputs are underdetermined 
## Description

### Proposed Changes
1. Replace the [ValueError](https://github.com/scikit-image/scikit-image/blob/38b595d60befe3a0b4c0742995b9737200a079c6/skimage/measure/fit.py#L94) in ```LineModelND.estimate()``` with ```return False```
2. Modify the [corresponding tests](https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/tests/test_fit.py#L25) to assert a ```False``` return, instead of checking for a ValueError
3. Add a ```return False``` to ```CircleModel.estimate()``` after the warning under the [if-statement](https://github.com/scikit-image/scikit-image/blob/38b595d60befe3a0b4c0742995b9737200a079c6/skimage/measure/fit.py#L295) that checks for collinearity.
4. Remove the second statement in the above mentioned warning, which states, "In scikit-image 1.0, this warning will become a ValueError."

### Justification: Make classes more consistent with analogs
[```LineModelND```](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.LineModelND) raises a ValueError when the input data are under-determined. Similarly, [```CircleModel```](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.CircleModel) is scheduled to raise a ValueError when the input data are collinear. 

```LineModelND``` and ```CircleModel``` are part of a wider set of classes that include an ```estimate``` method for fitting points to a model and may be applied in algorithms such as [```ransac```](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.ransac). Examples include the transforms in [```_geometric.py```](https://github.com/scikit-image/scikit-image/blob/main/skimage/transform/_geometric.py) and ```EllipseModel()``` in [```fit.py```](https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/fit.py). **None of analogous other classes raise a ValueError when the inputs are underdetermined but instead return False.** 

```LineModelND``` and ```CircleModel``` will be more consistent with their analogs if they do not raise ValueErrors when the input data are underdetermined.


## Way to reproduce

Not applicable.


## Version information
Not sure if applicable, but...
```python
# Paste the output of the following python commands
3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:07:06) [Clang 13.0.1 ]
macOS-12.4-arm64-arm-64bit
scikit-image version: 0.19.3
numpy version: 1.23.1
```




Discussion:

Resolving patch:
diff --git a/skimage/measure/fit.py b/skimage/measure/fit.py
--- a/skimage/measure/fit.py
+++ b/skimage/measure/fit.py
@@ -91,7 +91,7 @@ def estimate(self, data):
             _, _, v = np.linalg.svd(data, full_matrices=False)
             direction = v[0]
         else:  # under-determined
-            raise ValueError('At least 2 input points needed.')
+            return False
 
         self.params = (origin, direction)
 
@@ -293,8 +293,8 @@ def estimate(self, data):
         C, _, rank, _ = np.linalg.lstsq(A, f, rcond=None)
 
         if rank != 3:
-            warn("Input data does not contain enough significant data points. "
-                 "In scikit-image 1.0, this warning will become a ValueError.")
+            warn("Input does not contain enough significant data points.")
+            return False
 
         center = C[0:2]
         distances = spatial.minkowski_distance(center, data)


Test patch:
diff --git a/skimage/measure/tests/test_fit.py b/skimage/measure/tests/test_fit.py
--- a/skimage/measure/tests/test_fit.py
+++ b/skimage/measure/tests/test_fit.py
@@ -8,12 +8,6 @@
 from skimage.measure.fit import _dynamic_max_trials
 from skimage.transform import AffineTransform
 
-
-def test_line_model_invalid_input():
-    with testing.raises(ValueError):
-        LineModelND().estimate(np.empty((1, 3)))
-
-
 def test_line_model_predict():
     model = LineModelND()
     model.params = ((0, 0), (1, 1))
@@ -38,16 +32,12 @@ def test_line_model_nd_invalid_input():
     with testing.raises(ValueError):
         LineModelND().predict_y(np.zeros(1), np.zeros(1))
 
-    with testing.raises(ValueError):
-        LineModelND().estimate(np.empty((1, 3)))
+    assert not LineModelND().estimate(np.empty((1, 3)))
+    assert not LineModelND().estimate(np.empty((1, 2)))
 
     with testing.raises(ValueError):
         LineModelND().residuals(np.empty((1, 3)))
 
-    data = np.empty((1, 2))
-    with testing.raises(ValueError):
-        LineModelND().estimate(data)
-
 
 def test_line_model_nd_predict():
     model = LineModelND()
@@ -101,10 +91,6 @@ def test_line_model_nd_residuals():
     assert_equal(abs(model.residuals(data, params=params)), 30)
 
 
-def test_line_modelND_under_determined():
-    data = np.empty((1, 3))
-    with testing.raises(ValueError):
-        LineModelND().estimate(data)
 
 
 def test_circle_model_invalid_input():
@@ -162,14 +148,14 @@ def test_circle_model_residuals():
 
 def test_circle_model_insufficient_data():
     model = CircleModel()
-
-    with expected_warnings(["Input data does not contain enough significant"]):
+    warning_message = ["Input does not contain enough significant data points."]
+    with expected_warnings(warning_message):
         model.estimate(np.array([[1, 2], [3, 4]]))
 
-    with expected_warnings(["Input data does not contain enough significant"]):
+    with expected_warnings(warning_message):
         model.estimate(np.ones((6, 2)))
 
-    with expected_warnings(["Input data does not contain enough significant"]):
+    with expected_warnings(warning_message):
         model.estimate(np.array([[0, 0], [1, 1], [2, 2]]))