Repository: deepchecks/deepchecks

Issue:
[DOCS][CV] Add property user guide
Properties are a central theme in the current checks of the vision package. We need to:
- [x] Rename label measurements to properties - resolved by #1013 
- [x] Refactor label properties to work by batch and not by single label - resolved by #1013 
- [x] Improve image properties names - resolved by #1013 
- [ ] Create a dedicated user guide to help the user use them and define custom properties. 
- [ ] Link checks using properties to the user guide.


Discussion:

Resolving patch:
diff --git a/deepchecks/vision/checks/distribution/image_property_drift.py b/deepchecks/vision/checks/distribution/image_property_drift.py
--- a/deepchecks/vision/checks/distribution/image_property_drift.py
+++ b/deepchecks/vision/checks/distribution/image_property_drift.py
@@ -63,6 +63,11 @@ def __init__(
         self.train_properties = defaultdict(list)
         self.test_properties = defaultdict(list)
 
+    def initialize_run(self, context: Context):
+        """Initialize self state, and validate the run context."""
+        context.train.assert_image_formatter_valid()
+        context.test.assert_image_formatter_valid()
+
     def update(
         self,
         context: Context,
diff --git a/deepchecks/vision/utils/image_properties.py b/deepchecks/vision/utils/image_properties.py
--- a/deepchecks/vision/utils/image_properties.py
+++ b/deepchecks/vision/utils/image_properties.py
@@ -42,18 +42,14 @@ def area(batch: List[np.ndarray]) -> List[int]:
 
 def brightness(batch: List[np.ndarray]) -> List[float]:
     """Calculate brightness on each image in the batch."""
-    if _is_grayscale(batch) is True:
-        return [img.mean() for img in batch]
-    else:
-        return [rgb2gray(img).mean() for img in batch]
+    return [img.mean() if _is_grayscale(img) else rgb2gray(img).mean()
+            for img in batch]
 
 
 def rms_contrast(batch: List[np.array]) -> List[float]:
     """Return RMS contrast of image."""
-    if _is_grayscale(batch) is False:
-        batch = [rgb2gray(img) for img in batch]
-
-    return [img.std() for img in batch]
+    return [img.std() if _is_grayscale(img) else rgb2gray(img).std()
+            for img in batch]
 
 
 def normalized_red_mean(batch: List[np.ndarray]) -> List[float]:
@@ -87,8 +83,6 @@ def _normalized_rgb_mean(batch: List[np.ndarray]) -> List[Tuple[float, float, fl
     ----------
     batch: List[np.ndarray]
         A list of arrays, each arrays represents an image in the required deepchecks format.
-    sample_size_for_image_properties: int
-        The number of pixels to sample from each image.
 
     Returns
     -------
@@ -96,10 +90,8 @@ def _normalized_rgb_mean(batch: List[np.ndarray]) -> List[Tuple[float, float, fl
         List of 3-dimensional arrays, each dimension is the normalized mean of the color channel. An array is
         returned for each image.
     """
-    if _is_grayscale(batch) is True:
-        return [(None, None, None)] * len(batch)
-
-    return [_normalize_pixelwise(img).mean(axis=(1, 2)) for img in batch]
+    return [_normalize_pixelwise(img).mean(axis=(1, 2)) if not _is_grayscale(img) else (None, None, None)
+            for img in batch]
 
 
 def _normalize_pixelwise(img: np.ndarray) -> np.ndarray:
@@ -120,8 +112,8 @@ def _normalize_pixelwise(img: np.ndarray) -> np.ndarray:
                      for i in range(3)])
 
 
-def _is_grayscale(batch):
-    return get_dimension(batch[0]) == 1
+def _is_grayscale(img):
+    return get_dimension(img) == 1
 
 
 def get_size(img) -> Tuple[int, int]:
diff --git a/deepchecks/vision/vision_data.py b/deepchecks/vision/vision_data.py
--- a/deepchecks/vision/vision_data.py
+++ b/deepchecks/vision/vision_data.py
@@ -72,16 +72,18 @@ def __init__(self,
         self._label_map = label_map
         self._transform_field = transform_field
         self._warned_labels = set()
-        self._has_images = False
+        self._image_formatter_error = None
 
         try:
             self.validate_image_data(next(iter(self._data_loader)))
-            self._has_images = True
         except DeepchecksNotImplementedError:
-            logger.warning('batch_to_images() was not implemented, some checks will not run')
+            self._image_formatter_error = 'batch_to_images() was not implemented, some checks will not run'
+            logger.warning(self._image_formatter_error)
         except ValidationError as ex:
-            logger.warning('batch_to_images() was not implemented correctly, '
-                           'the validation has failed with the error: %s', {str(ex)})
+            self._image_formatter_error = f'batch_to_images() was not implemented correctly, the validation has ' \
+                                          f'failed with the error: "{ex}". To test your image formatting use the ' \
+                                          f'function `validate_image_data(batch)`'
+            logger.warning(self._image_formatter_error)
 
         self._task_type = TaskType.OTHER
         self._has_label = None
@@ -362,6 +364,11 @@ def __len__(self):
         """Return the number of batches in the dataset dataloader."""
         return len(self._data_loader)
 
+    def assert_image_formatter_valid(self):
+        """Assert the image formatter defined is valid. Else raise exception."""
+        if self._image_formatter_error is not None:
+            raise DeepchecksValueError(self._image_formatter_error)
+
     @staticmethod
     def _get_data_loader_copy(data_loader: DataLoader, n_samples: int = None, shuffle: bool = False,
                               random_state: int = None):


Test patch: