@@ -192,14 +192,6 @@ def _interpdr(self):
192
192
def iterpnames (self ):
193
193
return interpolations_names
194
194
195
- def set_cmap (self , cmap ):
196
- super (_ImageBase , self ).set_cmap (cmap )
197
- self .stale = True
198
-
199
- def set_norm (self , norm ):
200
- super (_ImageBase , self ).set_norm (norm )
201
- self .stale = True
202
-
203
195
def __str__ (self ):
204
196
return "AxesImage(%g,%g;%gx%g)" % tuple (self .axes .bbox .bounds )
205
197
@@ -357,58 +349,100 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
357
349
out_height = int (out_height_base )
358
350
359
351
if not unsampled :
360
- created_rgba_mask = False
361
-
362
352
if A .ndim not in (2 , 3 ):
363
353
raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
364
354
365
355
if A .ndim == 2 :
366
- A = self .norm (A )
367
- if A .dtype .kind == 'f' :
368
- # If the image is greyscale, convert to RGBA and
369
- # use the extra channels for resizing the over,
370
- # under, and bad pixels. This is needed because
371
- # Agg's resampler is very aggressive about
372
- # clipping to [0, 1] and we use out-of-bounds
373
- # values to carry the over/under/bad information
374
- rgba = np .empty ((A .shape [0 ], A .shape [1 ], 4 ), dtype = A .dtype )
375
- rgba [..., 0 ] = A # normalized data
376
- # this is to work around spurious warnings coming
377
- # out of masked arrays.
378
- with np .errstate (invalid = 'ignore' ):
379
- rgba [..., 1 ] = np .where (A < 0 , np .nan , 1 ) # under data
380
- rgba [..., 2 ] = np .where (A > 1 , np .nan , 1 ) # over data
381
- # Have to invert mask, Agg knows what alpha means
382
- # so if you put this in as 0 for 'good' points, they
383
- # all get zeroed out
384
- rgba [..., 3 ] = 1
385
- if A .mask .shape == A .shape :
386
- # this is the case of a nontrivial mask
387
- mask = np .where (A .mask , np .nan , 1 )
388
- else :
389
- # this is the case that the mask is a
390
- # numpy.bool_ of False
391
- mask = A .mask
392
- # ~A.mask # masked data
393
- A = rgba
394
- output = np .zeros ((out_height , out_width , 4 ),
395
- dtype = A .dtype )
396
- alpha = 1.0
397
- created_rgba_mask = True
356
+ # if we are a 2D array, then we are running through the
357
+ # norm + colormap transformation. However, in general the
358
+ # input data is not going to match the size on the screen so we
359
+ # have to resample to the correct number of pixels
360
+ # need to
361
+
362
+ # TODO slice input array first
363
+ inp_dtype = A .dtype
364
+ if inp_dtype .kind == 'f' :
365
+ scaled_dtype = A .dtype
366
+ else :
367
+ scaled_dtype = np .float32
368
+ # old versions of numpy do not work with `np.nammin`
369
+ # and `np.nanmax` as inputs
370
+ a_min = np .ma .min (A )
371
+ a_max = np .ma .max (A )
372
+ # scale the input data to [.1, .9]. The Agg
373
+ # interpolators clip to [0, 1] internally, use a
374
+ # smaller input scale to identify which of the
375
+ # interpolated points need to be should be flagged as
376
+ # over / under.
377
+ # This may introduce numeric instabilities in very broadly
378
+ # scaled data
379
+ A_scaled = np .empty (A .shape , dtype = scaled_dtype )
380
+ A_scaled [:] = A
381
+ A_scaled -= a_min
382
+ if a_min != a_max :
383
+ A_scaled /= ((a_max - a_min ) / 0.8 )
384
+ A_scaled += 0.1
385
+ A_resampled = np .zeros ((out_height , out_width ),
386
+ dtype = A_scaled .dtype )
387
+ # resample the input data to the correct resolution and shape
388
+ _image .resample (A_scaled , A_resampled ,
389
+ t ,
390
+ _interpd_ [self .get_interpolation ()],
391
+ self .get_resample (), 1.0 ,
392
+ self .get_filternorm () or 0.0 ,
393
+ self .get_filterrad () or 0.0 )
394
+
395
+ # we are done with A_scaled now, remove from namespace
396
+ # to be sure!
397
+ del A_scaled
398
+ # un-scale the resampled data to approximately the
399
+ # original range things that interpolated to above /
400
+ # below the original min/max will still be above /
401
+ # below, but possibly clipped in the case of higher order
402
+ # interpolation + drastically changing data.
403
+ A_resampled -= 0.1
404
+ if a_min != a_max :
405
+ A_resampled *= ((a_max - a_min ) / 0.8 )
406
+ A_resampled += a_min
407
+ # if using NoNorm, cast back to the original datatype
408
+ if isinstance (self .norm , mcolors .NoNorm ):
409
+ A_resampled = A_resampled .astype (A .dtype )
410
+
411
+ mask = np .empty (A .shape , dtype = np .float32 )
412
+ if A .mask .shape == A .shape :
413
+ # this is the case of a nontrivial mask
414
+ mask [:] = np .where (A .mask , np .float32 (np .nan ),
415
+ np .float32 (1 ))
398
416
else :
399
- # colormap norms that output integers (ex NoNorm
400
- # and BoundaryNorm) to RGBA space before
401
- # interpolating. This is needed due to the
402
- # Agg resampler only working on floats in the
403
- # range [0, 1] and because interpolating indexes
404
- # into an arbitrary LUT may be problematic.
405
- #
406
- # This falls back to interpolating in RGBA space which
407
- # can produce it's own artifacts of colors not in the map
408
- # showing up in the final image.
409
- A = self .cmap (A , alpha = self .get_alpha (), bytes = True )
410
-
411
- if not created_rgba_mask :
417
+ mask [:] = 1
418
+
419
+ # we always have to interpolate the mask to account for
420
+ # non-affine transformations
421
+ out_mask = np .zeros ((out_height , out_width ),
422
+ dtype = mask .dtype )
423
+ _image .resample (mask , out_mask ,
424
+ t ,
425
+ _interpd_ [self .get_interpolation ()],
426
+ True , 1 ,
427
+ self .get_filternorm () or 0.0 ,
428
+ self .get_filterrad () or 0.0 )
429
+ # we are done with the mask, delete from namespace to be sure!
430
+ del mask
431
+ # Agg updates the out_mask in place. If the pixel has
432
+ # no image data it will not be updated (and still be 0
433
+ # as we initialized it), if input data that would go
434
+ # into that output pixel than it will be `nan`, if all
435
+ # the input data for a pixel is good it will be 1, and
436
+ # if there is _some_ good data in that output pixel it
437
+ # will be between [0, 1] (such as a rotated image).
438
+
439
+ out_alpha = np .array (out_mask )
440
+ out_mask = np .isnan (out_mask )
441
+ out_alpha [out_mask ] = 1
442
+
443
+ # mask and run through the norm
444
+ output = self .norm (np .ma .masked_array (A_resampled , out_mask ))
445
+ else :
412
446
# Always convert to RGBA, even if only RGB input
413
447
if A .shape [2 ] == 3 :
414
448
A = _rgb_to_rgba (A )
@@ -421,57 +455,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421
455
if alpha is None :
422
456
alpha = 1.0
423
457
424
- _image .resample (
425
- A , output , t , _interpd_ [self .get_interpolation ()],
426
- self .get_resample (), alpha ,
427
- self .get_filternorm () or 0.0 , self .get_filterrad () or 0.0 )
428
-
429
- if created_rgba_mask :
430
- # Convert back to a masked greyscale array so
431
- # colormapping works correctly
432
- hid_output = output
433
- # any pixel where the a masked pixel is included
434
- # in the kernel (pulling this down from 1) needs to
435
- # be masked in the output
436
- if len (mask .shape ) == 2 :
437
- out_mask = np .empty ((out_height , out_width ),
438
- dtype = mask .dtype )
439
- _image .resample (mask , out_mask , t ,
440
- _interpd_ [self .get_interpolation ()],
441
- True , 1 ,
442
- self .get_filternorm () or 0.0 ,
443
- self .get_filterrad () or 0.0 )
444
- out_mask = np .isnan (out_mask )
445
- else :
446
- out_mask = mask
447
- # we need to mask both pixels which came in as masked
448
- # and the pixels that Agg is telling us to ignore (relavent
449
- # to non-affine transforms)
450
- # Use half alpha as the threshold for pixels to mask.
451
- out_mask = out_mask | (hid_output [..., 3 ] < .5 )
452
- output = np .ma .masked_array (
453
- hid_output [..., 0 ],
454
- out_mask )
455
- # 'unshare' the mask array to
456
- # needed to suppress numpy warning
457
- del out_mask
458
- invalid_mask = ~ output .mask * ~ np .isnan (output .data )
459
- # relabel under data. If any of the input data for
460
- # the pixel has input out of the norm bounds,
461
- output [np .isnan (hid_output [..., 1 ]) * invalid_mask ] = - 1
462
- # relabel over data
463
- output [np .isnan (hid_output [..., 2 ]) * invalid_mask ] = 2
458
+ _image .resample (
459
+ A , output , t , _interpd_ [self .get_interpolation ()],
460
+ self .get_resample (), alpha ,
461
+ self .get_filternorm () or 0.0 , self .get_filterrad () or 0.0 )
464
462
463
+ # at this point output is either a 2D array of normed data
464
+ # (of int or float)
465
+ # or an RGBA array of re-sampled input
465
466
output = self .to_rgba (output , bytes = True , norm = False )
467
+ # output is now a correctly sized RGBA array of uint8
466
468
467
469
# Apply alpha *after* if the input was greyscale without a mask
468
- if A .ndim == 2 or created_rgba_mask :
470
+ if A .ndim == 2 :
469
471
alpha = self .get_alpha ()
470
- if alpha is not None and alpha != 1.0 :
471
- alpha_channel = output [:, :, 3 ]
472
- alpha_channel [:] = np .asarray (
473
- np .asarray (alpha_channel , np .float32 ) * alpha ,
474
- np .uint8 )
472
+ if alpha is None :
473
+ alpha = 1
474
+ alpha_channel = output [:, :, 3 ]
475
+ alpha_channel [:] = np .asarray (
476
+ np .asarray (alpha_channel , np .float32 ) * out_alpha * alpha ,
477
+ np .uint8 )
478
+
475
479
else :
476
480
if self ._imcache is None :
477
481
self ._imcache = self .to_rgba (A , bytes = True , norm = (A .ndim == 2 ))
0 commit comments