From b533699e7f87906147e6ea732a29cd9c29df8ec0 Mon Sep 17 00:00:00 2001 From: Arthur031221 Date: Mon, 27 Jul 2026 03:37:01 +0800 Subject: [PATCH] FIX explicit vmin/vmax of 0 discarded by Dataview2D.to_json Dataview2D.to_json fell back to the dim's own range with `or`, so any limit equal to 0 was treated as unset and replaced by the 1st or 99th percentile of the data. A Volume2D or Vertex2D built with vmin=0 reached the webgl viewer with a colour scale nobody asked for. import numpy as np, cortex shape = cortex.db.get_xfm("S1", "fullhead").shape data = np.linspace(0.0, 1.0, int(np.prod(shape))).reshape(shape) d1 = cortex.Volume(data, "S1", "fullhead") d2 = cortex.Volume(data, "S1", "fullhead") view = cortex.Volume2D(d1, d2, vmin=0, vmax=3, vmin2=0, vmax2=4) view.vmin, view.vmax, view.vmin2, view.vmax2 # (0, 3, 0, 4) view.to_json()["vmin"] # [[np.float64(0.01), np.float64(0.01)]] <- the 1st percentile, not the 0 asked for view.to_json()["vmax"] # [[3, 4]] <- nonzero bounds in the same call survive The object holds what the caller asked for, so the loss happens in to_json alone, and only for the bounds that happen to be 0. This looks like an oversight rather than a chosen default. The same file makes this pick with an `is None` test in ten other places: lines 27-28 in Dataview2D.__init__, 171-174 in Volume2D.__init__, and 267-270 in Vertex2D.__init__. _to_raw passes self.vmin straight to Normalize, so quickshow already honoured a limit of 0 while the webgl path did not. The 1D Dataview.to_json in views.py uses `is None` as well. Use the same test here. The fallback is kept rather than dropped: Volume2D and Vertex2D resolve None in their constructors, so it is unreachable through them, but Dataview2D can be constructed directly and leaves vmin as None, and removing it would change behaviour beyond this bug. #332 reported the same family of problem, a limit the caller asked for not reaching the render, and e15f35f ("FIX vmin2 and vmax2 not used in Vertex2D") worked in __init__ and left to_json alone. --- cortex/dataset/view2D.py | 6 ++++-- cortex/tests/test_dataset.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/cortex/dataset/view2D.py b/cortex/dataset/view2D.py index 6a41792ac..341c7c50a 100644 --- a/cortex/dataset/view2D.py +++ b/cortex/dataset/view2D.py @@ -57,8 +57,10 @@ def to_json(self, simple=False): d1js = self.dim1.to_json() d2js = self.dim2.to_json() sdict.update(dict( - vmin = [[self.vmin or d1js['vmin'][0], self.vmin2 or d2js['vmin'][0]]], - vmax = [[self.vmax or d1js['vmax'][0], self.vmax2 or d2js['vmax'][0]]], + vmin = [[d1js['vmin'][0] if self.vmin is None else self.vmin, + d2js['vmin'][0] if self.vmin2 is None else self.vmin2]], + vmax = [[d1js['vmax'][0] if self.vmax is None else self.vmax, + d2js['vmax'][0] if self.vmax2 is None else self.vmax2]], )) if "xfm" in d1js: diff --git a/cortex/tests/test_dataset.py b/cortex/tests/test_dataset.py index d3224bd4f..0e3d03691 100644 --- a/cortex/tests/test_dataset.py +++ b/cortex/tests/test_dataset.py @@ -154,6 +154,36 @@ def test_2D(): twod.to_json() +def test_2D_to_json_keeps_explicit_zero_limits(): + """to_json must keep an explicitly requested limit of 0 for all four bounds. + + 0 is falsy, so a fallback written with ``or`` silently replaces it with the + data range taken from the underlying dim. + """ + d1 = cortex.Volume.random(subj, xfmname) + d2 = cortex.Volume.random(subj, xfmname) + + # Control: nonzero limits are already passed through untouched. + control = cortex.Volume2D(d1, d2, vmin=1, vmax=3, vmin2=2, vmax2=4) + assert control.to_json()["vmin"] == [[1, 2]] + assert control.to_json()["vmax"] == [[3, 4]] + + view = cortex.Volume2D(d1, d2, vmin=0, vmax=0, vmin2=0, vmax2=0) + assert view.to_json()["vmin"] == [[0, 0]] + assert view.to_json()["vmax"] == [[0, 0]] + + v1 = cortex.Vertex.random(subj) + v2 = cortex.Vertex.random(subj) + + control = cortex.Vertex2D(v1, v2, vmin=1, vmax=3, vmin2=2, vmax2=4) + assert control.to_json()["vmin"] == [[1, 2]] + assert control.to_json()["vmax"] == [[3, 4]] + + view = cortex.Vertex2D(v1, v2, vmin=0, vmax=0, vmin2=0, vmax2=0) + assert view.to_json()["vmin"] == [[0, 0]] + assert view.to_json()["vmax"] == [[0, 0]] + + def test_braindata_hash(): d = cortex.Volume.random(subj, xfmname) hash(d)