@@ -260,6 +260,32 @@ def query(self, graph, parent_coordinates="axes"):
260260 # TODO hash
261261
262262
263+ @dataclass
264+ class NonUniformImageContainer (ImageContainer ):
265+ def describe (self ):
266+ imshape = list (self .image .shape )
267+ imshape [:2 ] = ("M" , "N" )
268+
269+ return {
270+ "x" : Desc (("M" ,), "data" ),
271+ "y" : Desc (("N" ,), "data" ),
272+ "image" : Desc (tuple (imshape ), "data" ),
273+ }
274+
275+
276+ @dataclass
277+ class PcolorImageContainer (ImageContainer ):
278+ def describe (self ):
279+ imshape = list (self .image .shape )
280+ imshape [:2 ] = ("M" , "N" )
281+
282+ return {
283+ "x" : Desc (("M+1" ,), "data" ),
284+ "y" : Desc (("N+1" ,), "data" ),
285+ "image" : Desc (tuple (imshape ), "data" ),
286+ }
287+
288+
263289class _ImageBase (mcolorizer .ColorizingArtist ):
264290 """
265291 Base class for images.
@@ -333,6 +359,8 @@ def get_container(self):
333359 def _get_graph (self ):
334360 # TODO see about getting rid of self.axes
335361 ax = self .axes
362+ if ax is None :
363+ return Graph ([])
336364 desc : Desc = Desc (("N" ,), coordinates = "data" )
337365 xy : dict [str , Desc ] = {"x" : desc , "y" : desc }
338366 implicit_graph = Graph (
@@ -1038,7 +1066,7 @@ def set_extent(self, extent, **kwargs):
10381066 will redo the autoscaling in accord with `~.Axes.dataLim`.
10391067 """
10401068 if not isinstance (self ._container , ImageContainer ):
1041- raise TypeError ("Cannot use 'set_data ' on custom container types" )
1069+ raise TypeError ("Cannot use 'set_extent ' on custom container types" )
10421070
10431071 if extent is None :
10441072 sz = self .get_size ()
@@ -1145,18 +1173,27 @@ def __init__(self, ax, *, interpolation='nearest', **kwargs):
11451173 )
11461174 super ().__init__ (ax , ** kwargs )
11471175 self .set_interpolation (interpolation )
1176+ self ._container = NonUniformImageContainer (
1177+ np .array ([0. ,1. ]),
1178+ np .array ([0. ,1. ]),
1179+ np .array ([[np .nan ]]),
1180+ )
11481181
11491182 def _check_unsampled_image (self ):
11501183 """Return False. Do not use unsampled image."""
11511184 return False
11521185
11531186 def make_image (self , renderer , magnification = 1.0 , unsampled = False ):
11541187 # docstring inherited
1155- if self ._A is None :
1156- raise RuntimeError ('You must first set the image array' )
11571188 if unsampled :
11581189 raise ValueError ('unsampled not supported on NonUniformImage' )
1159- A = self ._A
1190+
1191+ q , _ = self ._container .query (self ._get_graph ())
1192+ Ax = q ["x" ]
1193+ Ay = q ["y" ]
1194+
1195+ A = q ["image" ]
1196+
11601197 if A .ndim == 2 :
11611198 if A .dtype != np .uint8 :
11621199 A = self .to_rgba (A , bytes = True )
@@ -1182,8 +1219,8 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
11821219 [(l , y ) for y in np .linspace (b , t , height )])[:, 1 ]
11831220
11841221 if self ._interpolation == "nearest" :
1185- x_mid = (self . _Ax [:- 1 ] + self . _Ax [1 :]) / 2
1186- y_mid = (self . _Ay [:- 1 ] + self . _Ay [1 :]) / 2
1222+ x_mid = (Ax [:- 1 ] + Ax [1 :]) / 2
1223+ y_mid = (Ay [:- 1 ] + Ay [1 :]) / 2
11871224 x_int = x_mid .searchsorted (x_pix )
11881225 y_int = y_mid .searchsorted (y_pix )
11891226 # The following is equal to `A[y_int[:, None], x_int[None, :]]`,
@@ -1196,16 +1233,16 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
11961233 else : # self._interpolation == "bilinear"
11971234 # Use np.interp to compute x_int/x_float has similar speed.
11981235 x_int = np .clip (
1199- self . _Ax . searchsorted (x_pix ) - 1 , 0 , len (self . _Ax ) - 2 )
1236+ Ax . searchsorted (x_pix ) - 1 , 0 , len (Ax ) - 2 )
12001237 y_int = np .clip (
1201- self . _Ay . searchsorted (y_pix ) - 1 , 0 , len (self . _Ay ) - 2 )
1238+ Ay . searchsorted (y_pix ) - 1 , 0 , len (Ay ) - 2 )
12021239 idx_int = np .add .outer (y_int * A .shape [1 ], x_int )
12031240 x_frac = np .clip (
1204- np .divide (x_pix - self . _Ax [x_int ], np .diff (self . _Ax )[x_int ],
1241+ np .divide (x_pix - Ax [x_int ], np .diff (Ax )[x_int ],
12051242 dtype = np .float32 ), # Downcasting helps with speed.
12061243 0 , 1 )
12071244 y_frac = np .clip (
1208- np .divide (y_pix - self . _Ay [y_int ], np .diff (self . _Ay )[y_int ],
1245+ np .divide (y_pix - Ay [y_int ], np .diff (Ay )[y_int ],
12091246 dtype = np .float32 ),
12101247 0 , 1 )
12111248 f00 = np .outer (1 - y_frac , 1 - x_frac )
@@ -1237,14 +1274,16 @@ def set_data(self, x, y, A):
12371274 (M, N) `~numpy.ndarray` or masked array of values to be
12381275 colormapped, or (M, N, 3) RGB array, or (M, N, 4) RGBA array.
12391276 """
1277+ if not isinstance (self ._container , NonUniformImageContainer ):
1278+ raise TypeError ("Cannot use 'set_data' on custom container types" )
12401279 A = self ._normalize_image_array (A )
12411280 x = np .array (x , np .float32 )
12421281 y = np .array (y , np .float32 )
12431282 if not (x .ndim == y .ndim == 1 and A .shape [:2 ] == y .shape + x .shape ):
12441283 raise TypeError ("Axes don't match array shape" )
1245- self ._A = A
1246- self ._Ax = x
1247- self ._Ay = y
1284+ self ._container . image = A
1285+ self ._container . x = x
1286+ self ._container . y = y
12481287 self ._imcache = None
12491288 self .stale = True
12501289
@@ -1263,36 +1302,36 @@ def set_interpolation(self, s):
12631302 'bilinear interpolations are supported' )
12641303 super ().set_interpolation (s )
12651304
1266- def get_extent (self ):
1267- if self ._A is None :
1268- raise RuntimeError ('Must set data first' )
1269- return self ._Ax [0 ], self ._Ax [- 1 ], self ._Ay [0 ], self ._Ay [- 1 ]
1270-
12711305 def set_filternorm (self , filternorm ):
12721306 pass
12731307
12741308 def set_filterrad (self , filterrad ):
12751309 pass
12761310
12771311 def set_norm (self , norm ):
1278- if self ._A is not None :
1279- raise RuntimeError ('Cannot change colors after loading data' )
1312+ # if self._A is not None:
1313+ # raise RuntimeError('Cannot change colors after loading data')
12801314 super ().set_norm (norm )
12811315
12821316 def set_cmap (self , cmap ):
1283- if self ._A is not None :
1284- raise RuntimeError ('Cannot change colors after loading data' )
1317+ # if self._A is not None:
1318+ # raise RuntimeError('Cannot change colors after loading data')
12851319 super ().set_cmap (cmap )
12861320
12871321 def get_cursor_data (self , event ):
12881322 # docstring inherited
1323+ q , _ = self ._container .query (self ._get_graph ())
1324+ Ax = q ["x" ]
1325+ Ay = q ["y" ]
1326+ A = q ["image" ]
1327+
12891328 x , y = event .xdata , event .ydata
1290- if (x < self . _Ax [0 ] or x > self . _Ax [- 1 ] or
1291- y < self . _Ay [0 ] or y > self . _Ay [- 1 ]):
1329+ if (x < Ax [0 ] or x > Ax [- 1 ] or
1330+ y < Ay [0 ] or y > Ay [- 1 ]):
12921331 return None
1293- j = np .searchsorted (self . _Ax , x ) - 1
1294- i = np .searchsorted (self . _Ay , y ) - 1
1295- return self . _A [i , j ]
1332+ j = np .searchsorted (Ax , x ) - 1
1333+ i = np .searchsorted (Ay , y ) - 1
1334+ return A [i , j ]
12961335
12971336
12981337class PcolorImage (AxesImage ):
@@ -1339,18 +1378,27 @@ def __init__(self, ax,
13391378 """
13401379 super ().__init__ (ax , norm = norm , cmap = cmap , colorizer = colorizer )
13411380 self ._internal_update (kwargs )
1381+ self ._container = PcolorImageContainer (
1382+ np .array ([0. ,1. ]),
1383+ np .array ([0. ,1. ]),
1384+ np .array ([[np .nan ]]),
1385+ )
13421386 if A is not None :
13431387 self .set_data (x , y , A )
13441388
13451389 def make_image (self , renderer , magnification = 1.0 , unsampled = False ):
13461390 # docstring inherited
1347- if self ._A is None :
1348- raise RuntimeError ('You must first set the image array' )
13491391 if unsampled :
1350- raise ValueError ('unsampled not supported on PColorImage' )
1392+ raise ValueError ('unsampled not supported on PcolorImage' )
1393+
1394+ q , _ = self ._container .query (self ._get_graph ())
1395+ Ax = q ["x" ]
1396+ Ay = q ["y" ]
1397+
1398+ A = q ["image" ]
13511399
13521400 if self ._imcache is None :
1353- A = self .to_rgba (self . _A , bytes = True )
1401+ A = self .to_rgba (A , bytes = True )
13541402 self ._imcache = np .pad (A , [(1 , 1 ), (1 , 1 ), (0 , 0 )], "constant" )
13551403 padded_A = self ._imcache
13561404 bg = mcolors .to_rgba (self .axes .patch .get_facecolor (), 0 )
@@ -1367,8 +1415,8 @@ def make_image(self, renderer, magnification=1.0, unsampled=False):
13671415
13681416 x_pix = np .linspace (vl .x0 , vl .x1 , width )
13691417 y_pix = np .linspace (vl .y0 , vl .y1 , height )
1370- x_int = self . _Ax .searchsorted (x_pix )
1371- y_int = self . _Ay .searchsorted (y_pix )
1418+ x_int = Ax .searchsorted (x_pix )
1419+ y_int = Ay .searchsorted (y_pix )
13721420 im = ( # See comment in NonUniformImage.make_image re: performance.
13731421 padded_A .view (np .uint32 ).ravel ()[
13741422 np .add .outer (y_int * padded_A .shape [1 ], x_int )]
@@ -1396,6 +1444,8 @@ def set_data(self, x, y, A):
13961444 - (M, N, 3): RGB array
13971445 - (M, N, 4): RGBA array
13981446 """
1447+ if not isinstance (self ._container , PcolorImageContainer ):
1448+ raise TypeError ("Cannot use 'set_data' on custom container types" )
13991449 A = self ._normalize_image_array (A )
14001450 x = np .arange (0. , A .shape [1 ] + 1 ) if x is None else np .array (x , float ).ravel ()
14011451 y = np .arange (0. , A .shape [0 ] + 1 ) if y is None else np .array (y , float ).ravel ()
@@ -1410,9 +1460,9 @@ def set_data(self, x, y, A):
14101460 if y [- 1 ] < y [0 ]:
14111461 y = y [::- 1 ]
14121462 A = A [::- 1 ]
1413- self ._A = A
1414- self ._Ax = x
1415- self ._Ay = y
1463+ self ._container . image = A
1464+ self ._container . x = x
1465+ self ._container . y = y
14161466 self ._imcache = None
14171467 self .stale = True
14181468
@@ -1421,13 +1471,18 @@ def set_array(self, *args):
14211471
14221472 def get_cursor_data (self , event ):
14231473 # docstring inherited
1474+ q , _ = self ._container .query (self ._get_graph ())
1475+ Ax = q ["x" ]
1476+ Ay = q ["y" ]
1477+ A = q ["image" ]
1478+
14241479 x , y = event .xdata , event .ydata
1425- if (x < self . _Ax [0 ] or x > self . _Ax [- 1 ] or
1426- y < self . _Ay [0 ] or y > self . _Ay [- 1 ]):
1480+ if (x < Ax [0 ] or x > Ax [- 1 ] or
1481+ y < Ay [0 ] or y > Ay [- 1 ]):
14271482 return None
1428- j = np .searchsorted (self . _Ax , x ) - 1
1429- i = np .searchsorted (self . _Ay , y ) - 1
1430- return self . _A [i , j ]
1483+ j = np .searchsorted (Ax , x ) - 1
1484+ i = np .searchsorted (Ay , y ) - 1
1485+ return A [i , j ]
14311486
14321487
14331488class FigureImage (_ImageBase ):
0 commit comments