Skip to content

Commit b8d6b49

Browse files
author
JonasHartmann
committed
Initial commit: added Track Tree pipeline along with example data generator, example data and README.
0 parents  commit b8d6b49

8 files changed

+1721
-0
lines changed

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Track Tree - Epic Tree Timecourse Visualization
2+
3+
by Jonas Hartmann, Gilmour group, EMBL Heidelberg
4+
5+
----
6+
7+
**Info:**
8+
9+
This pipeline reads time course data of a tracked object that divides into multiple objects over time (for example a cell as observed with a fluorescence microscope and tracked by image analysis) and plots the track data as a tree, visualizing measurements as an outline and/or through coloring.
10+
11+
See `About` section in `track_tree.ipynb` for more information!
12+
13+
----
14+
15+
**Example 1:** Tree with outline and color to represent track measurements
16+
17+
![Example Track Tree with color](example_tree_color.png)
18+
19+
20+
**Example 2:** Example tree with two outlines to represent track measurements
21+
22+
![Example Track Tree with double-outlines](example_tree_outline.png)
23+
24+
25+
26+
27+
28+
29+

data_gen.ipynb

+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Track Tree - Example Data Generation\n",
8+
"\n",
9+
"----\n",
10+
"\n",
11+
"This generates some random example data to illustrate the \"track tree\" visualization. The values for the two measurement tracks are generated by a pink noise generator. The tree structure is also randomly generated (in a very awkward way that allows very little control). Have fun. "
12+
]
13+
},
14+
{
15+
"cell_type": "markdown",
16+
"metadata": {},
17+
"source": [
18+
"### Prep"
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": 1,
24+
"metadata": {
25+
"collapsed": true
26+
},
27+
"outputs": [],
28+
"source": [
29+
"### Import modules\n",
30+
"from __future__ import division\n",
31+
"import os, sys\n",
32+
"import warnings\n",
33+
"import numpy as np\n",
34+
"import matplotlib.pyplot as plt"
35+
]
36+
},
37+
{
38+
"cell_type": "code",
39+
"execution_count": 2,
40+
"metadata": {
41+
"collapsed": true
42+
},
43+
"outputs": [],
44+
"source": [
45+
"### Seed random generator\n",
46+
"np.random.seed(5)"
47+
]
48+
},
49+
{
50+
"cell_type": "markdown",
51+
"metadata": {},
52+
"source": [
53+
"### Tree Generation"
54+
]
55+
},
56+
{
57+
"cell_type": "code",
58+
"execution_count": 3,
59+
"metadata": {
60+
"collapsed": true
61+
},
62+
"outputs": [],
63+
"source": [
64+
"### Function to generate two branches from a stem\n",
65+
"\n",
66+
"def make_branches(ID, IDs):\n",
67+
" \n",
68+
" # First branch\n",
69+
" new_ID_1 = IDs[-1] + 1\n",
70+
" tree[new_ID_1] = {'stem' : ID,\n",
71+
" 'branches' : '_none_'}\n",
72+
" IDs.append(new_ID_1)\n",
73+
" \n",
74+
" # Second branch\n",
75+
" new_ID_2 = IDs[-1] + 1\n",
76+
" tree[new_ID_2] = {'stem' : ID,\n",
77+
" 'branches' : '_none_'}\n",
78+
" IDs.append(new_ID_2)\n",
79+
" \n",
80+
" # Update stem\n",
81+
" tree[ID]['branches'] = [new_ID_1, new_ID_2]"
82+
]
83+
},
84+
{
85+
"cell_type": "code",
86+
"execution_count": 4,
87+
"metadata": {
88+
"collapsed": false
89+
},
90+
"outputs": [],
91+
"source": [
92+
"### Generate the tree\n",
93+
"\n",
94+
"# Parameters\n",
95+
"max_iter = 10\n",
96+
"p_branch = 0.55\n",
97+
"\n",
98+
"# Start by generating the root \n",
99+
"tree = { 0 : {'stem' : '_root_',\n",
100+
" 'branches' : '_none_'} }\n",
101+
"\n",
102+
"# Keep track of IDs\n",
103+
"IDs = [0]\n",
104+
"\n",
105+
"# Iterate\n",
106+
"for iterstep in range(max_iter):\n",
107+
" \n",
108+
" # For each existing stem...\n",
109+
" for ID in tree.keys():\n",
110+
" \n",
111+
" # If it can create branches...\n",
112+
" if tree[ID]['branches'] == '_none_':\n",
113+
" \n",
114+
" # Randomly decide if it branches\n",
115+
" if np.random.binomial(1, p_branch) or tree[ID]['stem'] == '_root_':\n",
116+
" \n",
117+
" # Create the new branches\n",
118+
" make_branches(ID, IDs)\n",
119+
" \n",
120+
" # Otherwise, make it a leaf\n",
121+
" else: \n",
122+
" tree[ID]['branches'] = ['_leaf_', '_leaf_']\n",
123+
" \n",
124+
"# Clean up final leaves (in case max_iter was reached)\n",
125+
"for ID in tree.keys():\n",
126+
" if tree[ID]['branches'] == '_none_':\n",
127+
" tree[ID]['branches'] = ['_leaf_', '_leaf_']"
128+
]
129+
},
130+
{
131+
"cell_type": "markdown",
132+
"metadata": {},
133+
"source": [
134+
"### Track Generation: Indices"
135+
]
136+
},
137+
{
138+
"cell_type": "code",
139+
"execution_count": 5,
140+
"metadata": {
141+
"collapsed": false
142+
},
143+
"outputs": [],
144+
"source": [
145+
"### Generate length & indices of all tracks\n",
146+
"\n",
147+
"# Parameters\n",
148+
"min_len = 10\n",
149+
"max_len = 100\n",
150+
"\n",
151+
"# Function to recursively assign lenghts / indices\n",
152+
"def generate_indices(ID):\n",
153+
" \n",
154+
" # Randomly generate length\n",
155+
" track_len = np.random.randint(min_len, max_len)\n",
156+
" \n",
157+
" # Generate indices for the root (count from zero)\n",
158+
" if tree[ID]['stem'] == '_root_':\n",
159+
" track_indices = np.arange(0, track_len)\n",
160+
" \n",
161+
" # Generate indices for branches/leaves (count from stem position)\n",
162+
" else:\n",
163+
" track_indices = np.arange(1, track_len+1) + tree[tree[ID]['stem']]['indices'][-1]\n",
164+
" \n",
165+
" # Add indices to tree\n",
166+
" tree[ID]['indices'] = track_indices\n",
167+
" \n",
168+
" # Generate indices for the branches (recursion)\n",
169+
" if tree[ID]['branches'][0] != '_leaf_':\n",
170+
" generate_indices(tree[ID]['branches'][0])\n",
171+
" generate_indices(tree[ID]['branches'][1])\n",
172+
" \n",
173+
"# Run the function\n",
174+
"generate_indices(0)"
175+
]
176+
},
177+
{
178+
"cell_type": "markdown",
179+
"metadata": {},
180+
"source": [
181+
"### Track Generation: Measurements"
182+
]
183+
},
184+
{
185+
"cell_type": "code",
186+
"execution_count": 6,
187+
"metadata": {
188+
"collapsed": false
189+
},
190+
"outputs": [],
191+
"source": [
192+
"### Function to generate pink-noise\n",
193+
"# Adapted from Allen B. Downey,\n",
194+
"# github.com/AllenDowney/ThinkDSP/blob/master/code/voss.ipynb\n",
195+
"# I removed the pandas dependency (at the cost of speed and scaling...)\n",
196+
"\n",
197+
"def voss(nrows, ncols=16):\n",
198+
" \"\"\"Generates pink noise using the Voss-McCartney algorithm.\n",
199+
" \n",
200+
" nrows: number of values to generate\n",
201+
" rcols: number of random sources to add\n",
202+
" \n",
203+
" returns: NumPy array\n",
204+
" \"\"\"\n",
205+
" \n",
206+
" # Set up the array\n",
207+
" array = np.empty((nrows, ncols))\n",
208+
" array.fill(np.nan)\n",
209+
" \n",
210+
" # Populate first row (first time point)\n",
211+
" array[0, :] = np.random.random(ncols)\n",
212+
" \n",
213+
" # Populate first columns (highest-freq generator)\n",
214+
" array[:, 0] = np.random.random(nrows)\n",
215+
" \n",
216+
" # Compute where changes happen and add new values\n",
217+
" n = nrows # the total number of changes is nrows\n",
218+
" cols = np.random.geometric(0.5, n)\n",
219+
" cols[cols >= ncols] = 0\n",
220+
" rows = np.random.randint(nrows, size=n)\n",
221+
" array[rows, cols] = np.random.random(n)\n",
222+
" \n",
223+
" # Forward-fill the skipped nan values\n",
224+
" lel = np.copy(array)\n",
225+
" while np.any(np.isnan(array)):\n",
226+
" nan_r, nan_c = np.where(np.isnan(array))\n",
227+
" fillable = np.where(~np.isnan(array[nan_r-1, nan_c]))\n",
228+
" array[nan_r[fillable], nan_c[fillable]] = array[nan_r[fillable]-1, nan_c[fillable]]\n",
229+
"\n",
230+
" # Return the sums\n",
231+
" return array.sum(axis=1)"
232+
]
233+
},
234+
{
235+
"cell_type": "code",
236+
"execution_count": 7,
237+
"metadata": {
238+
"collapsed": false
239+
},
240+
"outputs": [],
241+
"source": [
242+
"### Generate two measurements (at different order of magnitude)\n",
243+
"\n",
244+
"# For each branch...\n",
245+
"for ID in tree.keys():\n",
246+
" \n",
247+
" # For each measure & magnitude...\n",
248+
" for m_name, m_magnitude in zip(['measure_1','measure_2'],[1,10]):\n",
249+
" \n",
250+
" # Create measure track\n",
251+
" tree[ID][m_name] = voss(tree[ID]['indices'].shape[0], ncols=16) * m_magnitude"
252+
]
253+
},
254+
{
255+
"cell_type": "markdown",
256+
"metadata": {},
257+
"source": [
258+
"### Saving Generated Data"
259+
]
260+
},
261+
{
262+
"cell_type": "code",
263+
"execution_count": 8,
264+
"metadata": {
265+
"collapsed": false
266+
},
267+
"outputs": [],
268+
"source": [
269+
"### Save the tree structure\n",
270+
"# Files will be \"ID\\tStem\\tBranch1\\tBranch2\\n\"\n",
271+
"\n",
272+
"with open(\"tree_struct.txt\",\"w\") as outfile:\n",
273+
" \n",
274+
" # Write header\n",
275+
" outfile.write(\"trackID\\tstemID\\tbranchID1\\tbranchID2\\n\")\n",
276+
" \n",
277+
" # Iterate over branches in random order\n",
278+
" # (...to better approximate real experimental result lists)\n",
279+
" shuffled_keys = tree.keys()\n",
280+
" np.random.shuffle(shuffled_keys)\n",
281+
" for ID in shuffled_keys:\n",
282+
" \n",
283+
" # Write the output\n",
284+
" outfile.write(\"%s\\t%s\\t%s\\t%s\\n\" % (ID,\n",
285+
" tree[ID]['stem'],\n",
286+
" tree[ID]['branches'][0],\n",
287+
" tree[ID]['branches'][1]))"
288+
]
289+
},
290+
{
291+
"cell_type": "code",
292+
"execution_count": 9,
293+
"metadata": {
294+
"collapsed": false
295+
},
296+
"outputs": [],
297+
"source": [
298+
"### Save data tracks\n",
299+
"\n",
300+
"for m_name in ['measure_1','measure_2']:\n",
301+
" \n",
302+
" # Create fname\n",
303+
" fname = \"tracks_\"+m_name+\".txt\"\n",
304+
" \n",
305+
" # Create header\n",
306+
" all_IDs = sorted(tree.keys())\n",
307+
" header = '\\t'.join([\"index\"]+[str(ID) for ID in all_IDs])\n",
308+
"\n",
309+
" # Create a numpy array containing all track data\n",
310+
" # Note: first column is the time course index\n",
311+
" final_index = np.max(np.concatenate([tree[ID]['indices'] for ID in all_IDs]))\n",
312+
" track_array = np.zeros((final_index+1, len(all_IDs)+1))\n",
313+
" track_array.fill(np.nan)\n",
314+
" track_array[:, 0] = np.arange(final_index+1)\n",
315+
" for track_idx,ID in enumerate(all_IDs):\n",
316+
" track_array[tree[ID]['indices'], track_idx+1] = tree[ID][m_name]\n",
317+
"\n",
318+
" # Write the file\n",
319+
" fmt = ['%d'] + [' %.3f' for track_idx in range(len(all_IDs))] # To write index as d, everything else as f\n",
320+
" np.savetxt(fname, track_array, fmt=fmt, delimiter='\\t', header=header, comments='') "
321+
]
322+
}
323+
],
324+
"metadata": {
325+
"kernelspec": {
326+
"display_name": "Python 2",
327+
"language": "python",
328+
"name": "python2"
329+
},
330+
"language_info": {
331+
"codemirror_mode": {
332+
"name": "ipython",
333+
"version": 2
334+
},
335+
"file_extension": ".py",
336+
"mimetype": "text/x-python",
337+
"name": "python",
338+
"nbconvert_exporter": "python",
339+
"pygments_lexer": "ipython2",
340+
"version": "2.7.11"
341+
}
342+
},
343+
"nbformat": 4,
344+
"nbformat_minor": 2
345+
}

example_tree_color.png

31.3 KB
Loading

example_tree_outline.png

20.1 KB
Loading

0 commit comments

Comments
 (0)