update public distribution
based on internal repository c9a2ac8 2019-01-03 16:04:57 +0100 tagged rev-master-2.0.0
This commit is contained in:
0
pmsco/graphics/__init__.py
Normal file
0
pmsco/graphics/__init__.py
Normal file
198
pmsco/graphics/rfactor.py
Normal file
198
pmsco/graphics/rfactor.py
Normal file
@ -0,0 +1,198 @@
|
||||
"""
|
||||
@package pmsco.graphics.rfactor
|
||||
graphics rendering module for r-factor optimization results.
|
||||
|
||||
this module is under development.
|
||||
interface and implementation are subject to change.
|
||||
|
||||
@author Matthias Muntwiler, matthias.muntwiler@psi.ch
|
||||
|
||||
@copyright (c) 2018 by Paul Scherrer Institut @n
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); @n
|
||||
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
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
import math
|
||||
import numpy as np
|
||||
from pmsco.helpers import BraceMessage as BMsg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
except ImportError:
|
||||
Figure = None
|
||||
FigureCanvas = None
|
||||
logger.warning("error importing matplotlib. graphics rendering disabled.")
|
||||
|
||||
|
||||
def render_param_rfac(filename, data, param_name, summary=None, canvas=None):
|
||||
"""
|
||||
render an r-factor versus one model parameter graph.
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function raises an error.
|
||||
|
||||
@param filename: path and name of the results file.
|
||||
this is used to derive the output file path by adding the parameter name and
|
||||
the extension of the graphics file format.
|
||||
|
||||
@param data: numpy-structured array of results (one-dimensional).
|
||||
|
||||
the field names identify the model parameters and optimization control values.
|
||||
model parameters can have any name not including a leading underscore and are evaluated as is.
|
||||
the names of the special optimization control values begin with an underscore.
|
||||
of these, at least _rfac must be provided.
|
||||
|
||||
@param param_name: name of the model parameter to display.
|
||||
this must correspond to a field name of the data array.
|
||||
|
||||
@param summary: (dict) the dictionary returned by @ref evaluate_results.
|
||||
this is used to mark the optimum value and the error limits.
|
||||
if None, these values are not marked in the plot.
|
||||
|
||||
@param canvas: a FigureCanvas class reference from a matplotlib backend.
|
||||
if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format.
|
||||
|
||||
@return (str) path and name of the generated graphics file.
|
||||
empty string if an error occurred.
|
||||
|
||||
@raise TypeError if matplotlib is not available.
|
||||
"""
|
||||
if canvas is None:
|
||||
canvas = FigureCanvas
|
||||
fig = Figure()
|
||||
canvas(fig)
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
ax.scatter(data[param_name], data['_rfac'], c='b', marker='o', s=4.0)
|
||||
|
||||
if summary is not None:
|
||||
xval = summary['val'][param_name]
|
||||
ymin = summary['vmin']['_rfac']
|
||||
ymax = summary['vmax']['_rfac']
|
||||
ax.plot((xval, xval), (ymin, ymax), ':k')
|
||||
xmin = summary['vmin'][param_name]
|
||||
xmax = summary['vmax'][param_name]
|
||||
varr = summary['rmin'] + summary['rvar']
|
||||
ax.plot((xmin, xmax), (varr, varr), ':k')
|
||||
|
||||
ax.grid(True)
|
||||
ax.set_xlabel(param_name)
|
||||
ax.set_ylabel('R-factor')
|
||||
|
||||
out_filename = "{0}.{1}.{2}".format(filename, param_name, canvas.get_default_filetype())
|
||||
fig.savefig(out_filename)
|
||||
return out_filename
|
||||
|
||||
|
||||
def evaluate_results(data, features=50.):
|
||||
"""
|
||||
@param data: numpy-structured array of results (one-dimensional).
|
||||
|
||||
the field names identify the model parameters and optimization control values.
|
||||
model parameters can have any name not including a leading underscore and are evaluated as is.
|
||||
the names of the special optimization control values begin with an underscore.
|
||||
of these, at least _rfac must be provided.
|
||||
|
||||
@param features: number of independent features (pieces of information) in the data.
|
||||
this quantity can be approximated as the scan range divided by the average width of a feature
|
||||
which includes an intrinsic component and the instrumental resolution.
|
||||
see Booth et al., Surf. Sci. 387 (1997), 152 for energy scans, and
|
||||
Muntwiler et al., Surf. Sci. 472 (2001), 125 for angle scans.
|
||||
the default value of 50 is a typical value.
|
||||
|
||||
@return dictionary of evaluation results.
|
||||
|
||||
the dictionary contains scalars and structured arrays as follows.
|
||||
the structured arrays have the same data type as the input data and contain exactly one element.
|
||||
|
||||
@arg rmin: (scalar) minimum r-factor.
|
||||
@arg rvar: (scalar) one-sigma variation of r-factor.
|
||||
@arg imin: (scalar) array index where the minimum is located.
|
||||
@arg val: (structured array) estimates of parameter values (parameter value at rmin).
|
||||
@arg sig: (structured array) one-sigma error of estimated values.
|
||||
@arg vmin: (structured array) minimum value of the parameter.
|
||||
@arg vmax: (structured array) maximum value of the parameter.
|
||||
"""
|
||||
imin = data['_rfac'].argmin()
|
||||
rmin = data['_rfac'][imin]
|
||||
rvar = rmin * math.sqrt(2. / float(features))
|
||||
|
||||
val = np.zeros(1, dtype=data.dtype)
|
||||
sig = np.zeros(1, dtype=data.dtype)
|
||||
vmin = np.zeros(1, dtype=data.dtype)
|
||||
vmax = np.zeros(1, dtype=data.dtype)
|
||||
sel = data['_rfac'] <= rmin + rvar
|
||||
for name in data.dtype.names:
|
||||
val[name] = data[name][imin]
|
||||
vmin[name] = data[name].min()
|
||||
vmax[name] = data[name].max()
|
||||
if name[0] != '_':
|
||||
sig[name] = (data[name][sel].max() - data[name][sel].min()) / 2.
|
||||
|
||||
results = {'rmin': rmin, 'rvar': rvar, 'imin': imin, 'val': val, 'sig': sig, 'vmin': vmin, 'vmax': vmax}
|
||||
return results
|
||||
|
||||
|
||||
def render_results(results_file, data=None):
|
||||
"""
|
||||
produce a graphics file from optimization results.
|
||||
|
||||
the results can be passed in a file name or numpy array (see parameter section).
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function will log a warning message and return gracefully.
|
||||
|
||||
@param results_file: path and name of the result file.
|
||||
|
||||
result files are the ones written by swarm.SwarmPopulation.save_array, for instance.
|
||||
the file contains columns of model parameters and optimization control values.
|
||||
the first row must contain column names that identify the quantity.
|
||||
model parameters can have any name not including a leading underscore and are evaluated as is.
|
||||
the names of the special optimization control values begin with an underscore.
|
||||
of these, at least _rfac must be provided.
|
||||
|
||||
if the optional data parameter is present,
|
||||
this is used only to derive the output file path by adding the extension of the graphics file format.
|
||||
|
||||
@param data: numpy-structured array of results (one-dimensional).
|
||||
|
||||
the field names identify the model parameters and optimization control values.
|
||||
model parameters can have any name not including a leading underscore and are evaluated as is.
|
||||
the names of the special optimization control values begin with an underscore.
|
||||
of these, at least _rfac must be provided.
|
||||
|
||||
if this argument is omitted, the data is loaded from the file referenced by the filename argument.
|
||||
|
||||
@return (list of str) path names of the generated graphics files.
|
||||
empty if an error occurred.
|
||||
the most common exceptions are caught and add a warning in the log file.
|
||||
"""
|
||||
|
||||
if data is None:
|
||||
data = np.genfromtxt(results_file, names=True)
|
||||
|
||||
summary = evaluate_results(data)
|
||||
|
||||
out_files = []
|
||||
try:
|
||||
for name in data.dtype.names:
|
||||
if name[0] != '_' and summary['sig'][name] > 0.:
|
||||
graph_file = render_param_rfac(results_file, data, name, summary)
|
||||
out_files.append(graph_file)
|
||||
except (TypeError, AttributeError, IOError) as e:
|
||||
logger.warning(BMsg("error rendering scan file {file}: {msg}", file=results_file, msg=str(e)))
|
||||
|
||||
return out_files
|
273
pmsco/graphics/scan.py
Normal file
273
pmsco/graphics/scan.py
Normal file
@ -0,0 +1,273 @@
|
||||
"""
|
||||
@package pmsco.graphics.scan
|
||||
graphics rendering module for energy and angle scans.
|
||||
|
||||
this module is experimental.
|
||||
interface and implementation are subject to change.
|
||||
|
||||
@author Matthias Muntwiler, matthias.muntwiler@psi.ch
|
||||
|
||||
@copyright (c) 2018 by Paul Scherrer Institut @n
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); @n
|
||||
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
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
import logging
|
||||
import math
|
||||
import numpy as np
|
||||
import pmsco.data as md
|
||||
from pmsco.helpers import BraceMessage as BMsg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
# from matplotlib.backends.backend_pdf import FigureCanvasPdf
|
||||
# from matplotlib.backends.backend_svg import FigureCanvasSVG
|
||||
except ImportError:
|
||||
Figure = None
|
||||
FigureCanvas = None
|
||||
logger.warning("error importing matplotlib. graphics rendering disabled.")
|
||||
|
||||
|
||||
def render_1d_scan(filename, data, scan_mode, canvas=None, is_modf=False):
|
||||
"""
|
||||
produce a graphics file from a one-dimensional scan file.
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function raises an error.
|
||||
|
||||
@param filename: path and name of the scan file.
|
||||
this is used to derive the output file path by adding the extension of the graphics file format.
|
||||
@param data: numpy-structured array of EI, ETPI or ETPAI data.
|
||||
@param scan_mode: list containing the field name of the scanning axis of the data array.
|
||||
it must contain one element exactly.
|
||||
@param canvas: a FigureCanvas class reference from a matplotlib backend.
|
||||
if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format.
|
||||
@param is_modf: whether data contains a modulation function (True) or intensity (False, default).
|
||||
this parameter is used to set axis labels.
|
||||
|
||||
@return (str) path and name of the generated graphics file.
|
||||
empty string if an error occurred.
|
||||
|
||||
@raise TypeError if matplotlib is not available.
|
||||
"""
|
||||
if canvas is None:
|
||||
canvas = FigureCanvas
|
||||
fig = Figure()
|
||||
canvas(fig)
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
ax.plot(data[scan_mode[0]], data['i'])
|
||||
|
||||
ax.set_xlabel(scan_mode[0])
|
||||
if is_modf:
|
||||
ax.set_ylabel('chi')
|
||||
else:
|
||||
ax.set_ylabel('int')
|
||||
|
||||
out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype())
|
||||
fig.savefig(out_filename)
|
||||
return out_filename
|
||||
|
||||
|
||||
def render_ea_scan(filename, data, scan_mode, canvas=None, is_modf=False):
|
||||
"""
|
||||
produce a graphics file from an energy-angle scan file.
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function raises an error.
|
||||
|
||||
@param filename: path and name of the scan file.
|
||||
this is used to derive the output file path by adding the extension of the graphics file format.
|
||||
@param data: numpy-structured array of ETPI or ETPAI data.
|
||||
@param scan_mode: list containing the field names of the scanning axes of the data array,
|
||||
i.e. 'e' and one of the angle axes.
|
||||
@param canvas: a FigureCanvas class reference from a matplotlib backend.
|
||||
if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format.
|
||||
@param is_modf: whether data contains a modulation function (True) or intensity (False, default).
|
||||
this parameter is used to select a suitable color scale.
|
||||
|
||||
@return (str) path and name of the generated graphics file.
|
||||
empty string if an error occurred.
|
||||
|
||||
@raise TypeError if matplotlib is not available.
|
||||
"""
|
||||
(data2d, axis0, axis1) = md.reshape_2d(data, scan_mode, 'i')
|
||||
|
||||
if canvas is None:
|
||||
canvas = FigureCanvas
|
||||
fig = Figure()
|
||||
canvas(fig)
|
||||
|
||||
ax = fig.add_subplot(111)
|
||||
im = ax.imshow(data2d, origin='lower', aspect='auto', interpolation='none')
|
||||
im.set_extent((axis1[0], axis1[-1], axis0[0], axis0[-1]))
|
||||
|
||||
ax.set_xlabel(scan_mode[1])
|
||||
ax.set_ylabel(scan_mode[0])
|
||||
|
||||
cb = fig.colorbar(im, shrink=0.4, pad=0.1)
|
||||
|
||||
dlo = np.nanpercentile(data['i'], 1)
|
||||
dhi = np.nanpercentile(data['i'], 99)
|
||||
if is_modf:
|
||||
im.set_cmap("RdBu_r")
|
||||
dhi = max(abs(dlo), abs(dhi))
|
||||
dlo = -dhi
|
||||
im.set_clim((dlo, dhi))
|
||||
try:
|
||||
# requires matplotlib 2.1.0
|
||||
ti = cb.get_ticks()
|
||||
ti = [min(ti), 0., max(ti)]
|
||||
cb.set_ticks(ti)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
im.set_cmap("magma")
|
||||
im.set_clim((dlo, dhi))
|
||||
|
||||
out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype())
|
||||
fig.savefig(out_filename)
|
||||
return out_filename
|
||||
|
||||
|
||||
def render_tp_scan(filename, data, canvas=None, is_modf=False):
|
||||
"""
|
||||
produce a graphics file from an theta-phi (hemisphere) scan file.
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function raises an error.
|
||||
|
||||
@param filename: path and name of the scan file.
|
||||
this is used to derive the output file path by adding the extension of the graphics file format.
|
||||
@param data: numpy-structured array of TPI data.
|
||||
the T and P columns describes a full or partial hemispherical scan.
|
||||
the I column contains the intensity or modulation values.
|
||||
other columns are ignored.
|
||||
@param canvas: a FigureCanvas class reference from a matplotlib backend.
|
||||
if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format.
|
||||
@param is_modf: whether data contains a modulation function (True) or intensity (False, default).
|
||||
this parameter is used to select a suitable color scale.
|
||||
|
||||
@return (str) path and name of the generated graphics file.
|
||||
empty string if an error occurred.
|
||||
|
||||
@raise TypeError if matplotlib is not available.
|
||||
"""
|
||||
if canvas is None:
|
||||
canvas = FigureCanvas
|
||||
fig = Figure()
|
||||
canvas(fig)
|
||||
|
||||
ax = fig.add_subplot(111, projection='polar')
|
||||
|
||||
data = data[data['t'] <= 89.0]
|
||||
# stereographic projection
|
||||
rd = 2 * np.tan(np.radians(data['t']) / 2)
|
||||
drdt = 1 + np.tan(np.radians(data['t']) / 2)**2
|
||||
|
||||
# http://matplotlib.org/api/collections_api.html#matplotlib.collections.PathCollection
|
||||
pc = ax.scatter(data['p'] * math.pi / 180., rd, c=data['i'], lw=0, alpha=1.)
|
||||
|
||||
# interpolate marker size between 4 and 9 (for theta step = 1)
|
||||
unique_theta = np.unique(data['t'])
|
||||
theta_step = (np.max(unique_theta) - np.min(unique_theta)) / unique_theta.shape[0]
|
||||
sz = np.ones_like(pc.get_sizes()) * drdt * 4.5 * theta_step**2
|
||||
pc.set_sizes(sz)
|
||||
|
||||
# xticks = angles where grid lines are displayed (in radians)
|
||||
ax.set_xticks([])
|
||||
# rticks = radii where grid lines (circles) are displayed
|
||||
ax.set_rticks([])
|
||||
ax.set_rmax(2.0)
|
||||
|
||||
cb = fig.colorbar(pc, shrink=0.4, pad=0.1)
|
||||
|
||||
dlo = np.nanpercentile(data['i'], 2)
|
||||
dhi = np.nanpercentile(data['i'], 98)
|
||||
if is_modf:
|
||||
pc.set_cmap("RdBu_r")
|
||||
# im.set_cmap("coolwarm")
|
||||
dhi = max(abs(dlo), abs(dhi))
|
||||
dlo = -dhi
|
||||
pc.set_clim((dlo, dhi))
|
||||
try:
|
||||
# requires matplotlib 2.1.0
|
||||
ti = cb.get_ticks()
|
||||
ti = [min(ti), 0., max(ti)]
|
||||
cb.set_ticks(ti)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
pc.set_cmap("magma")
|
||||
# im.set_cmap("inferno")
|
||||
# im.set_cmap("viridis")
|
||||
pc.set_clim((dlo, dhi))
|
||||
ti = cb.get_ticks()
|
||||
ti = [min(ti), max(ti)]
|
||||
cb.set_ticks(ti)
|
||||
|
||||
out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype())
|
||||
fig.savefig(out_filename)
|
||||
return out_filename
|
||||
|
||||
|
||||
def render_scan(filename, data=None):
|
||||
"""
|
||||
produce a graphics file from a scan file.
|
||||
|
||||
the default file format is PNG.
|
||||
|
||||
this function requires the matplotlib module.
|
||||
if it is not available, the function will log a warning message and return gracefully.
|
||||
|
||||
@param filename: path and name of the scan file.
|
||||
the file must have one of the formats supported by pmsco.data.load_data().
|
||||
it must contain a single scan (not the combined scan from the model level of PMSCO).
|
||||
supported are all one-dimensional linear scans,
|
||||
and two-dimensional energy-angle scans (each axis must be linear).
|
||||
hemispherical scans are currently not supported.
|
||||
the filename should include ".modf" if the data contains a modulation function rather than intensity.
|
||||
|
||||
if the optional data parameter is present,
|
||||
this is used only to derive the output file path by adding the extension of the graphics file format.
|
||||
|
||||
@param data: numpy-structured array of ETPI or ETPAI data.
|
||||
if this argument is omitted, the data is loaded from the file referenced by the filename argument.
|
||||
|
||||
@return (str) path and name of the generated graphics file.
|
||||
empty string if an error occurred.
|
||||
"""
|
||||
if data is None:
|
||||
data = md.load_data(filename)
|
||||
scan_mode, scan_positions = md.detect_scan_mode(data)
|
||||
is_modf = filename.find(".modf") >= 0
|
||||
|
||||
try:
|
||||
if len(scan_mode) == 1:
|
||||
out_filename = render_1d_scan(filename, data, scan_mode, is_modf=is_modf)
|
||||
elif len(scan_mode) == 2 and 'e' in scan_mode:
|
||||
out_filename = render_ea_scan(filename, data, scan_mode, is_modf=is_modf)
|
||||
elif len(scan_mode) == 2 and 't' in scan_mode and 'p' in scan_mode:
|
||||
out_filename = render_tp_scan(filename, data, is_modf=is_modf)
|
||||
else:
|
||||
out_filename = ""
|
||||
logger.warning(BMsg("no render function for scan file {file}", file=filename))
|
||||
except (TypeError, AttributeError, IOError) as e:
|
||||
out_filename = ""
|
||||
logger.warning(BMsg("error rendering scan file {file}: {msg}", file=filename, msg=str(e)))
|
||||
|
||||
return out_filename
|
Reference in New Issue
Block a user