/tools/mutation/visualize.py

https://bitbucket.org/cistrome/cistrome-harvard/ · Python · 391 lines · 357 code · 26 blank · 8 comment · 26 complexity · d82a8d907b004b44f87141ac629babfe MD5 · raw file

  1. #!/usr/bin/env python
  2. '''
  3. Mutation Visualizer tool
  4. '''
  5. from __future__ import division
  6. import sys, csv, os, math
  7. import optparse
  8. from galaxy import eggs
  9. import pkg_resources
  10. pkg_resources.require( "SVGFig" )
  11. import svgfig as svg
  12. SVGPan = """
  13. /**
  14. * SVGPan library 1.2
  15. * ====================
  16. *
  17. * Given an unique existing element with id "viewport", including the
  18. * the library into any SVG adds the following capabilities:
  19. *
  20. * - Mouse panning
  21. * - Mouse zooming (using the wheel)
  22. * - Object dargging
  23. *
  24. * Known issues:
  25. *
  26. * - Zooming (while panning) on Safari has still some issues
  27. *
  28. * Releases:
  29. *
  30. * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui
  31. * Fixed a bug with browser mouse handler interaction
  32. *
  33. * 1.1, Wed Feb 3 17:39:33 GMT 2010, Zeng Xiaohui
  34. * Updated the zoom code to support the mouse wheel on Safari/Chrome
  35. *
  36. * 1.0, Andrea Leofreddi
  37. * First release
  38. *
  39. * This code is licensed under the following BSD license:
  40. *
  41. * Copyright 2009-2010 Andrea Leofreddi (a.leofreddi@itcharm.com). All rights reserved.
  42. *
  43. * Redistribution and use in source and binary forms, with or without modification, are
  44. * permitted provided that the following conditions are met:
  45. *
  46. * 1. Redistributions of source code must retain the above copyright notice, this list of
  47. * conditions and the following disclaimer.
  48. *
  49. * 2. Redistributions in binary form must reproduce the above copyright notice, this list
  50. * of conditions and the following disclaimer in the documentation and/or other materials
  51. * provided with the distribution.
  52. *
  53. * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED
  54. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  55. * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR
  56. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  57. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  58. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  59. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  60. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  61. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  62. *
  63. * The views and conclusions contained in the software and documentation are those of the
  64. * authors and should not be interpreted as representing official policies, either expressed
  65. * or implied, of Andrea Leofreddi.
  66. */
  67. var root = document.documentElement;
  68. var state = 'none', stateTarget, stateOrigin, stateTf;
  69. setupHandlers(root);
  70. /**
  71. * Register handlers
  72. */
  73. function setupHandlers(root){
  74. setAttributes(root, {
  75. "onmouseup" : "add(evt)",
  76. "onmousedown" : "handleMouseDown(evt)",
  77. "onmousemove" : "handleMouseMove(evt)",
  78. "onmouseup" : "handleMouseUp(evt)",
  79. //"onmouseout" : "handleMouseUp(evt)", // Decomment this to stop the pan functionality when dragging out of the SVG element
  80. });
  81. if(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)
  82. window.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari
  83. else
  84. window.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others
  85. }
  86. /**
  87. * Instance an SVGPoint object with given event coordinates.
  88. */
  89. function getEventPoint(evt) {
  90. var p = root.createSVGPoint();
  91. p.x = evt.clientX;
  92. p.y = evt.clientY;
  93. return p;
  94. }
  95. /**
  96. * Sets the current transform matrix of an element.
  97. */
  98. function setCTM(element, matrix) {
  99. var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
  100. element.setAttribute("transform", s);
  101. }
  102. /**
  103. * Dumps a matrix to a string (useful for debug).
  104. */
  105. function dumpMatrix(matrix) {
  106. var s = "[ " + matrix.a + ", " + matrix.c + ", " + matrix.e + "\\n " + matrix.b + ", " + matrix.d + ", " + matrix.f + "\\n 0, 0, 1 ]";
  107. return s;
  108. }
  109. /**
  110. * Sets attributes of an element.
  111. */
  112. function setAttributes(element, attributes){
  113. for (i in attributes)
  114. element.setAttributeNS(null, i, attributes[i]);
  115. }
  116. /**
  117. * Handle mouse move event.
  118. */
  119. function handleMouseWheel(evt) {
  120. if(evt.preventDefault)
  121. evt.preventDefault();
  122. evt.returnValue = false;
  123. var svgDoc = evt.target.ownerDocument;
  124. var delta;
  125. if(evt.wheelDelta)
  126. delta = evt.wheelDelta / 3600; // Chrome/Safari
  127. else
  128. delta = evt.detail / -90; // Mozilla
  129. var z = 1 + delta; // Zoom factor: 0.9/1.1
  130. var g = svgDoc.getElementById("viewport");
  131. var p = getEventPoint(evt);
  132. p = p.matrixTransform(g.getCTM().inverse());
  133. // Compute new scale matrix in current mouse position
  134. var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
  135. setCTM(g, g.getCTM().multiply(k));
  136. stateTf = stateTf.multiply(k.inverse());
  137. }
  138. /**
  139. * Handle mouse move event.
  140. */
  141. function handleMouseMove(evt) {
  142. if(evt.preventDefault)
  143. evt.preventDefault();
  144. evt.returnValue = false;
  145. var svgDoc = evt.target.ownerDocument;
  146. var g = svgDoc.getElementById("viewport");
  147. if(state == 'pan') {
  148. // Pan mode
  149. var p = getEventPoint(evt).matrixTransform(stateTf);
  150. setCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));
  151. } else if(state == 'move') {
  152. // Move mode
  153. var p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());
  154. setCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));
  155. stateOrigin = p;
  156. }
  157. }
  158. /**
  159. * Handle click event.
  160. */
  161. function handleMouseDown(evt) {
  162. if(evt.preventDefault)
  163. evt.preventDefault();
  164. evt.returnValue = false;
  165. var svgDoc = evt.target.ownerDocument;
  166. var g = svgDoc.getElementById("viewport");
  167. if(evt.target.tagName == "svg") {
  168. // Pan mode
  169. state = 'pan';
  170. stateTf = g.getCTM().inverse();
  171. stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
  172. }
  173. /*else {
  174. // Move mode
  175. state = 'move';
  176. stateTarget = evt.target;
  177. stateTf = g.getCTM().inverse();
  178. stateOrigin = getEventPoint(evt).matrixTransform(stateTf);
  179. }*/
  180. }
  181. /**
  182. * Handle mouse button release event.
  183. */
  184. function handleMouseUp(evt) {
  185. if(evt.preventDefault)
  186. evt.preventDefault();
  187. evt.returnValue = false;
  188. var svgDoc = evt.target.ownerDocument;
  189. if(state == 'pan' || state == 'move') {
  190. // Quit pan mode
  191. state = '';
  192. }
  193. }
  194. """
  195. COLS_PER_SAMPLE = 7
  196. HEADER_COLS = 4
  197. HEIGHT = 6
  198. WIDTH = 12
  199. BAR_WIDTH = 1.5
  200. GAP = 2
  201. colors = {'A':'blue', 'C':'green', 'G':'orange', 'T':'red'}
  202. bases = ['A', 'C', 'G', 'T' ]
  203. def stop_error(message):
  204. print >> sys.stderr, message
  205. sys.exit(1)
  206. def validate_bases(n_a, n_c, n_g, n_t, total):
  207. if n_a > total:
  208. return 'A'
  209. elif n_c > total:
  210. return 'C'
  211. elif n_g > total:
  212. return 'G'
  213. elif n_t > total:
  214. return 'T'
  215. return None
  216. def main(opts, args):
  217. s = svg.SVG('g', id='viewport')
  218. # display legend
  219. for i, b in enumerate( bases ):
  220. bt = svg.SVG("tspan", b, style="font-family:Verdana;font-size:20%")
  221. s.append(svg.SVG("text", bt, x=12+(i*10), y=3, stroke="none", fill="black"))
  222. s.append(svg.SVG("rect", x=14+(i*10), y=0, width=4, height=3,
  223. stroke="none", fill=colors[b], fill_opacity=0.5))
  224. reader = open(opts.input_file, 'U')
  225. samples = []
  226. for i in range(int(len(args)/3)):
  227. index = i*3
  228. samples.append(dict(name=args[index],
  229. a_col=args[index+1],
  230. totals_col=args[index+2]))
  231. if opts.zoom == 'interactive':
  232. y = 35
  233. else:
  234. y = 25
  235. for i, sample in enumerate(samples):
  236. x = 23+(i*(WIDTH+GAP))
  237. t = svg.SVG("text", svg.SVG("tspan", sample['name'], style="font-family:Verdana;font-size:25%"),
  238. x=x, y=y, transform="rotate(-90 %i,%i)" % (x, y), stroke="none", fill="black")
  239. s.append(t)
  240. count=1
  241. for line in reader:
  242. row = line.split('\t')
  243. highlighted_position = False
  244. show_pos = True
  245. position = row[int(opts.position_col)-1]
  246. ref = row[int(opts.ref_col)-1].strip().upper()
  247. # validate
  248. if ref not in bases:
  249. stop_error( "The reference column (col%s) contains invalid character '%s' at row %i of the dataset." % ( opts.ref_col, ref, count ) )
  250. # display positions
  251. if opts.zoom == 'interactive':
  252. textx = 0
  253. else:
  254. textx = 7
  255. bt = svg.SVG("tspan", str(position), style="font-family:Verdana;font-size:25%")
  256. s.append(svg.SVG("text", bt, x=textx, y=34+(count*(HEIGHT+GAP)), stroke="none", fill="black"))
  257. s.append(svg.SVG("rect", x=0, y=30+(count*(HEIGHT+GAP)), width=14, height=HEIGHT,
  258. stroke='none', fill=colors[ref.upper()], fill_opacity=0.2))
  259. for sample_index, sample in enumerate(samples):
  260. n_a = int(row[int(sample['a_col'])-1])
  261. n_c = int(row[int(sample['a_col'])+1-1])
  262. n_g = int(row[int(sample['a_col'])+2-1])
  263. n_t = int(row[int(sample['a_col'])+3-1])
  264. total = int(row[int(sample['totals_col'])-1])
  265. # validate
  266. base_error = validate_bases(n_a, n_c, n_g, n_t, total)
  267. if base_error:
  268. stop_error("For sample %i (%s), the number of base %s reads is more than the coverage on row %i." % (sample_index+1,
  269. sample['name'],
  270. base_error,
  271. count))
  272. if total:
  273. x = 16+(sample_index*(WIDTH+GAP))
  274. y = 30+(count*(HEIGHT+GAP))
  275. width = WIDTH
  276. height = HEIGHT
  277. if count%2:
  278. s.append(svg.SVG("rect", x=x, y=y, width=width, height=height,
  279. stroke='none', fill='grey', fill_opacity=0.25))
  280. else:
  281. s.append(svg.SVG("rect", x=x, y=y, width=width, height=height,
  282. stroke='none', fill='grey', fill_opacity=0.25))
  283. for base, value in enumerate([n_a, n_c, n_g, n_t]):
  284. width = int(math.ceil(value / total * WIDTH))
  285. s.append(svg.SVG("rect", x=x, y=y, width=width, height=BAR_WIDTH,
  286. stroke='none', fill=colors[bases[base]], fill_opacity=0.6))
  287. y = y + BAR_WIDTH
  288. count=count+1
  289. if opts.zoom == 'interactive':
  290. canv = svg.canvas(s)
  291. canv.save(opts.output_file)
  292. import fileinput
  293. flag = False
  294. for line in fileinput.input(opts.output_file, inplace=1):
  295. if line.startswith('<svg'):
  296. print '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">'
  297. flag = True
  298. continue
  299. else:
  300. if flag:
  301. print '<script type="text/javascript">%s</script>' % SVGPan
  302. flag = False
  303. print line,
  304. else:
  305. zoom = int(opts.zoom)
  306. w = "%ipx" % (x*(10+zoom))
  307. h = "%ipx" % (y*(2+zoom))
  308. canv = svg.canvas(s, width=w, height=h, viewBox="0 0 %i %i" %(x+100, y+100))
  309. canv.save(opts.output_file)
  310. if __name__ == '__main__':
  311. parser = optparse.OptionParser()
  312. parser.add_option('-i', '--input-file', dest='input_file', action='store')
  313. parser.add_option('-o', '--output-file', dest='output_file', action='store')
  314. parser.add_option('-z', '--zoom', dest='zoom', action='store', default='1')
  315. parser.add_option('-p', '--position_col', dest='position_col', action='store', default='c0')
  316. parser.add_option('-r', '--ref_col', dest='ref_col', action='store', default='c1')
  317. (opts, args) = parser.parse_args()
  318. main(opts, args)
  319. sys.exit(1)