3
3
from contextlib import suppress
4
4
from typing import TYPE_CHECKING
5
5
6
- from ndv .models ._lut_model import ClimsManual , ClimsMinMax , ClimsType
7
-
8
6
if TYPE_CHECKING :
9
7
from collections .abc import Iterable , Sequence
10
8
11
- import cmap
12
9
import numpy as np
13
10
14
11
from ndv .models ._lut_model import LUTModel
@@ -27,113 +24,33 @@ class ChannelController:
27
24
that displays the data, all for a single "channel" extracted from the data.
28
25
"""
29
26
30
- def __init__ (self , key : LutKey , model : LUTModel , views : Sequence [LutView ]) -> None :
27
+ def __init__ (
28
+ self , key : LutKey , lut_model : LUTModel , views : Sequence [LutView ]
29
+ ) -> None :
31
30
self .key = key
32
31
self .lut_views : list [LutView ] = []
33
- self .lut_model = model
32
+ self .lut_model = lut_model
33
+ self .lut_model .events .clims .connect (self ._auto_scale )
34
34
self .handles : list [ImageHandle ] = []
35
35
36
36
for v in views :
37
37
self .add_lut_view (v )
38
38
39
- # connect model changes to view callbacks that update the view
40
- self .lut_model .events .cmap .connect (self ._on_model_cmap_changed )
41
- self .lut_model .events .clims .connect (self ._on_model_clims_changed )
42
- self .lut_model .events .visible .connect (self ._on_model_visible_changed )
43
- self .lut_model .events .gamma .connect (self ._on_model_gamma_changed )
44
-
45
39
def add_lut_view (self , view : LutView ) -> None :
46
40
"""Add a LUT view to the controller."""
41
+ view .model = self .lut_model
47
42
self .lut_views .append (view )
48
- # connect view changes to controller callbacks that update the model
49
- view .visibilityChanged .connect (self ._on_view_lut_visible_changed )
50
- view .autoscaleChanged .connect (self ._on_view_lut_autoscale_changed )
51
- view .cmapChanged .connect (self ._on_view_lut_cmap_changed )
52
- view .climsChanged .connect (self ._on_view_lut_clims_changed )
53
- view .gammaChanged .connect (self ._on_view_lut_gamma_changed )
54
- self ._update_view_from_model (view )
55
-
56
- def _on_model_clims_changed (self , clims : ClimsType ) -> None :
57
- """The contrast limits in the model have changed."""
58
- is_autoscale = not clims .is_manual
59
- for handle in self .handles :
60
- min_max = clims .calc_clims (handle .data ())
61
- handle .set_clims (min_max )
62
- for v in self .lut_views :
63
- v .set_clims_without_signal (min_max )
64
- v .set_auto_scale_without_signal (is_autoscale )
65
-
66
- def _on_model_gamma_changed (self , gamma : float ) -> None :
67
- """The gamma value in the model has changed."""
68
- for view in self .lut_views :
69
- view .set_gamma_without_signal (gamma )
70
- for handle in self .handles :
71
- handle .set_gamma (gamma )
72
-
73
- def _on_model_cmap_changed (self , cmap : cmap .Colormap ) -> None :
74
- """The colormap in the model has changed."""
75
- for view in self .lut_views :
76
- view .set_colormap_without_signal (cmap )
77
- for handle in self .handles :
78
- handle .set_cmap (cmap )
79
-
80
- def _on_model_visible_changed (self , visible : bool ) -> None :
81
- """The visibility in the model has changed."""
82
- for view in self .lut_views :
83
- view .set_channel_visible_without_signal (visible )
84
- for handle in self .handles :
85
- handle .set_visible (visible )
86
-
87
- def _update_view_from_model (self , * views : LutView ) -> None :
88
- """Make sure the view matches the model."""
43
+ # TODO: Could probably reuse cached clims
44
+ self ._auto_scale ()
45
+
46
+ def synchronize (self , * views : LutView ) -> None :
47
+ """Aligns all views against the backing model."""
89
48
_views : Iterable [LutView ] = views or self .lut_views
49
+ name = str (self .key ) if self .key is not None else ""
90
50
for view in _views :
91
- view .set_colormap_without_signal (self .lut_model .cmap )
92
- if self .lut_model .clims and (clims := self .lut_model .clims .cached_clims ):
93
- view .set_clims_without_signal (clims )
94
-
95
- is_autoscale = not self .lut_model .clims .is_manual
96
- view .set_auto_scale_without_signal (is_autoscale )
97
- view .set_channel_visible_without_signal (True )
98
- name = str (self .key ) if self .key is not None else ""
51
+ view .synchronize ()
99
52
view .set_channel_name (name )
100
53
101
- def _on_view_lut_visible_changed (self , visible : bool , key : LutKey = None ) -> None :
102
- """The visibility checkbox in the LUT widget has changed."""
103
- for handle in self .handles :
104
- previous = handle .visible ()
105
- handle .set_visible (visible )
106
- if previous != visible :
107
- self ._update_clims (handle )
108
-
109
- def _on_view_lut_autoscale_changed (
110
- self , autoscale : bool , key : LutKey = None
111
- ) -> None :
112
- """The autoscale checkbox in the LUT widget has changed."""
113
- if autoscale :
114
- self .lut_model .clims = ClimsMinMax ()
115
- elif cached := self .lut_model .clims .cached_clims :
116
- self .lut_model .clims = ClimsManual (min = cached [0 ], max = cached [1 ])
117
-
118
- for view in self .lut_views :
119
- view .set_auto_scale_without_signal (autoscale )
120
-
121
- def _on_view_lut_cmap_changed (
122
- self , cmap : cmap .Colormap , key : LutKey = None
123
- ) -> None :
124
- """The colormap in the LUT widget has changed."""
125
- for handle in self .handles :
126
- handle .set_cmap (cmap ) # actually apply it to the Image texture
127
- self .lut_model .cmap = cmap # update the model as well
128
-
129
- def _on_view_lut_clims_changed (self , clims : tuple [float , float ]) -> None :
130
- """The contrast limits slider in the LUT widget has changed."""
131
- self .lut_model .clims = ClimsManual (min = clims [0 ], max = clims [1 ])
132
-
133
- def _on_view_lut_gamma_changed (self , gamma : float ) -> None :
134
- """The gamma slider in the LUT widget has changed."""
135
- self .lut_model .gamma = gamma
136
-
137
54
def update_texture_data (self , data : np .ndarray ) -> None :
138
55
"""Update the data in the image handle."""
139
56
# WIP:
@@ -143,20 +60,12 @@ def update_texture_data(self, data: np.ndarray) -> None:
143
60
return
144
61
handle = handles [0 ]
145
62
handle .set_data (data )
146
- if handle .visible ():
147
- self ._update_clims (handle )
63
+ self ._auto_scale ()
148
64
149
65
def add_handle (self , handle : ImageHandle ) -> None :
150
66
"""Add an image texture handle to the controller."""
151
67
self .handles .append (handle )
152
- handle .set_cmap (self .lut_model .cmap )
153
- self ._update_clims (handle )
154
-
155
- def _update_clims (self , handle : ImageHandle ) -> None :
156
- min_max = self .lut_model .clims .calc_clims (handle .data ())
157
- handle .set_clims (min_max )
158
- for view in self .lut_views :
159
- view .set_clims_without_signal (min_max )
68
+ self .add_lut_view (handle )
160
69
161
70
def get_value_at_index (self , idx : tuple [int , ...]) -> float | None :
162
71
"""Get the value of the data at the given index."""
@@ -174,3 +83,15 @@ def get_value_at_index(self, idx: tuple[int, ...]) -> float | None:
174
83
# the data source directly.
175
84
return handle .data ()[idx ] # type: ignore [no-any-return]
176
85
return None
86
+
87
+ def _auto_scale (self ) -> None :
88
+ if self .lut_model and len (self .handles ):
89
+ policy = self .lut_model .clims
90
+ handle_clims = [policy .calc_clims (handle .data ()) for handle in self .handles ]
91
+ mi , ma = handle_clims [0 ]
92
+ for clims in handle_clims [1 :]:
93
+ mi = min (mi , clims [0 ])
94
+ ma = max (ma , clims [1 ])
95
+
96
+ for view in self .lut_views :
97
+ view .set_clims ((mi , ma ))
0 commit comments