1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """Module for interfacing with Grace (also known as Xmgrace, Xmgr, and ace)."""
25
26
27 from math import ceil, sqrt
28
29
30 from lib.errors import RelaxError
31
32
33
34
35
37 """Write a python "grace to PNG/EPS/SVG..." conversion script..
38
39 The makes a conversion script to image types as PNG/EPS/SVG. The conversion is looping over a directory list of *.agr files, and making function calls to xmgrace. Successful conversion of images depends on the compilation of xmgrace. The input is a list of image types which is wanted, f.ex: PNG EPS SVG. PNG is default.
40
41 @keyword file: The file object to write the data to.
42 @type file: file object
43 """
44
45
46 file.write("#!/usr/bin/env python\n")
47 file.write("#\n")
48 file.write("# This script is used to batch convert the Grace *.agr files into graphics bitmap files using the\n")
49 file.write("# Grace program itself. Therefore you will need to install on your system xmgrace,\n")
50 file.write("# (http://plasma-gate.weizmann.ac.il/Grace/), qtgrace (http://sourceforge.net/projects/qtgrace/)\n")
51 file.write("# or gracegtk (http://sourceforge.net/projects/gracegtk/).\n")
52 file.write("\n")
53 file.write("import glob, os, sys\n")
54 file.write("import shlex, subprocess\n")
55 file.write("import optparse\n")
56 file.write("\n")
57 file.write("# Define a callback function, for a multiple input of PNG, EPS, SVG\n")
58 file.write("def foo_callback(option, opt, value, parser):\n")
59 file.write(" setattr(parser.values, option.dest, value.split(','))\n")
60 file.write("\n")
61 file.write("# Add functioning for argument parsing\n")
62 file.write("parser = optparse.OptionParser(description='Process grace files to images')\n")
63 file.write("# Add argument type. Destination instance is set to types.\n")
64 file.write("parser.add_option('-g', action='store_true', dest='relax_gui', default=False, help='Make it possible to run script through relax GUI. Run by using User-functions -> script')\n")
65 file.write("parser.add_option('-l', action='callback', callback=foo_callback, dest='l', type=\"string\", default=False, help='Make in possible to run scriptif relax has logfile turned on. Run by using User-functions -> script')\n")
66 file.write("parser.add_option('-t', action='callback', callback=foo_callback, dest='types', type=\"string\", default=[], help='List image types for conversion. Execute script with: python %s -t PNG,EPS ...'%(sys.argv[0]))\n")
67 file.write("\n")
68 file.write("# Parse the arguments to a Class instance object\n")
69 file.write("args = parser.parse_args()\n")
70 file.write("\n")
71 file.write("# Lets print help if no arguments are passed\n")
72 file.write("if len(sys.argv) == 1 or len(args[0].types) == 0:\n")
73 file.write(" print('system argument is:', sys.argv)\n")
74 file.write(" parser.print_help()\n")
75 file.write(" print('Performing a default PNG conversion')\n")
76 file.write(" # If no input arguments, we make a default PNG option\n")
77 file.write(" args[0].types = ['PNG']\n")
78 file.write("\n")
79 file.write("# If we run through the GUI we cannot pass input arguments so we make a default PNG option\n")
80 file.write("if args[0].relax_gui:\n")
81 file.write(" args[0].types = ['PNG']\n")
82 file.write("\n")
83 file.write("types = list(args[0].types)\n")
84 file.write("\n")
85 file.write("# A easy search for files with *.agr, is to use glob, which is pathnames matching a specified pattern according to the rules used by the Unix shell, not opening a shell\n")
86 file.write("gracefiles = glob.glob(\"*.agr\")\n")
87 file.write("\n")
88 file.write("# For png conversion, several parameters can be passed to xmgrace. These can be altered later afterwards and the script rerun. \n")
89 file.write("# The option for transparent is good for poster or insertion in color backgrounds. The ability for this still depends on xmgrace compilation\n")
90 file.write("if \"PNG\" in types:\n")
91 file.write(" pngpar = \"png.par\"\n")
92 file.write(" if not os.path.isfile(pngpar):\n")
93 file.write(" wpngpar = open(pngpar, \"w\")\n")
94 file.write(" wpngpar.write(\"DEVICE \\\"PNG\\\" FONT ANTIALIASING on\\n\")\n")
95 file.write(" wpngpar.write(\"DEVICE \\\"PNG\\\" OP \\\"transparent:off\\\"\\n\")\n")
96 file.write(" wpngpar.write(\"DEVICE \\\"PNG\\\" OP \\\"compression:9\\\"\\n\")\n")
97 file.write(" wpngpar.close()\n")
98 file.write("\n")
99 file.write("# Now loop over the grace files\n")
100 file.write("for grace in gracefiles:\n")
101 file.write(" # Get the filename without extension\n")
102 file.write(" fname = grace.split(\".agr\")[0]\n")
103 file.write(" if (\"PNG\" in types or \".PNG\" in types or \"png\" in types or \".png\" in types):\n")
104 file.write(" # Produce the argument string\n")
105 file.write(" im_args = r\"xmgrace -hdevice PNG -hardcopy -param %s -printfile %s.png %s\" % (pngpar, fname, grace)\n")
106 file.write(" # Split the arguments the right way to call xmgrace\n")
107 file.write(" im_args = shlex.split(im_args)\n")
108 file.write(" return_code = subprocess.call(im_args)\n")
109 file.write(" if (\"EPS\" in types or \".EPS\" in types or \"eps\" in types or \".eps\" in types):\n")
110 file.write(" im_args = r\"xmgrace -hdevice EPS -hardcopy -printfile %s.eps %s\" % (fname, grace)\n")
111 file.write(" im_args = shlex.split(im_args)\n")
112 file.write(" return_code = subprocess.call(im_args)\n")
113 file.write(" if (\"JPG\" in types or \".JPG\" in types or \"jpg\" in types or \".jpg\" in types):\n")
114 file.write(" im_args = r\"xmgrace -hdevice JPEG -hardcopy -printfile %s.jpg %s\" % (fname, grace)\n")
115 file.write(" im_args = shlex.split(im_args)\n")
116 file.write(" if (\"JPEG\" in types or \".JPEG\" in types or \"jpeg\" in types or \".jpeg\" in types):\n")
117 file.write(" im_args = r\"xmgrace -hdevice JPEG -hardcopy -printfile %s.jpg %s\" % (fname, grace)\n")
118 file.write(" im_args = shlex.split(im_args)\n")
119 file.write(" return_code = subprocess.call(im_args)\n")
120 file.write(" if (\"SVG\" in types or \".SVG\" in types or \"svg\" in types or \".svg\" in types):\n")
121 file.write(" im_args = r\"xmgrace -hdevice SVG -hardcopy -printfile %s.svg %s\" % (fname, grace)\n")
122 file.write(" im_args = shlex.split(im_args)\n")
123 file.write(" return_code = subprocess.call(im_args)\n")
124
125
126 -def write_xy_data(data, file=None, graph_type=None, norm_type='first', norm=None, autoscale=True):
127 """Write the data into the Grace xy-scatter plot.
128
129 The numerical data should be supplied as a 4 dimensional list or array object. The first dimension corresponds to the graphs, Gx. The second corresponds the sets of each graph, Sx. The third corresponds to the data series (i.e. each data point). The forth is a list of the information about each point, it is a list where the first element is the x value, the second is the y value, the third is the optional dx or dy error (either dx or dy dependent upon the graph_type arg), and the forth is the optional dy error when graph_type is xydxdy (the third position is then dx).
130
131
132 @param data: The 4D structure of numerical data to graph (see docstring).
133 @type data: list of lists of lists of float
134 @keyword file: The file object to write the data to.
135 @type file: file object
136 @keyword graph_type: The graph type which can be one of xy, xydy, xydx, or xydxdy.
137 @type graph_type: str
138 @keyword norm_type: The point to normalise to 1. This can be 'first' or 'last'.
139 @type norm_type: str
140 @keyword norm: The normalisation flag which if set to True will cause all graphs to be normalised to 1. The first dimension is the graph.
141 @type norm: None or list of bool
142 @keyword autoscale: A flag which if True will cause the world view of each graph to be autoscaled (by placing the Grace command "@autoscale" at the end of the file). If you have supplied a world view for the header or the tick spacing, this argument should be set to False to prevent that world view from being overwritten.
143 @type autoscale: bool
144 """
145
146
147 graph_num = len(data)
148
149
150 if not norm:
151 norm = []
152 for gi in range(graph_num):
153 norm.append(False)
154
155
156 comment_col = 2
157 if graph_type in ['xydx', 'xydy']:
158 comment_col = 3
159 elif graph_type == 'xydxdy':
160 comment_col = 4
161
162
163 for gi in range(graph_num):
164
165 for si in range(len(data[gi])):
166
167 file.write("@target G%s.S%s\n" % (gi, si))
168 file.write("@type %s\n" % graph_type)
169
170
171 norm_fact = 1.0
172 if norm[gi]:
173 if norm_type == 'first':
174 norm_fact = data[gi][si][0][1]
175 elif norm_type == 'last':
176 norm_fact = data[gi][si][-1][1]
177 else:
178 raise RelaxError("The normalisation type '%s' must be one of ['first', 'last']." % norm_fact)
179
180
181 for point in data[gi][si]:
182
183 if point[0] == None or point[1] == None:
184 continue
185
186
187 file.write("%30.15f %30.15f" % (point[0], point[1]/norm_fact))
188
189
190 if graph_type in ['xydx', 'xydy', 'xydxdy']:
191
192 if len(point) < 3:
193 error = None
194 else:
195 error = point[2]
196
197 if error == None:
198 error = 0.0
199
200
201 file.write(" %30.15f" % (error/norm_fact))
202
203
204 if graph_type == 'xydxdy':
205
206 error = point[3]
207 if error == None:
208 error = 0.0
209
210
211 file.write(" %30.15f" % (error/norm_fact))
212
213
214 try:
215 file.write(" \"%s\"" % point[comment_col])
216 except IndexError:
217 pass
218
219
220 file.write("\n")
221
222
223 file.write("&\n")
224
225
226 if autoscale:
227 for i in range(graph_num):
228 file.write("@with g%i\n" % i)
229 file.write("@autoscale\n")
230
231
232 if len(data) > 1:
233 row_num = int(round(sqrt(len(data))))
234 col_num = int(ceil(sqrt(len(data))))
235 file.write("@arrange(%i, %i, .1, .1, .1, OFF, OFF, OFF)\n" % (row_num, col_num))
236
237
239 """Write the grace header for xy-scatter plots.
240
241 Many of these keyword arguments should be supplied in a [X, Y] list format, where the first element corresponds to the X data, and the second the Y data. Defaults will be used for any non-supplied args (or lists with elements set to None).
242
243
244 @keyword file: The file object to write the data to.
245 @type file: file object
246 @keyword paper_size: The paper size, i.e. 'A4'. If set to None, this will default to letter size.
247 @type paper_size: str
248 @keyword title: The title of the graph.
249 @type title: None or str
250 @keyword subtitle: The sub-title of the graph.
251 @type subtitle: None or str
252 @keyword world: The Grace plot default zoom. This consists of a list of the X-axis minimum, Y-axis minimum, X-axis maximum, and Y-axis maximum values. Each graph should supply its own world view.
253 @type world: Nor or list of list of numbers
254 @keyword view: List of 4 coordinates defining the graph view port.
255 @type view: None or list of float
256 @keyword graph_num: The total number of graphs.
257 @type graph_num: int
258 @keyword sets: The number of data sets in each graph.
259 @type sets: list of int
260 @keyword set_names: The names associated with each graph data set Gx.Sy. For example this can be a list of spin identification strings. The first dimension is the graph, the second is the set.
261 @type set_names: None or list of list of str
262 @keyword set_colours: The colours for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
263 @type set_colours: None or list of list of int
264 @keyword x_axis_type_zero: The flags specifying if the X-axis should be placed at zero.
265 @type x_axis_type_zero: None or list of lists of bool
266 @keyword y_axis_type_zero: The flags specifying if the Y-axis should be placed at zero.
267 @type y_axis_type_zero: None or list of lists of bool
268 @keyword symbols: The symbol style for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
269 @type symbols: None or list of list of int
270 @keyword symbol_sizes: The symbol size for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
271 @type symbol_sizes: None or list of list of int
272 @keyword symbol_fill: The symbol file style for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
273 @type symbol_fill: None or list of list of int
274 @keyword linestyle: The line style for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
275 @type linestyle: None or list of list of int
276 @keyword linetype: The line type for each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
277 @type linetype: None or list of list of int
278 @keyword linewidth: The line width for all elements of each graph data set Gx.Sy. The first dimension is the graph, the second is the set.
279 @type linewidth: None or list of float
280 @keyword data_type: The axis data category (in the [X, Y] list format).
281 @type data_type: None or list of list of str
282 @keyword seq_type: The sequence data type (in the [X, Y] list format). This is for molecular sequence specific data and can be one of 'res', 'spin', or 'mixed'.
283 @type seq_type: None or list of list of str
284 @keyword tick_major_spacing: The spacing between major ticks. This is in the [X, Y] list format whereby the first dimension corresponds to the graph number.
285 @type tick_major_spacing: None or list of list of numbers
286 @keyword tick_minor_count: The number of minor ticks between the major ticks. This is in the [X, Y] list format whereby the first dimension corresponds to the graph number.
287 @type tick_minor_count: None or list of list of int
288 @keyword axis_labels: The labels for the axes (in the [X, Y] list format). The first dimension is the graph.
289 @type axis_labels: None or list of list of str
290 @keyword legend: If True, the legend will be visible. The first dimension is the graph.
291 @type legend: list of bool
292 @keyword legend_pos: The position of the legend, e.g. [0.3, 0.8]. The first dimension is the graph.
293 @type legend_pos: None or list of list of float
294 @keyword legend_box_fill_pattern: The legend box fill. If set to 0, it will become transparent.
295 @type legend_box_fill_pattern: int
296 @keyword legend_char_size: The size of the legend box text.
297 @type legend_char_size: float
298 @keyword norm: The normalisation flag which if set to True will cause all graphs to be normalised to 1. The first dimension is the graph.
299 @type norm: list of bool
300 """
301
302
303 if sets == None:
304 sets = []
305 for gi in range(graph_num):
306 sets.append(1)
307 if x_axis_type_zero == None:
308 x_axis_type_zero = []
309 for gi in range(graph_num):
310 x_axis_type_zero.append(False)
311 if y_axis_type_zero == None:
312 y_axis_type_zero = []
313 for gi in range(graph_num):
314 y_axis_type_zero.append(False)
315 if linewidth == None:
316 linewidth = []
317 for gi in range(graph_num):
318 linewidth.append(0.5)
319 if norm == None:
320 norm = []
321 for gi in range(graph_num):
322 norm.append(False)
323 if legend == None:
324 legend = []
325 for gi in range(graph_num):
326 legend.append(True)
327 if not legend_box_fill_pattern:
328 legend_box_fill_pattern = []
329 for gi in range(graph_num):
330 legend_box_fill_pattern.append(1)
331 if not legend_char_size:
332 legend_char_size = []
333 for gi in range(graph_num):
334 legend_char_size.append(1.0)
335
336
337 if not data_type:
338 data_type = [None, None]
339 if not seq_type:
340 seq_type = [None, None]
341 if not axis_labels:
342 axis_labels = []
343 for gi in range(graph_num):
344 axis_labels.append([None, None])
345
346
347 file.write("@version 50121\n")
348
349
350 if paper_size == 'A4':
351 file.write("@page size 842, 595\n")
352
353
354 for gi in range(graph_num):
355
356 file.write("@with g%i\n" % gi)
357
358
359 if world:
360 file.write("@ world %s, %s, %s, %s\n" % (world[gi][0], world[gi][1], world[gi][2], world[gi][3]))
361
362
363 if not view:
364 view = [0.15, 0.15, 1.28, 0.85]
365 file.write("@ view %s, %s, %s, %s\n" % (view[0], view[1], view[2], view[3]))
366
367
368 if title:
369 file.write("@ title \"%s\"\n" % title)
370 if subtitle:
371 file.write("@ subtitle \"%s\"\n" % subtitle)
372
373
374 if x_axis_type_zero[gi]:
375 file.write("@ xaxis type zero true\n")
376 if y_axis_type_zero[gi]:
377 file.write("@ yaxis type zero true\n")
378
379
380 axes = ['x', 'y']
381 for i in range(2):
382
383 if data_type[i] == 'spin':
384
385 if seq_type[i] == 'res':
386
387 if not axis_labels[gi][i]:
388 axis_labels[gi][i] = "Residue number"
389
390
391 if seq_type[i] == 'spin':
392
393 if not axis_labels[gi][i]:
394 axis_labels[gi][i] = "Spin number"
395
396
397 if seq_type[i] == 'mixed':
398
399 if not axis_labels[gi][i]:
400 axis_labels[gi][i] = "Spin ID string"
401
402
403 if axis_labels[gi][i]:
404 file.write("@ %saxis label \"%s\"\n" % (axes[i], axis_labels[gi][i]))
405 file.write("@ %saxis label char size 1.00\n" % axes[i])
406 if tick_major_spacing != None:
407 file.write("@ %saxis tick major %s\n" % (axes[i], tick_major_spacing[gi][i]))
408 file.write("@ %saxis tick major size 0.50\n" % axes[i])
409 file.write("@ %saxis tick major linewidth %s\n" % (axes[i], linewidth[gi]))
410 if tick_minor_count != None:
411 file.write("@ %saxis tick minor ticks %s\n" % (axes[i], tick_minor_count[gi][i]))
412 file.write("@ %saxis tick minor linewidth %s\n" % (axes[i], linewidth[gi]))
413 file.write("@ %saxis tick minor size 0.25\n" % axes[i])
414 file.write("@ %saxis ticklabel char size 0.70\n" % axes[i])
415
416
417 if legend != None and legend[gi]:
418 file.write("@ legend on\n")
419 else:
420 file.write("@ legend off\n")
421 if legend_pos != None:
422 file.write("@ legend %s, %s\n" % (legend_pos[gi][0], legend_pos[gi][1]))
423 file.write("@ legend box fill pattern %s\n" % legend_box_fill_pattern[gi])
424 file.write("@ legend char size %s\n" % legend_char_size[gi])
425
426
427 file.write("@ frame linewidth %s\n" % linewidth[gi])
428
429
430 for i in range(sets[gi]):
431
432 if symbols:
433 file.write("@ s%i symbol %i\n" % (i, symbols[gi][i]))
434 else:
435
436 num = i % 10 + 1
437
438
439 file.write("@ s%i symbol %i\n" % (i, num))
440
441
442 if symbol_sizes:
443 file.write("@ s%i symbol size %s\n" % (i, symbol_sizes[gi][i]))
444 else:
445 file.write("@ s%i symbol size 0.45\n" % i)
446
447
448 if symbol_fill:
449 file.write("@ s%i symbol fill pattern %i\n" % (i, symbol_fill[gi][i]))
450
451
452 file.write("@ s%i symbol linewidth %s\n" % (i, linewidth[gi]))
453
454
455 if set_colours:
456 file.write("@ s%i symbol color %s\n" % (i, set_colours[gi][i]))
457 file.write("@ s%i symbol fill color %s\n" % (i, set_colours[gi][i]))
458
459
460 file.write("@ s%i errorbar size 0.5\n" % i)
461 file.write("@ s%i errorbar linewidth %s\n" % (i, linewidth[gi]))
462 file.write("@ s%i errorbar riser linewidth %s\n" % (i, linewidth[gi]))
463
464
465 if linestyle:
466 file.write("@ s%i line linestyle %s\n" % (i, linestyle[gi][i]))
467
468
469 if linetype:
470 file.write("@ s%i line type %s\n" % (i, linetype[gi][i]))
471
472
473 if set_colours:
474 file.write("@ s%i line color %s\n" % (i, set_colours[gi][i]))
475 file.write("@ s%i fill color %s\n" % (i, set_colours[gi][i]))
476 file.write("@ s%i avalue color %s\n" % (i, set_colours[gi][i]))
477 file.write("@ s%i errorbar color %s\n" % (i, set_colours[gi][i]))
478
479
480 if set_names and len(set_names) and len(set_names[gi]) and set_names[gi][i]:
481 file.write("@ s%i legend \"%s\"\n" % (i, set_names[gi][i]))
482