segmentation.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
  6. <meta name="generator" content="pdoc 0.5.2" />
  7. <title>analysis.segmentation API documentation</title>
  8. <meta name="description" content="Segmentation algorithm used to identify the different structures
  9. that are formed in the encounter. This file can be called from the
  10. command line to …" />
  11. <link href='https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css' rel='stylesheet'>
  12. <link href='https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/8.0.0/sanitize.min.css' rel='stylesheet'>
  13. <link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css" rel="stylesheet">
  14. <style>.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:30px;overflow:hidden}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:1em 0 .50em 0}h3{font-size:1.4em;margin:25px 0 10px 0}h4{margin:0;font-size:105%}a{color:#058;text-decoration:none;transition:color .3s ease-in-out}a:hover{color:#e82}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900}pre code{background:#f8f8f8;font-size:.8em;line-height:1.4em}code{background:#f2f2f1;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{background:#f8f8f8;border:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0;padding:1ex}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{font-weight:bold}#index h4 + ul{margin-bottom:.6em}#index .two-column{column-count:2}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-weight:bold;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.name small{font-weight:normal}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source summary{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase;cursor:pointer}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{overflow:visible}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}.admonition{padding:.1em .5em}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
  15. <style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.item .name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul{padding-left:1.5em}.toc > ul > li{margin-top:.5em}}</style>
  16. <style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
  17. </head>
  18. <body>
  19. <main>
  20. <article id="content">
  21. <header>
  22. <h1 class="title"><code>analysis.segmentation</code> module</h1>
  23. </header>
  24. <section id="section-intro">
  25. <p>Segmentation algorithm used to identify the different structures
  26. that are formed in the encounter. This file can be called from the
  27. command line to make an illustrative plot of the algorithm.</p>
  28. <details class="source">
  29. <summary>Source code</summary>
  30. <pre><code class="python">&#34;&#34;&#34;Segmentation algorithm used to identify the different structures
  31. that are formed in the encounter. This file can be called from the
  32. command line to make an illustrative plot of the algorithm.
  33. &#34;&#34;&#34;
  34. import numpy as np
  35. import matplotlib.pyplot as plt
  36. import matplotlib.patches as patches
  37. import utils
  38. def segmentEncounter(data, plot=False, mode=&#39;all&#39;):
  39. &#34;&#34;&#34;Segment the encounter into tail, bridge, orbitting and
  40. stolen particles as described in the report.
  41. Parameters:
  42. data: A data instance as saved by the simulation to a pickle file
  43. plot: If true the segmentation will be plotted and shown. Highly
  44. useful for debugging.
  45. mode (string): If mode is &#39;all&#39; all parts of the encounter will be
  46. identified. If mode is &#39;bridge&#39; only the bridge will be
  47. identified. This is useful when there may be no tail.
  48. Returns:
  49. masks (tupple): tupple of array corresponding to the masks of the
  50. (bridge, stolen, orbitting, tail) particles. One can then use
  51. e.g. data[&#39;r_vec&#39;][bridgeMask].
  52. shape (tupple): tupple of (distances, angles) as measured from the
  53. center of mass and with respect to the x axis. They define the
  54. shape of the tail
  55. length (float): total length of the tail.
  56. &#34;&#34;&#34;
  57. nRings = 100 # number of rings to use when segmenting the data
  58. # Localize the central masses
  59. r_vec = data[&#39;r_vec&#39;]
  60. centers = r_vec[data[&#39;type&#39;][:,0]==&#39;center&#39;]
  61. rCenters_vec = centers[1] - centers[0]
  62. rCenters = np.linalg.norm(rCenters_vec)
  63. rCenters_unit = rCenters_vec/np.linalg.norm(rCenters_vec)
  64. # Take particles to be on the tail a priori and
  65. # remove them as they are found in other structures
  66. particlesLeft = np.arange(0, len(r_vec))
  67. if plot:
  68. colour = &#39;#c40f4c&#39;
  69. f, axs = plt.subplots(1, 3, figsize=(9, 4), sharey=False)
  70. f.subplots_adjust(hspace=0, wspace=0)
  71. axs[0].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
  72. axs[0].axis(&#39;equal&#39;)
  73. utils.plotCenterMasses(axs[0], data)
  74. axs[0].axis(&#39;off&#39;)
  75. # Step 1: project points to see if they are part of the bridge
  76. parallelProjection = np.dot(r_vec - centers[0], rCenters_unit)
  77. perpendicularProjection = np.linalg.norm(r_vec - centers[0][np.newaxis]
  78. - parallelProjection[:,np.newaxis] * rCenters_unit[np.newaxis], axis=-1)
  79. bridgeMask = np.logical_and(np.logical_and(0.3*rCenters &lt; parallelProjection,
  80. parallelProjection &lt; .7*rCenters), perpendicularProjection &lt; 2)
  81. # Remove the bridge
  82. notInBridge = np.logical_not(bridgeMask)
  83. r_vec = r_vec[notInBridge]
  84. particlesLeft = particlesLeft[notInBridge]
  85. if mode == &#39;bridge&#39;:
  86. return (bridgeMask, None, None, None), None, None
  87. # Step 2: select stolen particles by checking distance to centers
  88. stolenMask = (np.linalg.norm(r_vec - centers[0][np.newaxis], axis=-1)
  89. &gt; np.linalg.norm(r_vec - centers[1][np.newaxis], axis=-1))
  90. # Remove the stolen part
  91. notStolen = np.logical_not(stolenMask)
  92. r_vec = r_vec[notStolen]
  93. particlesLeft, stolenMask = particlesLeft[notStolen], particlesLeft[stolenMask]
  94. # Step 3: segment data into concentric rings (spherical shells really)
  95. r_vec = r_vec - centers[0]
  96. r = np.linalg.norm(r_vec, axis=-1)
  97. edges = np.linspace(0, 30, nRings) # nRings concentric spheres
  98. indices = np.digitize(r, edges) # Classify particles into shells
  99. if plot:
  100. axs[1].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=.1, s=.1)
  101. axs[1].axis(&#39;equal&#39;)
  102. axs[1].scatter(0, 0, s=100, marker=&#34;*&#34;, c=&#39;black&#39;, alpha=.7)
  103. axs[1].axis(&#39;off&#39;)
  104. # Step 4: find start of tail
  105. start = None
  106. for i in range(1, nRings+1):
  107. rMean = np.mean(r[indices==i])
  108. rMean_vec = np.mean(r_vec[indices==i], axis=0)
  109. parameter = np.linalg.norm(rMean_vec)/rMean
  110. if plot:
  111. circ = patches.Circle((0,0), edges[i-1], linewidth=0.5,edgecolor=&#39;black&#39;,facecolor=&#39;none&#39;, alpha=.7)
  112. axs[1].add_patch(circ)
  113. txtxy = edges[i-1] * np.array([np.sin(i/13), np.cos(i/13)])
  114. axs[1].annotate(&#34;{:.2f}&#34;.format(parameter), xy=txtxy, backgroundcolor=&#39;#ffffff55&#39;)
  115. if start is None and parameter&gt;.8 :
  116. start = i #Here starts the tail
  117. startDirection = rMean_vec/np.linalg.norm(rMean_vec)
  118. if not plot: break;
  119. if start is None: #abort if nothing found
  120. raise Exception(&#39;Could not identify tail&#39;)
  121. # Step 5: remove all circles before start
  122. inInnerRings = indices &lt; start
  123. # Remove particles on the opposite direction to startDirection.
  124. # in the now innermost 5 rings. Likely traces of the bridge.
  125. oppositeDirection = np.dot(r_vec, startDirection) &lt; 0
  126. in5InnermostRings = indices &lt;= start+5
  127. orbitting = np.logical_or(inInnerRings,
  128. np.logical_and(oppositeDirection, in5InnermostRings))
  129. orbittingMask = particlesLeft[orbitting]
  130. r_vec = r_vec[np.logical_not(orbitting)]
  131. tailMask = particlesLeft[np.logical_not(orbitting)]
  132. if plot:
  133. axs[2].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
  134. axs[2].axis(&#39;equal&#39;)
  135. axs[2].scatter(0, 0, s=100, marker=&#34;*&#34;, c=&#39;black&#39;, alpha=.7)
  136. axs[2].axis(&#39;off&#39;)
  137. # Step 6: measure tail length and shape
  138. r = np.linalg.norm(r_vec, axis=-1)
  139. indices = np.digitize(r, edges)
  140. # Make list of barycenters
  141. points = [list(np.mean(r_vec[indices==i], axis=0))
  142. for i in range(1, nRings) if len(r_vec[indices==i])!=0]
  143. points = np.array(points)
  144. # Calculate total length
  145. lengths = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1))
  146. length = np.sum(lengths)
  147. # Shape (for 2D only)
  148. angles = np.arctan2(points[:,1], points[:,0])
  149. distances = np.linalg.norm(points, axis=-1)
  150. shape = (distances, angles)
  151. if plot:
  152. axs[2].plot(points[:,0], points[:,1], c=&#39;black&#39;, linewidth=0.5, marker=&#39;+&#39;)
  153. if plot:
  154. plt.show()
  155. return (bridgeMask, stolenMask, orbittingMask, tailMask), shape, length
  156. if __name__ == &#34;__main__&#34;:
  157. data = utils.loadData(&#39;200mass&#39;, 10400)
  158. segmentEncounter(data, plot=True)</code></pre>
  159. </details>
  160. </section>
  161. <section>
  162. </section>
  163. <section>
  164. </section>
  165. <section>
  166. <h2 class="section-title" id="header-functions">Functions</h2>
  167. <dl>
  168. <dt id="analysis.segmentation.segmentEncounter"><code class="name flex">
  169. <span>def <span class="ident">segmentEncounter</span></span>(<span>data, plot=False, mode=&#39;all&#39;)</span>
  170. </code></dt>
  171. <dd>
  172. <section class="desc"><p>Segment the encounter into tail, bridge, orbitting and
  173. stolen particles as described in the report.</p>
  174. <h2 id="parameters">Parameters</h2>
  175. <dl>
  176. <dt><strong><code>data</code></strong></dt>
  177. <dd>A data instance as saved by the simulation to a pickle file</dd>
  178. <dt><strong><code>plot</code></strong></dt>
  179. <dd>If true the segmentation will be plotted and shown. Highly
  180. useful for debugging.</dd>
  181. <dt><strong><code>mode</code></strong> :&ensp;<code>string</code></dt>
  182. <dd>If mode is 'all' all parts of the encounter will be
  183. identified. If mode is 'bridge' only the bridge will be
  184. identified. This is useful when there may be no tail.</dd>
  185. </dl>
  186. <h2 id="returns">Returns</h2>
  187. <dl>
  188. <dt><strong><code>masks</code></strong> :&ensp;<code>tupple</code></dt>
  189. <dd>tupple of array corresponding to the masks of the
  190. (bridge, stolen, orbitting, tail) particles. One can then use
  191. e.g. data['r_vec'][bridgeMask].</dd>
  192. <dt><strong><code>shape</code></strong> :&ensp;<code>tupple</code></dt>
  193. <dd>tupple of (distances, angles) as measured from the
  194. center of mass and with respect to the x axis. They define the
  195. shape of the tail</dd>
  196. <dt><strong><code>length</code></strong> :&ensp;<code>float</code></dt>
  197. <dd>total length of the tail.</dd>
  198. </dl></section>
  199. <details class="source">
  200. <summary>Source code</summary>
  201. <pre><code class="python">def segmentEncounter(data, plot=False, mode=&#39;all&#39;):
  202. &#34;&#34;&#34;Segment the encounter into tail, bridge, orbitting and
  203. stolen particles as described in the report.
  204. Parameters:
  205. data: A data instance as saved by the simulation to a pickle file
  206. plot: If true the segmentation will be plotted and shown. Highly
  207. useful for debugging.
  208. mode (string): If mode is &#39;all&#39; all parts of the encounter will be
  209. identified. If mode is &#39;bridge&#39; only the bridge will be
  210. identified. This is useful when there may be no tail.
  211. Returns:
  212. masks (tupple): tupple of array corresponding to the masks of the
  213. (bridge, stolen, orbitting, tail) particles. One can then use
  214. e.g. data[&#39;r_vec&#39;][bridgeMask].
  215. shape (tupple): tupple of (distances, angles) as measured from the
  216. center of mass and with respect to the x axis. They define the
  217. shape of the tail
  218. length (float): total length of the tail.
  219. &#34;&#34;&#34;
  220. nRings = 100 # number of rings to use when segmenting the data
  221. # Localize the central masses
  222. r_vec = data[&#39;r_vec&#39;]
  223. centers = r_vec[data[&#39;type&#39;][:,0]==&#39;center&#39;]
  224. rCenters_vec = centers[1] - centers[0]
  225. rCenters = np.linalg.norm(rCenters_vec)
  226. rCenters_unit = rCenters_vec/np.linalg.norm(rCenters_vec)
  227. # Take particles to be on the tail a priori and
  228. # remove them as they are found in other structures
  229. particlesLeft = np.arange(0, len(r_vec))
  230. if plot:
  231. colour = &#39;#c40f4c&#39;
  232. f, axs = plt.subplots(1, 3, figsize=(9, 4), sharey=False)
  233. f.subplots_adjust(hspace=0, wspace=0)
  234. axs[0].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
  235. axs[0].axis(&#39;equal&#39;)
  236. utils.plotCenterMasses(axs[0], data)
  237. axs[0].axis(&#39;off&#39;)
  238. # Step 1: project points to see if they are part of the bridge
  239. parallelProjection = np.dot(r_vec - centers[0], rCenters_unit)
  240. perpendicularProjection = np.linalg.norm(r_vec - centers[0][np.newaxis]
  241. - parallelProjection[:,np.newaxis] * rCenters_unit[np.newaxis], axis=-1)
  242. bridgeMask = np.logical_and(np.logical_and(0.3*rCenters &lt; parallelProjection,
  243. parallelProjection &lt; .7*rCenters), perpendicularProjection &lt; 2)
  244. # Remove the bridge
  245. notInBridge = np.logical_not(bridgeMask)
  246. r_vec = r_vec[notInBridge]
  247. particlesLeft = particlesLeft[notInBridge]
  248. if mode == &#39;bridge&#39;:
  249. return (bridgeMask, None, None, None), None, None
  250. # Step 2: select stolen particles by checking distance to centers
  251. stolenMask = (np.linalg.norm(r_vec - centers[0][np.newaxis], axis=-1)
  252. &gt; np.linalg.norm(r_vec - centers[1][np.newaxis], axis=-1))
  253. # Remove the stolen part
  254. notStolen = np.logical_not(stolenMask)
  255. r_vec = r_vec[notStolen]
  256. particlesLeft, stolenMask = particlesLeft[notStolen], particlesLeft[stolenMask]
  257. # Step 3: segment data into concentric rings (spherical shells really)
  258. r_vec = r_vec - centers[0]
  259. r = np.linalg.norm(r_vec, axis=-1)
  260. edges = np.linspace(0, 30, nRings) # nRings concentric spheres
  261. indices = np.digitize(r, edges) # Classify particles into shells
  262. if plot:
  263. axs[1].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=.1, s=.1)
  264. axs[1].axis(&#39;equal&#39;)
  265. axs[1].scatter(0, 0, s=100, marker=&#34;*&#34;, c=&#39;black&#39;, alpha=.7)
  266. axs[1].axis(&#39;off&#39;)
  267. # Step 4: find start of tail
  268. start = None
  269. for i in range(1, nRings+1):
  270. rMean = np.mean(r[indices==i])
  271. rMean_vec = np.mean(r_vec[indices==i], axis=0)
  272. parameter = np.linalg.norm(rMean_vec)/rMean
  273. if plot:
  274. circ = patches.Circle((0,0), edges[i-1], linewidth=0.5,edgecolor=&#39;black&#39;,facecolor=&#39;none&#39;, alpha=.7)
  275. axs[1].add_patch(circ)
  276. txtxy = edges[i-1] * np.array([np.sin(i/13), np.cos(i/13)])
  277. axs[1].annotate(&#34;{:.2f}&#34;.format(parameter), xy=txtxy, backgroundcolor=&#39;#ffffff55&#39;)
  278. if start is None and parameter&gt;.8 :
  279. start = i #Here starts the tail
  280. startDirection = rMean_vec/np.linalg.norm(rMean_vec)
  281. if not plot: break;
  282. if start is None: #abort if nothing found
  283. raise Exception(&#39;Could not identify tail&#39;)
  284. # Step 5: remove all circles before start
  285. inInnerRings = indices &lt; start
  286. # Remove particles on the opposite direction to startDirection.
  287. # in the now innermost 5 rings. Likely traces of the bridge.
  288. oppositeDirection = np.dot(r_vec, startDirection) &lt; 0
  289. in5InnermostRings = indices &lt;= start+5
  290. orbitting = np.logical_or(inInnerRings,
  291. np.logical_and(oppositeDirection, in5InnermostRings))
  292. orbittingMask = particlesLeft[orbitting]
  293. r_vec = r_vec[np.logical_not(orbitting)]
  294. tailMask = particlesLeft[np.logical_not(orbitting)]
  295. if plot:
  296. axs[2].scatter(r_vec[:,0], r_vec[:,1], c=colour, alpha=0.1, s=0.1)
  297. axs[2].axis(&#39;equal&#39;)
  298. axs[2].scatter(0, 0, s=100, marker=&#34;*&#34;, c=&#39;black&#39;, alpha=.7)
  299. axs[2].axis(&#39;off&#39;)
  300. # Step 6: measure tail length and shape
  301. r = np.linalg.norm(r_vec, axis=-1)
  302. indices = np.digitize(r, edges)
  303. # Make list of barycenters
  304. points = [list(np.mean(r_vec[indices==i], axis=0))
  305. for i in range(1, nRings) if len(r_vec[indices==i])!=0]
  306. points = np.array(points)
  307. # Calculate total length
  308. lengths = np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1))
  309. length = np.sum(lengths)
  310. # Shape (for 2D only)
  311. angles = np.arctan2(points[:,1], points[:,0])
  312. distances = np.linalg.norm(points, axis=-1)
  313. shape = (distances, angles)
  314. if plot:
  315. axs[2].plot(points[:,0], points[:,1], c=&#39;black&#39;, linewidth=0.5, marker=&#39;+&#39;)
  316. if plot:
  317. plt.show()
  318. return (bridgeMask, stolenMask, orbittingMask, tailMask), shape, length</code></pre>
  319. </details>
  320. </dd>
  321. </dl>
  322. </section>
  323. <section>
  324. </section>
  325. </article>
  326. <nav id="sidebar">
  327. <h1>Index</h1>
  328. <div class="toc">
  329. <ul></ul>
  330. </div>
  331. <ul id="index">
  332. <li><h3><a href="#header-functions">Functions</a></h3>
  333. <ul class="">
  334. <li><code><a title="analysis.segmentation.segmentEncounter" href="#analysis.segmentation.segmentEncounter">segmentEncounter</a></code></li>
  335. </ul>
  336. </li>
  337. </ul>
  338. </nav>
  339. </main>
  340. <footer id="footer">
  341. <p>Generated by <a href="https://pdoc3.github.io/pdoc"><cite>pdoc</cite> 0.5.2</a>.</p>
  342. </footer>
  343. <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
  344. <script>hljs.initHighlightingOnLoad()</script>
  345. </body>
  346. </html>