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