Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb1b9404

Browse files
authored
Merge pull request#8966 from tacaswell/fix_image_interpolation
Fix image interpolation
2 parents6c69f3e +1aebff1 commitb1b9404

File tree

46 files changed

+2349
-2702
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2349
-2702
lines changed

‎lib/matplotlib/cm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,4 @@ def changed(self):
375375

376376
forkeyinself.update_dict:
377377
self.update_dict[key]=True
378+
self.stale=True

‎lib/matplotlib/image.py

Lines changed: 105 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,6 @@ def _interpdr(self):
192192
defiterpnames(self):
193193
returninterpolations_names
194194

195-
defset_cmap(self,cmap):
196-
super(_ImageBase,self).set_cmap(cmap)
197-
self.stale=True
198-
199-
defset_norm(self,norm):
200-
super(_ImageBase,self).set_norm(norm)
201-
self.stale=True
202-
203195
def__str__(self):
204196
return"AxesImage(%g,%g;%gx%g)"%tuple(self.axes.bbox.bounds)
205197

@@ -357,58 +349,100 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
357349
out_height=int(out_height_base)
358350

359351
ifnotunsampled:
360-
created_rgba_mask=False
361-
362352
ifA.ndimnotin (2,3):
363353
raiseValueError("Invalid dimensions, got {}".format(A.shape))
364354

365355
ifA.ndim==2:
366-
A=self.norm(A)
367-
ifA.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-
withnp.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-
ifA.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+
ifinp_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+
ifa_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()or0.0,
393+
self.get_filterrad()or0.0)
394+
395+
# we are done with A_scaled now, remove from namespace
396+
# to be sure!
397+
delA_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+
ifa_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+
ifisinstance(self.norm,mcolors.NoNorm):
409+
A_resampled=A_resampled.astype(A.dtype)
410+
411+
mask=np.empty(A.shape,dtype=np.float32)
412+
ifA.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))
398416
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-
ifnotcreated_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()or0.0,
428+
self.get_filterrad()or0.0)
429+
# we are done with the mask, delete from namespace to be sure!
430+
delmask
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:
412446
# Always convert to RGBA, even if only RGB input
413447
ifA.shape[2]==3:
414448
A=_rgb_to_rgba(A)
@@ -421,57 +455,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
421455
ifalphaisNone:
422456
alpha=1.0
423457

424-
_image.resample(
425-
A,output,t,_interpd_[self.get_interpolation()],
426-
self.get_resample(),alpha,
427-
self.get_filternorm()or0.0,self.get_filterrad()or0.0)
428-
429-
ifcreated_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-
iflen(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()or0.0,
443-
self.get_filterrad()or0.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-
delout_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()or0.0,self.get_filterrad()or0.0)
464462

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
465466
output=self.to_rgba(output,bytes=True,norm=False)
467+
# output is now a correctly sized RGBA array of uint8
466468

467469
# Apply alpha *after* if the input was greyscale without a mask
468-
ifA.ndim==2orcreated_rgba_mask:
470+
ifA.ndim==2:
469471
alpha=self.get_alpha()
470-
ifalphaisnotNoneandalpha!=1.0:
471-
alpha_channel=output[:, :,3]
472-
alpha_channel[:]=np.asarray(
473-
np.asarray(alpha_channel,np.float32)*alpha,
474-
np.uint8)
472+
ifalphaisNone:
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+
475479
else:
476480
ifself._imcacheisNone:
477481
self._imcache=self.to_rgba(A,bytes=True,norm=(A.ndim==2))
Binary file not shown.
Loading

‎lib/matplotlib/tests/baseline_images/test_axes/imshow.svg

Lines changed: 56 additions & 116 deletions
Loading
Binary file not shown.

‎lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg

Lines changed: 407 additions & 471 deletions
Loading
Binary file not shown.

‎lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.svg

Lines changed: 60 additions & 130 deletions
Loading
Binary file not shown.

‎lib/matplotlib/tests/baseline_images/test_image/image_clip.svg

Lines changed: 254 additions & 135 deletions
Loading
Binary file not shown.

‎lib/matplotlib/tests/baseline_images/test_image/image_cliprect.svg

Lines changed: 76 additions & 142 deletions
Loading
Binary file not shown.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp