fitzee commented on code in PR #44034:
URL: https://github.com/apache/superset/pull/44034#discussion_r3967157656


##########
superset/utils/screenshot_utils.py:
##########
@@ -130,26 +137,91 @@ class ScreenshotCaptureTimeoutError(RuntimeError):
     """Raised when Chromium repeatedly times out while capturing a tile."""
 
 
-def is_screenshot_nearly_uniform(screenshot: bytes) -> tuple[bool, float]:
-    """Return whether one color occupies nearly all sampled screenshot 
pixels."""
+class ScreenshotBlankCaptureError(RuntimeError):
+    """Raised when Chromium repeatedly returns a perceptually blank capture."""
+
+
+@dataclass(frozen=True)
+class ScreenshotBlanknessMetrics:
+    """Metrics used to decide whether a screenshot is perceptually blank."""
+
+    is_blank: bool
+    dominant_pixel_ratio: float
+    near_white_pixel_ratio: float
+    mean_luminance: float
+    luminance_stddev: float
+    entropy: float
+
+
+def get_screenshot_blankness_metrics(screenshot: bytes) -> 
ScreenshotBlanknessMetrics:
+    """Measure exact-color and perceptual blankness on a sampled screenshot."""
 
     try:
         with Image.open(io.BytesIO(screenshot)) as image:
-            sample = image.convert("RGB")
-            sample.thumbnail((256, 256))
-            colors = sample.getcolors(maxcolors=256)
-            if not colors:
-                return False, 0.0
-            dominant_pixels = max(count for count, _color in colors)
-            dominant_ratio = dominant_pixels / (sample.width * sample.height)
-            return (
-                dominant_ratio >= TILED_SCREENSHOT_BLANK_DOMINANT_PIXEL_RATIO,
-                dominant_ratio,
+            sample_metrics: list[ScreenshotBlanknessMetrics] = []
+            for sample_size in SCREENSHOT_BLANK_SAMPLE_SIZES:
+                sample = image.convert("RGB")
+                sample.thumbnail((sample_size, sample_size))
+                pixel_count = sample.width * sample.height
+                colors = sample.getcolors(maxcolors=pixel_count) or []
+                dominant_pixels = max(
+                    (count for count, _color in colors),
+                    default=0,
+                )
+                dominant_ratio = dominant_pixels / pixel_count
+
+                grayscale = sample.convert("L")
+                histogram = grayscale.histogram()
+                near_white_ratio = (
+                    sum(histogram[SCREENSHOT_BLANK_MIN_LUMINANCE:]) / 
pixel_count
+                )
+                statistics = ImageStat.Stat(grayscale)
+                mean_luminance = float(statistics.mean[0])
+                luminance_stddev = float(statistics.stddev[0])
+                entropy = -sum(
+                    (count / pixel_count) * math.log2(count / pixel_count)
+                    for count in histogram
+                    if count
+                )
+                low_information = (
+                    luminance_stddev <= SCREENSHOT_BLANK_MAX_LUMINANCE_STDDEV
+                    and entropy <= SCREENSHOT_BLANK_MAX_ENTROPY
+                )
+                perceptually_blank = low_information and (

Review Comment:
   Addressed the false-positive surface in 0c283b88d8 without adding a global 
opt-out. The classifier now measures structural edge density at both sampling 
scales, so light/thin chart and table marks prevent a blank classification even 
when global luminance variance and entropy remain low. The actual incident 
raster remains classified blank (stddev=2.525, entropy=0.801, 
structural_edge_ratio=0.00000). I’m intentionally not adding a bypass flag 
because disabling validation would restore silent blank delivery; the more 
targeted classifier correction preserves fail-closed behavior.



##########
superset/utils/screenshot_utils.py:
##########
@@ -1224,6 +1342,27 @@ def _raise_if_budget_exhausted() -> None:
             log_context=log_context,
         )
 
+        if report_execution_context and contentful_tiles_captured:

Review Comment:
   Fixed in 0c283b88d8. Once contentful tiles have individually passed capture 
validation, the combined-image classification is now advisory/log-only () 
rather than a second rejection gate. This avoids a 1024px composite downsample 
overriding stronger per-tile evidence on tall or sparse dashboards.



##########
tests/unit_tests/utils/test_screenshot_utils.py:
##########
@@ -68,6 +79,66 @@ def test_chart_like_png_is_not_blank(self):
         assert is_blank is False
         assert dominant_ratio < 0.995
 
+    def test_two_tone_near_white_png_is_blank(self):
+        is_blank, dominant_ratio = 
is_screenshot_nearly_uniform(_two_tone_blank())
+
+        assert is_blank is True
+        assert dominant_ratio == 0.85
+
+    def test_two_tone_gray_below_near_white_cutoff_is_blank(self):
+        image = Image.new("RGB", (100, 100), "white")
+        for y in range(85, 100):
+            for x in range(100):
+                image.putpixel((x, y), (239, 239, 239))
+        output = io.BytesIO()
+        image.save(output, format="PNG")
+
+        is_blank, dominant_ratio = 
is_screenshot_nearly_uniform(output.getvalue())
+
+        assert is_blank is True
+        assert dominant_ratio == 0.85
+
+    def test_sparse_readable_text_is_not_blank(self):
+        image = Image.new("RGB", (800, 1000), "white")
+        label = Image.new("RGB", (60, 14), "white")
+        ImageDraw.Draw(label).text(
+            (0, 0), "No data", fill="black", font=ImageFont.load_default()
+        )
+        label = label.resize((240, 56), Image.Resampling.NEAREST)
+        image.paste(label, (20, 20))
+        output = io.BytesIO()
+        image.save(output, format="PNG")
+
+        is_blank, _dominant_ratio = 
is_screenshot_nearly_uniform(output.getvalue())
+
+        assert is_blank is False
+
+    def test_tall_sparse_report_with_content_is_not_blank(self):

Review Comment:
   Addressed in 0c283b88d8. Added 3000x1200 fixtures for a light, thin-stroke 
line/scatter-style chart and a sparse light table. Both exercise the new 
structural-edge signal and assert non-blank classification. The actual customer 
blank raster was also rechecked and still classifies blank.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to