Repository: scipp/plopp

Issue:
Set default limit on shape to avoid freezes/crashes when plotting large objects

Discussion:

Patch:
diff --git a/src/plopp/prep.py b/src/plopp/prep.py
index f0876a1..4f790da 100644
--- a/src/plopp/prep.py
+++ b/src/plopp/prep.py
@@ -4,7 +4,7 @@
 from .tools import number_to_variable
 
 from scipp import Variable, DataArray, arange, to_unit
-from numpy import ndarray
+from numpy import ndarray, prod
 
 
 def _to_data_array(obj):
@@ -27,7 +27,17 @@ def _convert_if_not_none(x, unit):
     return x
 
 
-def preprocess(obj, crop=None, name=''):
+def _check_input_data_size(obj):
+    limits = {1: 1_000_000, 2: 2500 * 2500}
+    if obj.ndim not in limits:
+        raise ValueError("plot can only handle 1d and 2d data.")
+    if prod(obj.shape) > limits[obj.ndim]:
+        raise ValueError(f"Plotting data of size {obj.shape} may take very long or use "
+                         "an excessive amount of memory. This is therefore disabled by "
+                         "default. To bypass this check, use `ignore_size=True`.")
+
+
+def preprocess(obj, crop=None, name='', ignore_size=False):
     out = _to_data_array(obj)
     if not out.name:
         out.name = name
@@ -43,4 +53,6 @@ def preprocess(obj, crop=None, name=''):
         start = max(out[dim, :smin].sizes[dim] - 1, 0)
         width = out[dim, smin:smax].sizes[dim]
         out = out[dim, start:start + width + 2]
+    if not ignore_size:
+        _check_input_data_size(out)
     return out
diff --git a/src/plopp/wrappers.py b/src/plopp/wrappers.py
index b6503d1..ee91b2c 100644
--- a/src/plopp/wrappers.py
+++ b/src/plopp/wrappers.py
@@ -30,6 +30,7 @@ def plot(obj: Union[VariableLike, Dict[str, VariableLike]],
          crop: Dict[str, Dict[str, Variable]] = None,
          errorbars: bool = True,
          grid: bool = False,
+         ignore_size: bool = False,
          mask_color: str = 'black',
          norm: Literal['linear', 'log'] = 'linear',
          scale: Dict[str, str] = None,
@@ -63,6 +64,9 @@ def plot(obj: Union[VariableLike, Dict[str, VariableLike]],
         Show errorbars in 1d plots if `True`.
     grid:
         Show grid if `True`.
+    ignore_size:
+        If `True`, skip the check that prevents the rendering of very large data
+        objects.
     mask_color:
         Color of masks in 1d plots.
     norm:
@@ -93,15 +97,18 @@ def plot(obj: Union[VariableLike, Dict[str, VariableLike]],
     """
     _, _, _, listed_args = inspect.getargvalues(inspect.currentframe())
     all_args = {
-        **{k: v
-           for k, v in listed_args.items() if k not in ('obj', 'kwargs')},
+        **{
+            k: v
+            for k, v in listed_args.items() if k not in ('obj', 'ignore_size', 'kwargs')
+        },
         **kwargs
     }
     if isinstance(obj, (dict, Dataset)):
         nodes = [
-            input_node(preprocess(item, crop=crop, name=name))
+            input_node(preprocess(item, crop=crop, name=name, ignore_size=ignore_size))
             for name, item in obj.items()
         ]
         return figure(*nodes, **all_args)
     else:
-        return figure(input_node(preprocess(obj, crop=crop)), **all_args)
+        return figure(input_node(preprocess(obj, crop=crop, ignore_size=ignore_size)),
+                      **all_args)


Test patch:
diff --git a/tests/wrappers_test.py b/tests/wrappers_test.py
index 9aa6af1..19cf382 100644
--- a/tests/wrappers_test.py
+++ b/tests/wrappers_test.py
@@ -1,10 +1,11 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright (c) 2022 Scipp contributors (https://github.com/scipp)
 
-import scipp as sc
 import numpy as np
 from factory import make_dense_data_array, make_dense_dataset
 import plopp as pp
+import pytest
+import scipp as sc
 
 
 def test_plot_ndarray():
@@ -56,3 +57,17 @@ def test_plot_coord_with_no_unit():
     da = sc.data.table_xyz(100).bin(x=10).bins.mean()
     da.coords['x'].unit = None
     pp.plot(da)
+
+
+def test_plot_rejects_large_input():
+    error_message = "may take very long or use an excessive amount of memory"
+    with pytest.raises(ValueError) as e1d:
+        pp.plot(np.random.random(1_100_000))
+    assert error_message in str(e1d)
+    with pytest.raises(ValueError) as e2d:
+        pp.plot(np.random.random((3000, 2500)))
+    assert error_message in str(e2d)
+
+
+def test_plot_ignore_size_disables_size_check():
+    pp.plot(np.random.random(1_100_000), ignore_size=True)