#
#------------------------------------------------------------------------------
# Copyright (c) 2013-2014, Christian Therien
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#------------------------------------------------------------------------------
#
# out.py - This file is part of the PySptools package.
#
import os.path as osp
import numpy as np
[docs]class Output(object):
""" Add plot and display functionality to the classifiers classes.
"""
def __init__(self, label):
self.label = label
def cm_dispatch(self, name):
from pysptools.classification._cm import datad
return datad[name]
def _custom_listed_color_map(self, name, N, firstBlack=False):
""" add the black color in front of 'name' color """
import matplotlib.cm as cm
from matplotlib import colors
if name == 'jet':
mp = cm.datad[name]
else:
mp = self.cm_dispatch(name)
new_mp1 = {'blue': colors.makeMappingArray(N-1, mp['blue']),
'green': colors.makeMappingArray(N-1, mp['green']),
'red': colors.makeMappingArray(N-1, mp['red'])}
new_mp2 = []
new_mp2.extend(zip(new_mp1['red'], new_mp1['green'], new_mp1['blue']))
if firstBlack == True:
new_mp2 = [(0,0,0)]+new_mp2 # the black color
return colors.ListedColormap(new_mp2, N=N-1), new_mp2
[docs] def plot(self, img, n_classes, path=None, labels=None, mask=None, interpolation='none', colorMap='jet', firstBlack=False, suffix=''):
"""
Plot a classification map using matplotlib.
Parameters:
img: `numpy array`
A classified map, (m x n x 1),
the classes start at 0.
n_classes: `int`
The number of classes found in img.
path: `string`
The path where to put the plot.
labels: `list of string [default None]`
The legend labels.
mask: `numpy array [default None]`
A binary mask, when *True* the corresponding pixel is displayed.
interpolation: `string [default none]`
A matplotlib interpolation method.
colorMap: `string [default 'Accent']`
A color map element of
['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3'],
"Accent" is the default and it fall back on "Jet".
firstBlack: `bool [default False]`
Display the first legend element in black if *True*. If it is the case,
the corresponding classification class value is zero and it can be use when the
meaning is nothing to classify (example: a background).
suffix: `string [default None]`
Add a suffix to the file name.
"""
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib import colors
if path != None:
plt.ioff()
# fallback on jet colormap
if colorMap == 'Accent': color = cm.Accent
elif colorMap == 'Dark2': color = cm.Dark2
elif colorMap == 'Paired': color = cm.Paired
elif colorMap == 'Pastel1': color = cm.Pastel1
elif colorMap == 'Pastel2': color = cm.Pastel2
elif colorMap == 'Set1': color = cm.Set1
elif colorMap == 'Set2': color = cm.Set2
elif colorMap == 'Set3': color = cm.Set3
else:
color = cm.jet
colorMap = 'jet'
if isinstance(mask, np.ndarray):
img = img[:,:] * mask
if firstBlack == False: n_classes = n_classes - 1
bounds = range(n_classes+2)
color, dummy = self._custom_listed_color_map(colorMap, len(bounds)+1, firstBlack=firstBlack)
norm = colors.BoundaryNorm(bounds, color.N)
fig0, ax0 = plt.subplots()
plot = ax0.imshow(img, cmap=color, interpolation=interpolation, norm=norm)
cbar = fig0.colorbar(plot, cmap=color, norm=norm, boundaries=bounds,
ticks=[x+0.5 for x in range(n_classes+1)])
if labels == None:
if firstBlack == True:
sigSet = [x+1 for x in range(n_classes)]
lbls = ['None']
lbls.extend(sigSet)
else:
sigSet = [x+1 for x in range(n_classes+1)]
lbls = []
lbls.extend(sigSet)
else:
if firstBlack == True:
lbls = ['None']
lbls.extend(labels)
else:
lbls = []
lbls.extend(labels)
cbar.set_ticklabels(lbls)
if labels == None:
ax0.set_ylabel('class #', rotation=270, labelpad=70)
ax0.get_yaxis().set_label_position("right")
if path != None:
if suffix == None:
fout = osp.join(path, '{0}.png'.format(self.label))
else:
fout = osp.join(path, '{0}_{1}.png'.format(self.label, suffix))
try:
plt.savefig(fout)
except IOError:
raise IOError('in classification.SVC, no such file or directory: {0}'.format(path))
else:
if suffix == None:
plt.title('{0}'.format(self.label))
else:
plt.title('{0} - {1}'.format(self.label, suffix))
plt.show()
plt.close()
[docs] def display(self, img, n_classes, labels=None, mask=None, interpolation='none', colorMap='jet', firstBlack=False, suffix=''):
"""
Display a classification map using matplotlib.
Parameters:
img: `numpy array`
A classified map, (m x n x 1),
the classes start at 0.
n_classes: `int`
The number of classes found in img.
labels: `list of string [default None]`
The legend labels.
mask: `numpy array [default None]`
A binary mask, when *True* the corresponding pixel is displayed.
interpolation: `string [default none]`
A matplotlib interpolation method.
colorMap: `string [default 'Accent']`
A color map element of
['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3'],
"Accent" is the default and it fall back on "Jet".
firstBlack: `bool [default False]`
Display the first legend element in black if *True*. If it is the case,
the corresponding classification class value is zero and it can be use when the
meaning is nothing to classify (example: a background).
suffix: `string [default None]`
Add a suffix to the file name.
"""
self.plot(img, n_classes, labels=labels, mask=mask, interpolation=interpolation, colorMap=colorMap, firstBlack=firstBlack, suffix=suffix)
def plot1(self, img, path=None, mask=None, interpolation='none', colorMap='jet', suffix=''):
import matplotlib.pyplot as plt
if path != None:
plt.ioff()
if isinstance(mask, np.ndarray):
img = img[:,:] * mask
plt.imshow(img, interpolation=interpolation)
plt.set_cmap(colorMap)
cbar = plt.colorbar()
cbar.set_ticks([])
if path != None:
if suffix == None:
fout = osp.join(path, '{0}.png'.format(self.label))
else:
fout = osp.join(path, '{0}_{1}.png'.format(self.label, suffix))
try:
plt.savefig(fout)
except IOError:
raise IOError('in classifiers.output, no such file or directory: {0}'.format(path))
else:
if suffix == None:
plt.title('{0}'.format(self.label))
else:
plt.title('{0} - {1}'.format(self.label, suffix))
plt.show()
plt.close()
def plot_single_map(self, path, cmap, dist_map, lib_idx, em_nbr, threshold, constrained, stretch, colorMap, suffix):
## """
## Plot individual classified map. One for each spectrum.
##
## Parameters
## path : string
## The path where to put the plot.
##
## lib_idx : int
## * A number between 1 and the number of spectra in the library.
## * 'all', plot all the individual maps.
##
## suffix : string
## Add a suffix to the file name.
##
## """
if lib_idx == 'all':
for signo in range(em_nbr):
self._plot_single_map1(path, cmap, signo + 1, dist_map, threshold, constrained, stretch, colorMap, suffix)
else:
self._plot_single_map1(path, cmap, lib_idx, dist_map, threshold, constrained, stretch, colorMap, suffix)
def _plot_single_map1(self, path, cmap, signo, dist_map, threshold, constrained, stretch, colorMap, suffix):
import matplotlib.pyplot as plt
if path != None:
plt.ioff()
grad = self.get_single_map(signo, cmap, dist_map, threshold, constrained, stretch)
plt.imshow(grad, interpolation='none')
plt.set_cmap(colorMap)
cbar = plt.colorbar()
cbar.set_ticks([])
if path != None:
if suffix == None:
fout = osp.join(path, '{0}_{1}.png'.format(self.label, signo))
else:
fout = osp.join(path, '{0}_{1}_{2}.png'.format(self.label, signo, suffix))
try:
plt.savefig(fout)
except IOError:
raise IOError('in classifiers.output, no such file or directory: {0}'.format(path))
else:
if suffix == None:
plt.title('{0} - EM{1}'.format(self.label, signo))
else:
plt.title('{0} - EM{1} - {2}'.format(self.label, signo, suffix))
plt.show()
plt.close()
def get_single_map(self, signo, cmap, dist_map, threshold, constrained, stretch, inverse_scale=True):
if constrained == False:
amin = np.min(dist_map[:,:,signo - 1])
amax = np.max(dist_map[:,:,signo - 1])
if type(threshold) is float:
limit = amin + (amax - amin) * threshold
if type(threshold) is list:
limit = amin + (amax - amin) * threshold[signo - 1]
if self.label == 'NormXCorr':
grad = (dist_map[:,:,signo - 1] > limit) * dist_map[:,:,signo - 1]
else:
grad = (dist_map[:,:,signo - 1] < limit) * dist_map[:,:,signo - 1]
if constrained == True:
thresholded = cmap == signo
grad = (dist_map[:,:,signo - 1] * thresholded)
# Inverse the scale for SAM and SID,
# not needed for NormXCorr
# Some brain damaging logic here:
# inverse_scale == True only for the plot_single_map() call
# inverse_scale == False only for the get_single_map() call
if inverse_scale == True:
if self.label == 'NormXCorr' and stretch == True:
# strech between 0 and 1
min = 2
for i in range(grad.shape[0]):
for j in range(grad.shape[1]):
if grad[i,j] < min and grad[i,j] != 0:
min = grad[i,j]
for i in range(grad.shape[0]):
for j in range(grad.shape[1]):
if grad[i,j] != 0:
grad[i,j] = grad[i,j] - min
grad = (1 / np.max(grad)) * grad
if self.label == 'SAM' or self.label == 'SID':
# strech between 0 and 1
if stretch == True:
grad = (1 / np.max(grad)) * grad
# and inverse
for i in range(grad.shape[0]):
for j in range(grad.shape[1]):
if grad[i,j] != 0: grad[i,j] = 1 - grad[i,j]
return grad
def plot_histo(self, path, cmap, em_nbr, suffix):
import matplotlib.pyplot as plt
plt.ioff()
farray = np.ndarray.flatten(cmap)
plt.hist(farray, bins=range(em_nbr+2), align='left')
if suffix == None:
fout = osp.join(path, 'histo_{0}.png'.format(self.label))
else:
fout = osp.join(path, 'histo_{0}_{1}.png'.format(self.label, suffix))
try:
plt.savefig(fout)
except IOError:
raise IOError('in classifiers.output, no such file or directory: {0}'.format(path))
plt.close()