# NIST Public License - 2019
#
# This software was developed by employees of the National Institute of
# Standards and Technology (NIST), an agency of the Federal Government
# and is being made available as a public service. Pursuant to title 17
# United States Code Section 105, works of NIST employees are not subject
# to copyright protection in the United States. This software may be
# subject to foreign copyright. Permission in the United States and in
# foreign countries, to the extent that NIST may hold copyright, to use,
# copy, modify, create derivative works, and distribute this software and
# its documentation without fee is hereby granted on a non-exclusive basis,
# provided that this notice and disclaimer of warranty appears in all copies.
#
# THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND,
# EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
# TO, ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY
# IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
# AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION
# WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE
# ERROR FREE. IN NO EVENT SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING,
# BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES,
# ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THIS SOFTWARE,
# WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT, OR OTHERWISE, WHETHER
# OR NOT INJURY WAS SUSTAINED BY PERSONS OR PROPERTY OR OTHERWISE, AND
# WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT OF THE RESULTS OF,
# OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
#
import numpy as _np
import tempfile as _tmp
import hyperspy.api as _hsapi
from hyperspy.drawing.marker import dict2marker as _dict2marker
import os as _os
import textwrap as _textwrap
import matplotlib as _mpl
from skimage.io import imread as _imread
import skimage.transform as _tform
from skimage.transform import resize as _resize
import matplotlib.pyplot as _plt
from matplotlib.offsetbox import AnchoredOffsetbox as _AOb
from matplotlib.offsetbox import OffsetImage as _OIm
from matplotlib.transforms import Bbox as _Bbox
from PIL import Image as _PILImage
from PIL.Image import LANCZOS as _LANCZOS
import logging as _logging
_logger = _logging.getLogger(__name__)
_logger.setLevel(_logging.INFO)
_dir_path = _os.path.dirname(_os.path.realpath(__file__))
_mpl.use('Agg')
def _full_extent(ax, items, pad=0.0):
"""Get the full extent of items in an axis.
Adapted from https://stackoverflow.com/a/26432947/1435788
"""
# For text objects, we need to draw the figure first, otherwise the extents
# are undefined.
ax.figure.canvas.draw()
bbox = _Bbox.union([item.get_window_extent() for item in items])
return bbox.expanded(1.0 + pad, 1.0 + pad)
def _set_title(ax, title):
"""
Set an axis title, making sure it is no wider than 60 characters (so it
doesn't run over the edges of the plot)
Parameters
----------
ax : :py:mod:`matplotlib.axis`
A matplotlib axis instance on which to operate
title : str
The desired axis title
"""
new_title = _textwrap.fill(title, 60)
ax.set_title(new_title)
def _get_visible_labels(ax):
"""
Helper method to return only the tick labels that are visible given the
current extent of the axes. Useful when calculating the extent of the figure
to save so extra white space from invisible labels is not included.
Parameters
----------
ax : :py:mod:`matplotlib.axis`
A matplotlib axis instance on which to operate
Returns
-------
vis_labels_x, vis_labels_y : tuple of lists
lists of only the label objects that are visible on the current axis
"""
vis_labels_x = _mpl.cbook.silent_list('Text xticklabel')
vis_labels_y = _mpl.cbook.silent_list('Text yticklabel')
for l in ax.get_xticklabels():
label_pos = l.get_position()[0]
x_limits = ax.get_xlim()
if x_limits[0] < label_pos < x_limits[1]:
vis_labels_x.append(l)
for l in ax.get_yticklabels():
label_pos = l.get_position()[1]
y_limits = ax.get_ylim()
if y_limits[0] < label_pos < y_limits[1]:
vis_labels_y.append(l)
return vis_labels_x, vis_labels_y
def _project_image_stack(s, num=5, dpi=92, v_shear=0.3, h_scale=0.3):
"""
Create a preview of an image stack by selecting a number of example frames
and projecting them into a pseudo-3D display.
Parameters
----------
s : :py:class:`hyperspy.signal.BaseSignal` (or subclass)
The HyperSpy signal for which an image stack preview should be
generated. Should have a signal dimension of 2 and a navigation
dimension of 1.
num : int
The number of frames in the image stack to use to make the preview
dpi : int
The "dots per inch" of the individual frames within the preview
v_shear : float
The factor by which to vertically shear (0.5 means shear the top border
down by half of the original image's height)
h_scale : float
The factor by which to scale in the horizontal direction (0.3 means
each projected frame will be 30% the width of the original image)
Returns
-------
output : :py:class:`numpy.ndarray`
The `num` frames loaded into a single NumPy array for plotting
"""
shear = _np.array([[ 1, 0, 0],
[-1 * v_shear, 1, 0],
[ 0, 0, 1]])
scale = _np.array([[h_scale, 0, 0],
[ 0, 1, 0],
[ 0, 0, 1]])
trans_mat = _np.dot(shear, _np.linalg.inv(scale))
tmps = [''] * num
for idx, i in enumerate(
_np.linspace(0, s.axes_manager.navigation_size - 1, num=num,
dtype=int)):
_hsapi.plot.plot_images([s.inav[i].as_signal2D((0, 1))],
axes_decor='off', colorbar=False,
scalebar='all', label=None)
tmp = _tmp.NamedTemporaryFile()
ax = _plt.gca()
ax.set_position([0, 0, 1, 1])
ax.set_axis_on()
for axis in ['top', 'bottom', 'left', 'right']:
ax.spines[axis].set_linewidth(5)
ax.figure.canvas.draw()
ax.figure.savefig(tmp.name + '.png', dpi=dpi)
tmps[idx] = tmp
_plt.close(ax.figure)
im_data = [None] * num
for idx, tmp in enumerate(tmps):
img = _plt.imread(tmp.name + '.png')
img_trans = _tform.warp(img, trans_mat, order=1, preserve_range=True,
mode='constant', cval=_np.nan,
output_shape=(int(img.shape[1] * (1 + v_shear)),
int(img.shape[0] * h_scale)))
im_data[idx] = img_trans
for t in tmps:
t.close()
_os.remove(t.name + '.png')
output = _np.hstack(im_data)
return output
def _pad_to_square(im_path, new_width=500):
"""
Helper method to pad an image saved on disk to a square with size
``width x width``. This ensures consistent display on the front-end web
page. Increasing the size of a dimension is done by padding with empty
space. The original image is overwritten.
Method adapted from:
https://jdhao.github.io/2017/11/06/resize-image-to-square-with-padding/
Parameters
----------
im_path : str
The path to the image that should be resized/padded
new_width : int
Desired output width/height of the image (in pixels)
"""
im = _PILImage.open(im_path)
old_size = im.size # old_size[0] is in (width, height) format
ratio = float(new_width) / max(old_size)
new_size = tuple([int(x * ratio) for x in old_size])
im = im.resize(new_size, _PILImage.ANTIALIAS)
new_im = _PILImage.new("RGBA", (new_width, new_width))
new_im.paste(im, ((new_width - new_size[0]) // 2,
(new_width - new_size[1]) // 2))
new_im.save(im_path)
def _get_marker_color(annotation):
"""
Get the color of a DigitalMicrograph annotation
Parameters
----------
annotation : dict
The tag dictionary for a given annotation from a DigitalMicrograph
tag structure
Returns
-------
color : str or tuple
Either an RGB tuple, or string containing a color name
"""
if ('ForegroundColor' in annotation) or ('Color' in annotation):
# There seems to be 3 different colors in annotations in
# dm3-files: Color, ForegroundColor and BackgroundColor.
# ForegroundColor and BackgroundColor seems to be present
# for all annotations. Color is present in some of them.
# If Color is present, it seems to override the others.
# Currently, BackgroundColor is not utilized, due to
# HyperSpy markers only supporting a single color.
if 'Color' in annotation:
color_raw = annotation['Color']
else:
color_raw = annotation['ForegroundColor']
# Colors in DM are saved as negative values
# Some values are also in 16-bit
color = []
for raw_value in color_raw:
raw_value = abs(raw_value)
if raw_value > 1:
raw_value /= 2**16
color.append(raw_value)
color = tuple(color)
else:
color = 'red'
return color
def _get_marker_props(annotation):
"""
Get the properties of a DigitalMicrograph annotation
Parameters
----------
annotation : dict
The tag dictionary for a given annotation from a DigitalMicrograph
tag structure
Returns
-------
marker_properties : dict
A dictionary containing various properties for this
annotation/marker, such as line width, style, etc.
temp_dict : dict
A dictionary that contains the marker type
marker_text : None or str
If present, the text of a textual annotation
"""
marker_properties = {}
temp_dict = {}
marker_text = None
if 'AnnotationType' in annotation:
annotation_type = annotation['AnnotationType']
if annotation_type == 2:
temp_dict['marker_type'] = "LineSegment"
marker_properties['linewidth'] = 2
elif annotation_type == 3:
_logger.debug('Arrow marker not loaded: not implemented')
elif annotation_type == 4:
_logger.debug('Double arrow marker not loaded: not implemented')
elif annotation_type == 5:
temp_dict['marker_type'] = "Rectangle"
marker_properties['linewidth'] = 2
elif annotation_type == 6:
_logger.debug('Ellipse marker not loaded: not implemented')
elif annotation_type == 8:
_logger.debug('Mask spot marker not loaded: not implemented')
elif annotation_type == 9:
_logger.debug('Mask array marker not loaded: not implemented')
elif annotation_type == 13:
temp_dict['marker_type'] = "Text"
marker_text = annotation['Text']
elif annotation_type == 15:
_logger.debug(
'Mask band pass marker not loaded: not implemented')
elif annotation_type == 19:
_logger.debug(
'Mask wedge marker not loaded: not implemented')
elif annotation_type == 23: # roirectangle
temp_dict['marker_type'] = "Rectangle"
marker_properties['linestyle'] = '--'
marker_properties['linewidth'] = 2
elif annotation_type == 25: # roiline
temp_dict['marker_type'] = "LineSegment"
marker_properties['linestyle'] = '--'
marker_properties['linewidth'] = 2
elif annotation_type == 27:
temp_dict['marker_type'] = "Point"
elif annotation_type == 29:
_logger.debug(
'ROI curve marker not loaded: not implemented')
elif annotation_type == 31:
_logger.debug('Scalebar marker not loaded: not implemented')
return marker_properties, temp_dict, marker_text
def _get_markers_dict(s, tags_dict):
"""
Parameters
----------
s : :py:class:`hyperspy.signal.BaseSignal`
The HyperSpy signal from which annotations should be read
tags_dict : dict
The dictionary of DigitalMicrograph tags (saved as
``s.original_metadata``)
Returns
-------
markers_dict : dict
The Markers that correspond to the annotations found in `s`
"""
scale_y, scale_x = s.axes_manager['y'].scale, s.axes_manager['x'].scale
offset_y, offset_x = s.axes_manager['y'].offset, s.axes_manager['x'].offset
markers_dict = {}
annotations_dict = tags_dict[
'DocumentObjectList']['TagGroup0']['AnnotationGroupList']
for annotation in annotations_dict.values():
if 'Rectangle' in annotation:
position = annotation['Rectangle']
marker_properties, temp_dict, marker_text = \
_get_marker_props(annotation)
if 'marker_type' in temp_dict:
color = _get_marker_color(annotation)
if 'Label' in annotation:
# Some annotations contains an empty label, which are
# represented in the input dict as an empty list: []
if annotation['Label'] != []:
marker_label = annotation['Label']
label_marker_dict = {
'marker_type': "Text",
'plot_marker': True,
'plot_on_signal': True,
'axes_manager': s.axes_manager,
'data': {
'y1': position[0] * scale_y+offset_y,
'x1': position[1] * scale_x+offset_x,
'size': 20,
'text': marker_label,
},
'marker_properties': {
'color': color,
'va': 'bottom',
}
}
marker_name = "Text" + str(annotation['UniqueID'])
markers_dict[marker_name] = label_marker_dict
marker_properties['color'] = color
temp_dict['plot_on_signal'] = True,
temp_dict['plot_marker'] = True,
temp_dict['axes_manager'] = s.axes_manager,
temp_dict['data'] = {
'y1': position[0]*scale_y+offset_y,
'x1': position[1]*scale_x+offset_x,
'y2': position[2]*scale_y+offset_y,
'x2': position[3]*scale_x+offset_x,
'size': 20,
'text': marker_text,
}
temp_dict['marker_properties'] = marker_properties
name = temp_dict['marker_type'] + str(annotation['UniqueID'])
markers_dict[name] = temp_dict
return markers_dict
[docs]def add_annotation_markers(s):
"""
Read annotations from a signal originating from DigitalMicrograph and
convert the ones (that we can) into Hyperspy markers for plotting.
Adapted from a currently (at the time of writing) open `pull request`_ in
HyperSpy.
.. _pull request: https://github.com/hyperspy/hyperspy/pull/1491
Parameters
----------
s : :py:class:`hyperspy.signal.BaseSignal` (or subclass)
The HyperSpy signal for which a thumbnail should be generated
"""
# Parsing markers can potentially lead to errors, so to avoid
# this any Exceptions are caught and logged instead of the files
# not being loaded at all.
try:
markers_dict = _get_markers_dict(s, s.original_metadata.as_dictionary())
except Exception as err:
_logger.warning(
"Markers could not be loaded from the file "
"due to: {0}".format(err))
markers_dict = {}
if markers_dict:
markers_list = []
for k, v in markers_dict.items():
# convert each marker dictionary item into a Marker object
markers_list.append(_dict2marker(v, k))
if len(markers_list) > 0:
# add the Marker objects (in a list) to the signal
s.add_marker(markers_list, permanent=True)
[docs]def sig_to_thumbnail(s, out_path, dpi=92):
"""
Generate a preview thumbnail from an arbitrary HyperSpy signal. For a 2D
signal, the signal from the first navigation position is used (most
likely the top- and left-most position. For a 1D signal (*i.e.* a
spectrum or spectrum image), the output depends on the
number of navigation dimensions:
- 0: Image of spectrum
- 1: Image of linescan (*a la* DigitalMicrograph)
- 2: Image of spectra sampled from navigation space
- 2+: As for 2 dimensions
Parameters
----------
s : :py:class:`hyperspy.signal.BaseSignal` (or subclass)
The HyperSpy signal for which a thumbnail should be generated
out_path : str
A path to the desired thumbnail filename. All formats supported by
:py:meth:`~matplotlib.figure.Figure.savefig` can be used.
dpi : int
The "dots per inch" resolution for the outputted figure
Returns
-------
f : :py:class:`matplotlib.figure.Figure`
Handle to a matplotlib Figure
Notes
-----
This method heavily utilizes HyperSpy's existing plotting functions to
figure out how to best display the image
"""
def _set_extent_and_save():
_set_title(ax, s.metadata.General.title)
items = [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
for labels in _get_visible_labels(ax):
items += labels
extent = _full_extent(ax, items, pad=0.05).transformed(
ax.figure.dpi_scale_trans.inverted())
f.savefig(out_path, bbox_inches=extent, dpi=dpi)
_pad_to_square(out_path, 500)
# _plt.close(f)
# close all currently open plots to ensure we don't leave a mess behind
# in memory
_plt.close('all')
_plt.rcParams['image.cmap'] = 'gray'
# Processing 1D signals (spectra, spectrum images, etc)
if isinstance(s, _hsapi.signals.Signal1D):
# signal is single spectrum
if s.axes_manager.navigation_dimension == 0:
s.plot()
# get signal plot figure
f = s._plot.signal_plot.figure
ax = f.get_axes()[0]
# Change line color to matplotlib default
ax.get_lines()[0].set_color(_plt.get_cmap('tab10')(0))
_set_extent_and_save()
return f
# signal is 1D linescan
elif s.axes_manager.navigation_dimension == 1:
s.plot()
s._plot.pointer.set_on(False) # remove pointer
f = s._plot.navigator_plot.figure
f.get_axes()[1].remove() # remove colorbar scale
ax = f.get_axes()[0]
_set_extent_and_save()
return f
elif s.axes_manager.navigation_dimension > 1:
nav_size = s.axes_manager.navigation_size
if nav_size >= 9:
n_to_plot = 9
else:
n_to_plot = nav_size
# temporarily unfold the signal so we can get spectra from all
# over the navigation space easily:
with s.unfolded():
idx_to_plot = _np.linspace(0, nav_size-1, n_to_plot, dtype=int)
s_to_plot = [s.inav[i] for i in idx_to_plot]
f = _plt.figure()
_hsapi.plot.plot_spectra(s_to_plot, style='cascade',
padding=0.1, fig=f)
ax = _plt.gca()
desc = r'\ x\ '.join([str(x) for x in
s.axes_manager.navigation_shape])
_set_title(ax, s.metadata.General.title)
ax.set_title(ax.get_title() + '\n' + r"$\bf{" +
desc + r'\ Spectrum\ Image}$')
# Load "watermark" stamp and rescale to be appropriately sized
stamp = _imread(_os.path.join(_dir_path,
'spectrum_image_logo.svg.png'))
width, height = ax.figure.get_size_inches() * f.dpi
stamp_width = int(width / 2.5)
scaling = (stamp_width / float(stamp.shape[0]))
stamp_height = int(float(stamp.shape[1]) * float(scaling))
stamp = _resize(stamp, (stamp_width, stamp_height),
mode='wrap', anti_aliasing=True)
# Create matplotlib annotation with image in center
imagebox = _OIm(stamp, zoom=1, alpha=.15)
imagebox.image.axes = ax
ao = _AOb('center', pad=1, borderpad=0, child=imagebox)
ao.patch.set_alpha(0)
ax.add_artist(ao)
# Pack figure and save
f.tight_layout()
f.savefig(out_path, dpi=dpi)
_pad_to_square(out_path, 500)
return f
# Signal is an image of some sort, so we'll use hs.plot.plot_images
elif isinstance(s, _hsapi.signals.Signal2D):
# signal is single image
if s.axes_manager.navigation_dimension == 0:
# check to see if this is a dm3/dm4; if so try to plot with
# annotations
orig_fname = s.metadata.General.original_filename
if '.dm3' in orig_fname or '.dm4' in orig_fname:
add_annotation_markers(s)
s.plot(colorbar=False)
_plt.gca().axis('off')
else:
_hsapi.plot.plot_images([s], axes_decor='off',
colorbar=False, scalebar='all',
label=None)
f = _plt.gcf()
ax = _plt.gca()
_set_title(ax, s.metadata.General.title)
f.tight_layout()
f.savefig(out_path, dpi=dpi)
_pad_to_square(out_path, 500)
return f
# we're looking at an image stack
elif s.axes_manager.navigation_dimension == 1:
_plt.figure()
_plt.imshow(_project_image_stack(
s, num=min(5, s.axes_manager.navigation_size), dpi=dpi))
ax = _plt.gca()
ax.set_position([0, 0, 1, .8])
ax.set_axis_off()
_set_title(ax, s.metadata.General.title)
ax.set_title(ax.get_title() + '\n' +
r"$\bf{" + str(s.axes_manager.navigation_size) +
r'-member' + r'\ Image\ Series}$')
# use _full_extent to determine the bounding box needed to pick
# out just the items we're interested in
extent = _full_extent(ax, [ax, ax.title], pad=0.1).transformed(
ax.figure.dpi_scale_trans.inverted())
ax.figure.savefig(out_path, bbox_inches=extent, dpi=300)
_pad_to_square(out_path, 500)
return ax.figure
# This is a 4D-STEM type image, so display as tableau
elif s.axes_manager.navigation_dimension == 2:
asp_ratio = s.axes_manager.signal_shape[
1]/s.axes_manager.signal_shape[0]
width = 6
f = _plt.figure(figsize=(width, width * asp_ratio))
if s.axes_manager.navigation_size >= 9:
square_n = 3
elif s.axes_manager.navigation_size >= 4:
square_n = 2
else:
square_n = 1
num_to_plot = square_n**2
im_list = [None] * num_to_plot
desc = r'\ x\ '.join([str(x) for x in
s.axes_manager.navigation_shape])
s.unfold_navigation_space()
chunk_size = s.axes_manager.navigation_size // num_to_plot
for i in range(num_to_plot):
if square_n == 1:
im_list = [s]
else:
im_list[i] = s.inav[i * chunk_size:
(i+1) * chunk_size].inav[chunk_size//2]
axlist = _hsapi.plot.plot_images(im_list, colorbar=None,
axes_decor='off',
tight_layout=True, scalebar=[0],
per_row=square_n, fig=f)
# Make sure scalebar is fully on plot:
txt = axlist[0].texts[0]
left_extent = txt.get_window_extent().transformed(
axlist[0].transData.inverted()).bounds[0]
if left_extent < 0:
# Move scalebar text over if it overlaps outside of axis
txt.set_x(txt.get_position()[0] + left_extent * -1)
# txt.set_y(txt.get_position()[1]*1.1)
f.suptitle(_textwrap.fill(s.metadata.General.title, 60) + '\n' +
r"$\bf{" + desc + r'\ Hyperimage}$')
f.tight_layout(rect=(0, 0, 1,
f.texts[0].get_window_extent().transformed(
f.transFigure.inverted()).bounds[1]))
f.savefig(out_path, dpi=dpi)
_pad_to_square(out_path, 500)
return f
# Complex image, so plot power spectrum (like an FFT)
elif isinstance(s, _hsapi.signals.ComplexSignal2D):
# in tests, setting minimum to a percentile around 66% looks good
s.amplitude.plot(interpolation='bilinear', norm='log',
vmin=_np.nanpercentile(s.amplitude.data, 66),
colorbar=None, axes_off=True)
f = _plt.gcf()
ax = _plt.gca()
_set_title(ax, s.metadata.General.title)
extent = _full_extent(ax, [ax, ax.title], pad=0.1).transformed(
ax.figure.dpi_scale_trans.inverted())
f.savefig(out_path, dpi=dpi, bbox_inches=extent)
_pad_to_square(out_path, 500)
return f
# if we have a different type of signal, just output a graphical
# representation of the axis manager
else:
f, ax = _plt.subplots()
ax.set_position([0, 0, 1, 1])
ax.set_axis_off()
# Remove axes_manager text
ax_m = s.axes_manager.__repr__()
ax_m = ax_m.split('\n')
ax_m = ax_m[1:]
ax_m = '\n'.join(ax_m)
ax.text(0.03, .9, s.metadata.General.title,
fontweight='bold', va='top')
ax.text(0.03, 0.85, 'Could not generate preview image',
va='top', color='r')
ax.text(0.03, 0.8, 'Axes information:',
va='top', fontstyle='italic')
ax.text(0.03, .75, ax_m,
fontfamily='monospace', va='top')
extent = _full_extent(ax, ax.texts, pad=0.1).transformed(
ax.figure.dpi_scale_trans.inverted())
f.savefig(out_path, bbox_inches=extent, dpi=300)
_pad_to_square(out_path, 500)
return f
[docs]def down_sample_image(fname, out_path, output_size=None, factor=None):
"""
Load an image file from disk, down-sample it to the requested dpi, and save.
Sometimes the data doesn't need to be loaded as a HyperSpy signal,
and it's better just to down-sample existing image data (such as for .tif
files created by the Quanta SEM).
Parameters
----------
fname : str
The filepath that will be resized. All formats supported by
:py:func:`PIL.Image.open` can be used
out_path : str
A path to the desired thumbnail filename. All formats supported by
:py:meth:`PIL.Image.Image.save` can be used.
output_size : tuple
A tuple of ints specifying the width and height of the output image.
Either this argument or ``factor`` should be provided (not both).
factor : int
The multiple of the image size to reduce by (i.e. a value of 2
results in an image that is 50% of each original dimension). Either
this argument or ``output_size`` should be provided (not both).
"""
if output_size is None and factor is None:
raise ValueError('One of output_size or factor must be provided')
if output_size is not None and factor is not None:
raise ValueError('Only one of output_size or factor should be provided')
im = _PILImage.open(fname)
size = im.size
if output_size is not None:
resized = output_size
else:
resized = tuple([s//factor for s in size])
# if im.mode not in ['RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB', 'HSV']:
# im = im.convert('I') # convert to 8-bit
if 'I' in im.mode:
im = im.point(lambda i: i * (1. / 256)).convert('L')
im.thumbnail(resized, resample=_LANCZOS)
im.save(out_path)
_pad_to_square(out_path, new_width=500)
_plt.rcParams['image.cmap'] = 'gray'
f = _plt.figure()
f.gca().imshow(im)
return f