Skip to content

Commit 274d793

Browse files
authored
Add tally colorbar min/max rescaling options (#169)
1 parent d06fb2f commit 274d793

File tree

4 files changed

+85
-27
lines changed

4 files changed

+85
-27
lines changed

openmc_plotter/docks.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -773,17 +773,12 @@ class ColorForm(QWidget):
773773
Selector for colormap
774774
dataIndicatorCheckBox : QCheckBox
775775
Inidcates whether or not the data indicator will appear on the colorbar
776-
userMinMaxBox : QCheckBox
777-
Indicates whether or not the user defined values in the min and max
778-
will be used to set the bounds of the colorbar.
776+
minMaxTypeBox : QComboBox
777+
Dropdown to select min/max type: "Full data", "Visible data", or "Custom"
779778
maxBox : ScientificDoubleSpinBox
780-
Max value of the colorbar. If the userMinMaxBox is checked, this will be
781-
the user's input. If the userMinMaxBox is not checked, this box will
782-
hold the max value of the visible data.
779+
Max value of the colorbar. Only visible when minMaxTypeBox is set to "Custom".
783780
minBox : ScientificDoubleSpinBox
784-
Min value of the colorbar. If the userMinMaxBox is checked, this will be
785-
the user's input. If the userMinMaxBox is not checked, this box will
786-
hold the max value of the visible data.
781+
Min value of the colorbar. Only visible when minMaxTypeBox is set to "Custom".
787782
scaleBox : QCheckBox
788783
Indicates whether or not the data is displayed on a log or linear
789784
scale
@@ -844,10 +839,13 @@ def __init__(self, model, main_window, field, colormaps=None):
844839
self.dataIndicatorCheckBox.stateChanged.connect(
845840
data_indicator_connector)
846841

847-
# User specified min/max check box
848-
self.userMinMaxBox = QCheckBox()
849-
minmax_connector = partial(main_window.toggleTallyDataUserMinMax)
850-
self.userMinMaxBox.stateChanged.connect(minmax_connector)
842+
# Min/max type dropdown
843+
self.minMaxTypeBox = QComboBox()
844+
self.minMaxTypeBox.addItem("Full data")
845+
self.minMaxTypeBox.addItem("Visible data")
846+
self.minMaxTypeBox.addItem("Custom")
847+
minmax_type_connector = partial(main_window.setTallyMinMaxType)
848+
self.minMaxTypeBox.currentIndexChanged.connect(minmax_type_connector)
851849

852850
# Data min spin box
853851
self.minBox = ScientificDoubleSpinBox()
@@ -861,6 +859,10 @@ def __init__(self, model, main_window, field, colormaps=None):
861859
max_connector = partial(main_window.editTallyDataMax)
862860
self.maxBox.valueChanged.connect(max_connector)
863861

862+
# Labels for min/max (so we can show/hide them)
863+
self.minLabel = QLabel("Min: ")
864+
self.maxLabel = QLabel("Max: ")
865+
864866
# Linear/Log scaling check box
865867
self.scaleBox = QCheckBox()
866868
scale_connector = partial(main_window.toggleTallyLogScale)
@@ -894,9 +896,9 @@ def __init__(self, model, main_window, field, colormaps=None):
894896
self.layout.addRow("Colormap: ", self.colormapBox)
895897
self.layout.addRow("Reverse colormap: ", self.reverseCmapBox)
896898
self.layout.addRow("Data Indicator: ", self.dataIndicatorCheckBox)
897-
self.layout.addRow("Custom Min/Max: ", self.userMinMaxBox)
898-
self.layout.addRow("Min: ", self.minBox)
899-
self.layout.addRow("Max: ", self.maxBox)
899+
self.layout.addRow("Min/max: ", self.minMaxTypeBox)
900+
self.layout.addRow(self.minLabel, self.minBox)
901+
self.layout.addRow(self.maxLabel, self.maxBox)
900902
self.layout.addRow("Log Scale: ", self.scaleBox)
901903
self.layout.addRow("Clip Data: ", self.clipDataBox)
902904
self.layout.addRow("Mask Zeros: ", self.maskZeroBox)
@@ -914,16 +916,26 @@ def updateDataIndicator(self):
914916
cv = self.model.currentView
915917
self.dataIndicatorCheckBox.setChecked(cv.tallyDataIndicator)
916918

917-
def setMinMaxEnabled(self, enable):
918-
enable = bool(enable)
919-
self.minBox.setEnabled(enable)
920-
self.maxBox.setEnabled(enable)
919+
def updateMinMaxType(self):
920+
"""Update the min/max type dropdown and show/hide min/max inputs."""
921+
cv = self.model.currentView
922+
type_map = {'full': 0, 'visible': 1, 'custom': 2}
923+
idx = type_map.get(cv.tallyDataMinMaxType, 0)
924+
self.minMaxTypeBox.blockSignals(True)
925+
self.minMaxTypeBox.setCurrentIndex(idx)
926+
self.minMaxTypeBox.blockSignals(False)
927+
# Show/hide min/max inputs based on whether custom is selected
928+
show_custom = (cv.tallyDataMinMaxType == 'custom')
929+
self.minLabel.setVisible(show_custom)
930+
self.minBox.setVisible(show_custom)
931+
self.maxLabel.setVisible(show_custom)
932+
self.maxBox.setVisible(show_custom)
921933

922934
def updateMinMax(self):
923935
cv = self.model.currentView
924936
self.minBox.setValue(cv.tallyDataMin)
925937
self.maxBox.setValue(cv.tallyDataMax)
926-
self.setMinMaxEnabled(cv.tallyDataUserMinMax)
938+
self.updateMinMaxType()
927939

928940
def updateTallyVisibility(self):
929941
cv = self.model.currentView
@@ -951,7 +963,6 @@ def update(self):
951963

952964
self.alphaBox.setValue(cv.tallyDataAlpha)
953965
self.visibilityBox.setChecked(cv.tallyDataVisible)
954-
self.userMinMaxBox.setChecked(cv.tallyDataUserMinMax)
955966
self.scaleBox.setChecked(cv.tallyDataLogScale)
956967

957968
self.updateMinMax()

openmc_plotter/main_window.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,10 +1037,29 @@ def toggleTallyDataClip(self, state):
10371037
av = self.model.activeView
10381038
av.clipTallyData = bool(state)
10391039

1040-
def toggleTallyDataUserMinMax(self, state, apply=False):
1040+
def setTallyMinMaxType(self, index, apply=False):
1041+
"""Set the min/max type for tally data.
1042+
1043+
Parameters
1044+
----------
1045+
index : int
1046+
Index of the selected option: 0='full', 1='visible', 2='custom'
1047+
apply : bool
1048+
Whether to apply changes immediately
1049+
"""
10411050
av = self.model.activeView
1042-
av.tallyDataUserMinMax = bool(state)
1043-
self.tallyDock.tallyColorForm.setMinMaxEnabled(bool(state))
1051+
type_map = {0: 'full', 1: 'visible', 2: 'custom'}
1052+
new_type = type_map.get(index, 'full')
1053+
av.tallyDataMinMaxType = new_type
1054+
1055+
# Immediately update visibility of min/max fields based on selection
1056+
show_custom = (new_type == 'custom')
1057+
form = self.tallyDock.tallyColorForm
1058+
form.minLabel.setVisible(show_custom)
1059+
form.minBox.setVisible(show_custom)
1060+
form.maxLabel.setVisible(show_custom)
1061+
form.maxBox.setVisible(show_custom)
1062+
10441063
if apply:
10451064
self.applyChanges()
10461065

openmc_plotter/plotgui.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ def updatePixmap(self):
567567
# draw tally image
568568
if image_data is not None:
569569

570-
if not cv.tallyDataUserMinMax:
570+
if cv.tallyDataMinMaxType != 'custom':
571571
cv.tallyDataMin = data_min
572572
cv.tallyDataMax = data_max
573573
else:
@@ -577,6 +577,9 @@ def updatePixmap(self):
577577
# always mask out negative values
578578
image_mask = image_data < 0.0
579579

580+
# mask out invalid values (NaN/Inf)
581+
image_mask |= ~np.isfinite(image_data)
582+
580583
if cv.clipTallyData:
581584
image_mask |= image_data < data_min
582585
image_mask |= image_data > data_max
@@ -587,6 +590,21 @@ def updatePixmap(self):
587590
# mask out invalid values
588591
image_data = np.ma.masked_where(image_mask, image_data)
589592

593+
# auto-rescale based on displayed (imshow) data
594+
if cv.tallyDataMinMaxType == 'visible' and not cv.tallyContours:
595+
displayed = image_data.compressed()
596+
if displayed.size:
597+
visible_min = float(displayed.min())
598+
visible_max = float(displayed.max())
599+
# Fall back to full data range if visible range is invalid for log scale
600+
if cv.tallyDataLogScale and visible_min <= 0:
601+
pass # keep the full data range
602+
else:
603+
data_min = visible_min
604+
data_max = visible_max
605+
cv.tallyDataMin = data_min
606+
cv.tallyDataMax = data_max
607+
590608
if extents is None:
591609
extents = data_bounds
592610

openmc_plotter/plotmodel.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,8 @@ class PlotViewIndependent:
937937
Minimum scale value for tally data
938938
tallyDataLogScale : bool
939939
Indicator of logarithmic scale for tally data
940+
tallyDataMinMaxType : str
941+
Type of min/max scaling for tally data: 'full', 'visible', or 'custom'
940942
tallyMaskZeroValues : bool
941943
Indicates whether or not zero values in tally data should be masked
942944
clipTallyData: bool
@@ -980,7 +982,7 @@ def __init__(self):
980982
self.tallyDataVisible = True
981983
self.tallyDataAlpha = 1.0
982984
self.tallyDataIndicator = False
983-
self.tallyDataUserMinMax = False
985+
self.tallyDataMinMaxType = 'full' # 'full', 'visible', or 'custom'
984986
self.tallyDataMin = 0.0
985987
self.tallyDataMax = np.inf
986988
self.tallyDataLogScale = False
@@ -999,6 +1001,14 @@ def __setstate__(self, state):
9991001
self.outlinesCell = False
10001002
if not hasattr(self, 'outlinesMat'):
10011003
self.outlinesMat = False
1004+
# Migrate old boolean attributes to new tallyDataMinMaxType
1005+
if not hasattr(self, 'tallyDataMinMaxType'):
1006+
if getattr(self, 'tallyDataUserMinMax', False):
1007+
self.tallyDataMinMaxType = 'custom'
1008+
else:
1009+
self.tallyDataMinMaxType = 'full'
1010+
# Remove old attributes if present
1011+
self.__dict__.pop('tallyDataUserMinMax', None)
10021012

10031013
def getDataLimits(self):
10041014
return self.data_minmax

0 commit comments

Comments
 (0)