root / ase / visualize / fieldplotter.py @ 3
Historique | Voir | Annoter | Télécharger (10,86 ko)
1 | 1 | tkerber | """plotting fields defined on atoms during a simulation."""
|
---|---|---|---|
2 | 1 | tkerber | |
3 | 1 | tkerber | from ase.visualize.primiplotter import PostScriptFile, PnmFile, GifFile, JpegFile, X11Window |
4 | 1 | tkerber | from ase.visualize.primiplotter import PrimiPlotter as _PrimiPlotter |
5 | 1 | tkerber | import numpy |
6 | 1 | tkerber | import time |
7 | 1 | tkerber | |
8 | 1 | tkerber | class FieldPlotter(_PrimiPlotter): |
9 | 1 | tkerber | def __init__(self, atoms, datasource=None, verbose=0, timing=0, |
10 | 1 | tkerber | interval=1, initframe=0): |
11 | 1 | tkerber | _PrimiPlotter.__init__(self, atoms, verbose=verbose, timing=timing,
|
12 | 1 | tkerber | interval=interval, initframe=initframe) |
13 | 1 | tkerber | self.datasource = datasource
|
14 | 1 | tkerber | self.dims = (100,100) |
15 | 1 | tkerber | self.set_plot_plane("xy") |
16 | 1 | tkerber | self.set_data_range("plot") |
17 | 1 | tkerber | self.set_background(0.0) |
18 | 1 | tkerber | self.set_red_yellow_colors()
|
19 | 1 | tkerber | |
20 | 1 | tkerber | def set_plot_plane(self, plane): |
21 | 1 | tkerber | """Set the plotting plane to xy, xz or yz (default: xy)"""
|
22 | 1 | tkerber | if plane in ("xy", "xz", "yz"): |
23 | 1 | tkerber | self.plane = plane
|
24 | 1 | tkerber | else:
|
25 | 1 | tkerber | raise ValueError, "The argument to plotPlane must be 'xy', 'xz' or 'yz'." |
26 | 1 | tkerber | |
27 | 1 | tkerber | def set_data_range(self, range1, range2=None): |
28 | 1 | tkerber | """Set the range of the data used when coloring.
|
29 | 1 | tkerber |
|
30 | 1 | tkerber | This function sets the range of data values mapped unto colors
|
31 | 1 | tkerber | in the final plot.
|
32 | 1 | tkerber |
|
33 | 1 | tkerber | Three possibilities:
|
34 | 1 | tkerber |
|
35 | 1 | tkerber | 'data': Autoscale using the data on visible atoms.
|
36 | 1 | tkerber | The range goes from the lowest to the highest
|
37 | 1 | tkerber | value present on the atoms. If only a few atoms
|
38 | 1 | tkerber | have extreme values, the entire color range may not
|
39 | 1 | tkerber | be used on the plot, as many values may be averaged
|
40 | 1 | tkerber | on each point in the plot.
|
41 | 1 | tkerber |
|
42 | 1 | tkerber | 'plot': Autoscale using the data on the plot. Unlike 'data'
|
43 | 1 | tkerber | this guarantees that the entire color range is used.
|
44 | 1 | tkerber |
|
45 | 1 | tkerber | min, max: Use the range [min, max]
|
46 | 1 | tkerber |
|
47 | 1 | tkerber | """
|
48 | 1 | tkerber | if (range1 == "data" or range1 == "plot") and range2 == None: |
49 | 1 | tkerber | self.autorange = range1
|
50 | 1 | tkerber | elif range2 != None: |
51 | 1 | tkerber | self.autorange = None |
52 | 1 | tkerber | self.range = (range1, range2)
|
53 | 1 | tkerber | else:
|
54 | 1 | tkerber | raise ValueError, "Illegal argument(s) to set_data_range" |
55 | 1 | tkerber | |
56 | 1 | tkerber | def set_background(self, value): |
57 | 1 | tkerber | """Set the data value of the background. See also set_background_color
|
58 | 1 | tkerber |
|
59 | 1 | tkerber | Set the value of the background (parts of the plot without atoms) to
|
60 | 1 | tkerber | a specific value, or to 'min' or 'max' representing the minimal or
|
61 | 1 | tkerber | maximal data values on the atoms.
|
62 | 1 | tkerber |
|
63 | 1 | tkerber | Calling set_background cancels previous calls to set_background_color.
|
64 | 1 | tkerber | """
|
65 | 1 | tkerber | self.background = value
|
66 | 1 | tkerber | self.backgroundcolor = None |
67 | 1 | tkerber | |
68 | 1 | tkerber | def set_background_color(self, color): |
69 | 1 | tkerber | """Set the background color. See also set_background.
|
70 | 1 | tkerber |
|
71 | 1 | tkerber | Set the background color. Use a single value in the range [0, 1[
|
72 | 1 | tkerber | for gray values, or a tuple of three such values as an RGB color.
|
73 | 1 | tkerber |
|
74 | 1 | tkerber | Calling set_background_color cancels previous calls to set_background.
|
75 | 1 | tkerber | """
|
76 | 1 | tkerber | self.background = None |
77 | 1 | tkerber | self.backgroundcolor = color
|
78 | 1 | tkerber | |
79 | 1 | tkerber | def set_red_yellow_colors(self, reverse=False): |
80 | 1 | tkerber | """Set colors to Black-Red-Yellow-White (a.k.a. STM colors)"""
|
81 | 1 | tkerber | self.set_colors([(0.0, 0, 0, 0), |
82 | 1 | tkerber | (0.33, 1, 0, 0), |
83 | 1 | tkerber | (0.66, 1, 1, 0), |
84 | 1 | tkerber | (1.0, 1, 1, 1)], |
85 | 1 | tkerber | reverse) |
86 | 1 | tkerber | |
87 | 1 | tkerber | def set_black_white_colors(self, reverse=False): |
88 | 1 | tkerber | """Set the color to Black-White (greyscale)"""
|
89 | 1 | tkerber | self.set_colors([(0.0, 0), (1.0, 1)], reverse) |
90 | 1 | tkerber | |
91 | 1 | tkerber | def set_colors(self, colors, reverse=False): |
92 | 1 | tkerber | colors = numpy.array(colors, numpy.float) |
93 | 1 | tkerber | if len(colors.shape) != 2: |
94 | 1 | tkerber | raise ValueError, "Colors must be a 2D array." |
95 | 1 | tkerber | if reverse:
|
96 | 1 | tkerber | colors[:,0] = 1 - colors[:,0] |
97 | 1 | tkerber | colors = numpy.array(colors[::-1,:])
|
98 | 1 | tkerber | #print colors
|
99 | 1 | tkerber | if colors[0,0] != 0.0 or colors[-1,0] != 1.0: |
100 | 1 | tkerber | raise ValueError, "First row must define the value 0 and last row must define the value 1" |
101 | 1 | tkerber | if colors.shape[1] == 2: |
102 | 1 | tkerber | self.colormode = 1 |
103 | 1 | tkerber | elif colors.shape[1] == 4: |
104 | 1 | tkerber | self.colormode = 3 |
105 | 1 | tkerber | else:
|
106 | 1 | tkerber | raise ValueError, "Color specification must be Nx2 (grey) or Nx4 (rgb) matrix." |
107 | 1 | tkerber | self.colorfunction = InterpolatingFunction(colors[:,0], colors[:,1:]) |
108 | 1 | tkerber | |
109 | 1 | tkerber | def plot(self, data=None): |
110 | 1 | tkerber | """Create a plot now. Does not respect the interval timer.
|
111 | 1 | tkerber |
|
112 | 1 | tkerber | This method makes a plot unconditionally. It does not look at
|
113 | 1 | tkerber | the interval variable, nor is this plot taken into account in
|
114 | 1 | tkerber | the counting done by the update() method if an interval
|
115 | 1 | tkerber | variable was specified.
|
116 | 1 | tkerber |
|
117 | 1 | tkerber | If data is specified, it must be an array of numbers with the
|
118 | 1 | tkerber | same length as the atoms. That data will then be plotted. If
|
119 | 1 | tkerber | no data is given, the data source specified when creating the
|
120 | 1 | tkerber | plotter is used.
|
121 | 1 | tkerber |
|
122 | 1 | tkerber | """
|
123 | 1 | tkerber | if self.timing: |
124 | 1 | tkerber | self._starttimer()
|
125 | 1 | tkerber | self.log("FieldPlotter: Starting plot at " |
126 | 1 | tkerber | + time.strftime("%a, %d %b %Y %H:%M:%S"))
|
127 | 1 | tkerber | if data is None: |
128 | 1 | tkerber | data = self.datasource()
|
129 | 1 | tkerber | if len(data) != len(self.atoms): |
130 | 1 | tkerber | raise ValueError, ("Data has wrong length: %d instead of %d." |
131 | 1 | tkerber | % (len(data), len(self.atoms))) |
132 | 1 | tkerber | |
133 | 1 | tkerber | invisible = self._getinvisible()
|
134 | 1 | tkerber | coords = self._rotate(self._getpositions()) |
135 | 1 | tkerber | radii = self._getradii()
|
136 | 1 | tkerber | if self.autoscale: |
137 | 1 | tkerber | self._autoscale(coords,radii)
|
138 | 1 | tkerber | scale = self.scale * self.relativescale |
139 | 1 | tkerber | coords = scale * coords |
140 | 1 | tkerber | center = self._getcenter(coords)
|
141 | 1 | tkerber | offset = numpy.array(self.dims + (0.0,))/2.0 - center |
142 | 1 | tkerber | coords = coords + offset |
143 | 1 | tkerber | radii = radii * scale |
144 | 1 | tkerber | self.log("Scale is %f and size is (%d, %d)" |
145 | 1 | tkerber | % (scale, self.dims[0], self.dims[1])) |
146 | 1 | tkerber | self.log("Physical size of plot is %f Angstrom times %f Angstrom" |
147 | 1 | tkerber | % (self.dims[0] / scale, self.dims[1] / scale)) |
148 | 1 | tkerber | |
149 | 1 | tkerber | # Remove invisible atoms
|
150 | 1 | tkerber | selector = numpy.logical_not(invisible) |
151 | 1 | tkerber | coords = numpy.compress(selector, coords, 0)
|
152 | 1 | tkerber | radii = numpy.compress(selector, radii) |
153 | 1 | tkerber | data = numpy.compress(selector, data) |
154 | 1 | tkerber | |
155 | 1 | tkerber | self.log("plotting data in the range [%f,%f]" % |
156 | 1 | tkerber | (data.min(), data.max())) |
157 | 1 | tkerber | # Now create the output array
|
158 | 1 | tkerber | sumarray = numpy.zeros(self.dims, numpy.float)
|
159 | 1 | tkerber | weight = numpy.zeros(self.dims)
|
160 | 1 | tkerber | |
161 | 1 | tkerber | # Loop over all atoms, and plot them
|
162 | 1 | tkerber | nmiss = 0
|
163 | 1 | tkerber | if self.plane == "xy": |
164 | 1 | tkerber | xy = coords[:,:2]
|
165 | 1 | tkerber | elif self.plane == "xz": |
166 | 1 | tkerber | xy = coords[:,::2]
|
167 | 1 | tkerber | elif self.plane == "yz": |
168 | 1 | tkerber | xy = coords[:,1:]
|
169 | 1 | tkerber | else:
|
170 | 1 | tkerber | raise RuntimeError, "self.plane is bogus: "+str(self.plane) |
171 | 1 | tkerber | assert xy.shape[1] == 2 |
172 | 1 | tkerber | |
173 | 1 | tkerber | self.log("plotting %d atoms on %d * %d (= %d) grid" % |
174 | 1 | tkerber | (len(xy), sumarray.shape[0], sumarray.shape[1], |
175 | 1 | tkerber | len(sumarray.flat)))
|
176 | 1 | tkerber | |
177 | 1 | tkerber | xy = xy.astype(numpy.int) |
178 | 1 | tkerber | for i in xrange(len(xy)): |
179 | 1 | tkerber | (x, y) = xy[i] |
180 | 1 | tkerber | d = data[i] |
181 | 1 | tkerber | if (x >= 0 and x < self.dims[0] and y >= 0 and y < self.dims[1]): |
182 | 1 | tkerber | sumarray[x,y] += d |
183 | 1 | tkerber | weight[x,y] += 1
|
184 | 1 | tkerber | else:
|
185 | 1 | tkerber | nmiss += 1
|
186 | 1 | tkerber | print "... %d atoms fell outside plot." % (nmiss,) |
187 | 1 | tkerber | |
188 | 1 | tkerber | datamap = self._makedatamap(sumarray, weight, data.min(), data.max())
|
189 | 1 | tkerber | self.log("Range of data map: [%f, %f]" % |
190 | 1 | tkerber | (datamap.min(), datamap.max())) |
191 | 1 | tkerber | plot = self._makeplotmap(datamap, weight)
|
192 | 1 | tkerber | #self.log("Range of plot: [%f, %f]" %
|
193 | 1 | tkerber | # (min(plot.flat), max(plot.flat)))
|
194 | 1 | tkerber | examinplot = plot[:] |
195 | 1 | tkerber | examinplot.shape = (plot.shape[0] * plot.shape[1],) + plot.shape[2:] |
196 | 1 | tkerber | self.log("Range of plot: %s -> %s" % |
197 | 1 | tkerber | (str(examinplot.min(0)), str(examinplot.max(0)))) |
198 | 1 | tkerber | del examinplot
|
199 | 1 | tkerber | for device in self.outputdevice: |
200 | 1 | tkerber | device.inform_about_scale(scale) |
201 | 1 | tkerber | device.plotArray(self.n, numpy.swapaxes(plot,0,1)) |
202 | 1 | tkerber | self.n = self.n + 1 |
203 | 1 | tkerber | self.log("FieldPlotter: Finished plotting at " |
204 | 1 | tkerber | + time.strftime("%a, %d %b %Y %H:%M:%S"))
|
205 | 1 | tkerber | self.log("\n\n") |
206 | 1 | tkerber | |
207 | 1 | tkerber | |
208 | 1 | tkerber | def _makedatamap(self, sumarray, weight, minimum, maximum): |
209 | 1 | tkerber | background = numpy.equal(weight, 0)
|
210 | 1 | tkerber | print "Number of background points:", sum(background.flat) |
211 | 1 | tkerber | datamap = sumarray / numpy.where(background, 1, weight)
|
212 | 1 | tkerber | |
213 | 1 | tkerber | if self.background is not None: |
214 | 1 | tkerber | if self.background == "min": |
215 | 1 | tkerber | bg = minimum |
216 | 1 | tkerber | elif self.background == "max": |
217 | 1 | tkerber | bg = maximum |
218 | 1 | tkerber | else:
|
219 | 1 | tkerber | bg = self.background
|
220 | 1 | tkerber | datamap = numpy.where(background, bg, datamap) |
221 | 1 | tkerber | |
222 | 1 | tkerber | if self.autorange == "data": |
223 | 1 | tkerber | datamap = (datamap - minimum) / (maximum - minimum) |
224 | 1 | tkerber | self.log("Autorange using data. Data range is [%f, %f]" |
225 | 1 | tkerber | % (minimum, maximum)) |
226 | 1 | tkerber | elif self.autorange == "plot": |
227 | 1 | tkerber | ma = numpy.where(background, minimum, datamap).max() |
228 | 1 | tkerber | mi = numpy.where(background, maximum, datamap).min() |
229 | 1 | tkerber | datamap = (datamap - mi) / (ma - mi) |
230 | 1 | tkerber | self.log("Autorange using plot. Data range is [%f, %f]" |
231 | 1 | tkerber | % (mi, ma)) |
232 | 1 | tkerber | else:
|
233 | 1 | tkerber | assert self.autorange == None |
234 | 1 | tkerber | datamap = (datamap - self.range[0]) / (self.range[1] |
235 | 1 | tkerber | - self.range[0]) |
236 | 1 | tkerber | datamap = numpy.clip(datamap, 0.0, 1.0) |
237 | 1 | tkerber | self.log("Data range specified by user: [%f, %f]" % self.range) |
238 | 1 | tkerber | datamap = numpy.where(background, bg, datamap) |
239 | 1 | tkerber | assert datamap.min() >= 0 and datamap.max() <= 1.0 |
240 | 1 | tkerber | |
241 | 1 | tkerber | return datamap
|
242 | 1 | tkerber | |
243 | 1 | tkerber | def _makeplotmap(self, datamap, weight): |
244 | 1 | tkerber | plot = numpy.zeros(self.dims + (self.colormode,), numpy.float) |
245 | 1 | tkerber | for i in range(self.dims[0]): |
246 | 1 | tkerber | for j in range(self.dims[1]): |
247 | 1 | tkerber | if self.backgroundcolor is not None and weight[i,j] == 0: |
248 | 1 | tkerber | plot[i,j,:] = self.backgroundcolor
|
249 | 1 | tkerber | else:
|
250 | 1 | tkerber | x = datamap[i,j] |
251 | 1 | tkerber | plot[i,j,:] = self.colorfunction(x)
|
252 | 1 | tkerber | return plot
|
253 | 1 | tkerber | |
254 | 1 | tkerber | class InterpolatingFunction: |
255 | 1 | tkerber | def __init__(self, xpoints, ypoints): |
256 | 1 | tkerber | if len(xpoints) != len(ypoints): |
257 | 1 | tkerber | raise ValueError, "Length of x and y arrays should be the same." |
258 | 1 | tkerber | idx = xpoints.argsort() |
259 | 1 | tkerber | self.xpoints = xpoints[idx]
|
260 | 1 | tkerber | self.ypoints = ypoints[idx]
|
261 | 1 | tkerber | def __call__(self, x): |
262 | 1 | tkerber | n = self.xpoints.searchsorted(x)
|
263 | 1 | tkerber | if n == 0: |
264 | 1 | tkerber | return self.ypoints[0] |
265 | 1 | tkerber | if n == len(self.xpoints): |
266 | 1 | tkerber | return self.xpoints[-1] |
267 | 1 | tkerber | x0 = self.xpoints[n-1] |
268 | 1 | tkerber | x1 = self.xpoints[n]
|
269 | 1 | tkerber | y0 = self.ypoints[n-1] |
270 | 1 | tkerber | y1 = self.ypoints[n]
|
271 | 1 | tkerber | return y0 + (y1 - y0) / (x1 - x0) * (x - x0)
|