root / ase / visualize / fieldplotter.py @ 4
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)
|