@@ -357,58 +357,98 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
357
357
out_height = int (out_height_base )
358
358
359
359
if not unsampled :
360
- created_rgba_mask = False
361
-
362
360
if A .ndim not in (2 , 3 ):
363
361
raise ValueError ("Invalid dimensions, got {}" .format (A .shape ))
364
362
365
363
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
364
+ # if we are a 2D array, then we are running through the
365
+ # norm + colormap transformation. However, in general the
366
+ # input data is not going to match the size on the screen so we
367
+ # have to resample to the correct number of pixels
368
+ # need to
369
+
370
+ # TODO slice input array first
371
+ inp_dtype = A .dtype
372
+ if inp_dtype .kind == 'f' :
373
+ scaled_dtype = A .dtype
398
374
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 :
375
+ scaled_dtype = np .float32
376
+ # old versions of numpy do not work with `np.nammin`
377
+ # and `np.nanmax` as inputs
378
+ a_min = np .ma .min (A )
379
+ a_max = np .ma .max (A )
380
+ # scale the input data to [.1, .9]. The Agg
381
+ # interpolators clip to [0, 1] internally, use a
382
+ # smaller input scale to identify which of the
383
+ # interpolated points need to be should be flagged as
384
+ # over / under.
385
+ # This may introduce numeric instabilities in very broadly
386
+ # scaled data
387
+ A_scaled = np .empty (A .shape , dtype = scaled_dtype )
388
+ A_scaled [:] = A
389
+ A_scaled -= a_min
390
+ A_scaled /= ((a_max - a_min ) / 0.8 )
391
+ A_scaled += 0.1
392
+ A_resampled = np .zeros ((out_height , out_width ),
393
+ dtype = A_scaled .dtype )
394
+ # resample the input data to the correct resolution and shape
395
+ _image .resample (A_scaled , A_resampled ,
396
+ t ,
397
+ _interpd_ [self .get_interpolation ()],
398
+ self .get_resample (), 1.0 ,
399
+ self .get_filternorm () or 0.0 ,
400
+ self .get_filterrad () or 0.0 )
401
+
402
+ # we are done with A_scaled now, remove from namespace
403
+ # to be sure!
404
+ del A_scaled
405
+ # un-scale the resampled data to approximately the
406
+ # original range things that interpolated to above /
407
+ # below the original min/max will still be above /
408
+ # below, but possibly clipped in the case of higher order
409
+ # interpolation + drastically changing data.
410
+ A_resampled -= 0.1
411
+ A_resampled *= ((a_max - a_min ) / 0.8 )
412
+ A_resampled += a_min
413
+ # if using NoNorm, cast back to the original datatype
414
+ if isinstance (self .norm , mcolors .NoNorm ):
415
+ A_resampled = A_resampled .astype (A .dtype )
416
+
417
+ mask = np .empty (A .shape , dtype = np .float32 )
418
+ if A .mask .shape == A .shape :
419
+ # this is the case of a nontrivial mask
420
+ mask [:] = np .where (A .mask , np .float32 (np .nan ),
421
+ np .float32 (1 ))
422
+ else :
423
+ mask [:] = 1
424
+
425
+ # we always have to interpolate the mask to account for
426
+ # non-affine transformations
427
+ out_mask = np .zeros ((out_height , out_width ),
428
+ dtype = mask .dtype )
429
+ _image .resample (mask , out_mask ,
430
+ t ,
431
+ _interpd_ [self .get_interpolation ()],
432
+ True , 1 ,
433
+ self .get_filternorm () or 0.0 ,
434
+ self .get_filterrad () or 0.0 )
435
+ # we are done with the mask, delete from namespace to be sure!
436
+ del mask
437
+ # Agg updates the out_mask in place. If the pixel has
438
+ # no image data it will not be updated (and still be 0
439
+ # as we initialized it), if input data that would go
440
+ # into that output pixel than it will be `nan`, if all
441
+ # the input data for a pixel is good it will be 1, and
442
+ # if there is _some_ good data in that output pixel it
443
+ # will be between [0, 1] (such as a rotated image).
444
+
445
+ out_alpha = np .array (out_mask )
446
+ out_mask = np .isnan (out_mask )
447
+ out_alpha [out_mask ] = 1
448
+
449
+ # mask and run through the norm
450
+ output = self .norm (np .ma .masked_array (A_resampled , out_mask ))
451
+ else :
412
452
# Always convert to RGBA, even if only RGB input
413
453
if A .shape [2 ] == 3 :
414
454
A = _rgb_to_rgba (A )
@@ -421,57 +461,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421
461
if alpha is None :
422
462
alpha = 1.0
423
463
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
464
+ _image .resample (
465
+ A , output , t , _interpd_ [self .get_interpolation ()],
466
+ self .get_resample (), alpha ,
467
+ self .get_filternorm () or 0.0 , self .get_filterrad () or 0.0 )
464
468
469
+ # at this point output is either a 2D array of normed data
470
+ # (of int or float)
471
+ # or an RGBA array of re-sampled input
465
472
output = self .to_rgba (output , bytes = True , norm = False )
473
+ # output is now a correctly sized RGBA array of uint8
466
474
467
475
# Apply alpha *after* if the input was greyscale without a mask
468
- if A .ndim == 2 or created_rgba_mask :
476
+ if A .ndim == 2 :
469
477
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 )
478
+ if alpha is None :
479
+ alpha = 1
480
+ alpha_channel = output [:, :, 3 ]
481
+ alpha_channel [:] = np .asarray (
482
+ np .asarray (alpha_channel , np .float32 ) * out_alpha * alpha ,
483
+ np .uint8 )
484
+
475
485
else :
476
486
if self ._imcache is None :
477
487
self ._imcache = self .to_rgba (A , bytes = True , norm = (A .ndim == 2 ))
0 commit comments