Skip to content

Commit c0b32ed

Browse files
authored
Merge pull request #37 from Image-Analysis-Hub/feature/geff_io
Feature/geff io
2 parents 405326e + 94710e1 commit c0b32ed

540 files changed

Lines changed: 15178 additions & 3168 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ cov.xml
1818

1919
# Data
2020
sample_data/FakeTracks.tif
21+

notebooks/Custom properties.ipynb

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
{
2626
"cell_type": "code",
27-
"execution_count": 19,
27+
"execution_count": 1,
2828
"metadata": {},
2929
"outputs": [],
3030
"source": [
@@ -42,7 +42,7 @@
4242
},
4343
{
4444
"cell_type": "code",
45-
"execution_count": 20,
45+
"execution_count": 2,
4646
"metadata": {},
4747
"outputs": [
4848
{
@@ -95,7 +95,7 @@
9595
"Where does the property come from? For Pycellin computed properties, Pycellin uses `Pycellin`. For imported properties, Pycellin uses the name of the tool the data was imported from, like `TrackMate` or `CTC`. For custom properties, you can use `custom`, your initials or whatever works for you. This field is useful for traceability (e.g. reopening a model after a long time, sharing a model with other people...).\n",
9696
"\n",
9797
"**prop_type** \n",
98-
"The type of graph element the property applies to, either `node`, `edge`, `lineage`.\n",
98+
"The type of graph element the property applies to, either `node`, `edge`, `lineage` or a combination of those.\n",
9999
"\n",
100100
"**lin_type** \n",
101101
"Either `CellLineage`, `CycleLineage` or just `Lineage` depending on which type of lineage your property is related to. See [Pycellin data structure](./Pycellin%20data%20structure.ipynb) if in doubt.\n",
@@ -118,7 +118,7 @@
118118
},
119119
{
120120
"cell_type": "code",
121-
"execution_count": 21,
121+
"execution_count": 3,
122122
"metadata": {},
123123
"outputs": [],
124124
"source": [
@@ -143,7 +143,7 @@
143143
},
144144
{
145145
"cell_type": "code",
146-
"execution_count": 22,
146+
"execution_count": 4,
147147
"metadata": {},
148148
"outputs": [
149149
{
@@ -192,14 +192,15 @@
192192
},
193193
{
194194
"cell_type": "code",
195-
"execution_count": 23,
195+
"execution_count": 5,
196196
"metadata": {},
197197
"outputs": [],
198198
"source": [
199199
"class AgeCalculator(pc.NodeGlobalPropCalculator):\n",
200200
" def compute(self, data: pc.Data, lineage: pc.CellLineage, nid: int) -> int:\n",
201201
" root = lineage.get_root()\n",
202-
" return lineage.nodes[nid][\"frame\"] - lineage.nodes[root][\"frame\"]"
202+
" # FRAME is the name of the one of the time properties in TrackMate.\n",
203+
" return lineage.nodes[nid][\"FRAME\"] - lineage.nodes[root][\"FRAME\"]"
203204
]
204205
},
205206
{
@@ -267,7 +268,7 @@
267268
},
268269
{
269270
"cell_type": "code",
270-
"execution_count": 24,
271+
"execution_count": 6,
271272
"metadata": {},
272273
"outputs": [],
273274
"source": [
@@ -304,7 +305,7 @@
304305
},
305306
{
306307
"cell_type": "code",
307-
"execution_count": 25,
308+
"execution_count": 7,
308309
"metadata": {},
309310
"outputs": [],
310311
"source": [
@@ -322,7 +323,7 @@
322323
},
323324
{
324325
"cell_type": "code",
325-
"execution_count": 26,
326+
"execution_count": 8,
326327
"metadata": {},
327328
"outputs": [
328329
{
@@ -2539,7 +2540,7 @@
25392540
},
25402541
{
25412542
"cell_type": "code",
2542-
"execution_count": 27,
2543+
"execution_count": 9,
25432544
"metadata": {},
25442545
"outputs": [],
25452546
"source": [
@@ -2577,7 +2578,7 @@
25772578
},
25782579
{
25792580
"cell_type": "code",
2580-
"execution_count": 28,
2581+
"execution_count": 10,
25812582
"metadata": {},
25822583
"outputs": [],
25832584
"source": [
@@ -2602,7 +2603,7 @@
26022603
},
26032604
{
26042605
"cell_type": "code",
2605-
"execution_count": 29,
2606+
"execution_count": 11,
26062607
"metadata": {
26072608
"tags": [
26082609
"raises-exception"
@@ -2616,8 +2617,8 @@
26162617
"traceback": [
26172618
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
26182619
"\u001b[31mTypeError\u001b[39m Traceback (most recent call last)",
2619-
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[29]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m calc = ParityCalculator_incorrect(prop_incorrect)\n\u001b[32m 2\u001b[39m model.add_custom_property(calc)\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m.\u001b[49m\u001b[43mupdate\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
2620-
"\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:910\u001b[39m, in \u001b[36mModel.update\u001b[39m\u001b[34m(self, props_to_update)\u001b[39m\n\u001b[32m 903\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m time_step \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 904\u001b[39m \u001b[38;5;66;03m# TODO: add \"see documentation\" to the error message or explain\u001b[39;00m\n\u001b[32m 905\u001b[39m \u001b[38;5;66;03m# directly how to set it?\u001b[39;00m\n\u001b[32m 906\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 907\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mThe time step of the model is currently not defined \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 908\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mbut is required for cycle lineage computation.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 909\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m910\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_updater\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_update\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 911\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 912\u001b[39m \u001b[43m \u001b[49m\u001b[43mtime_prop\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mmodel_metadata\u001b[49m\u001b[43m.\u001b[49m\u001b[43mreference_time_property\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 913\u001b[39m \u001b[43m \u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 914\u001b[39m \u001b[43m \u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 915\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
2620+
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m calc = ParityCalculator_incorrect(prop_incorrect)\n\u001b[32m 2\u001b[39m model.add_custom_property(calc)\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m.\u001b[49m\u001b[43mupdate\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
2621+
"\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:963\u001b[39m, in \u001b[36mModel.update\u001b[39m\u001b[34m(self, props_to_update)\u001b[39m\n\u001b[32m 956\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m time_step \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 957\u001b[39m \u001b[38;5;66;03m# TODO: add \"see documentation\" to the error message or explain\u001b[39;00m\n\u001b[32m 958\u001b[39m \u001b[38;5;66;03m# directly how to set it?\u001b[39;00m\n\u001b[32m 959\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 960\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mThe time step of the model is currently not defined \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 961\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mbut is required for cycle lineage computation.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 962\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m963\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_updater\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_update\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 964\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 965\u001b[39m \u001b[43m \u001b[49m\u001b[43mtime_prop\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mmodel_metadata\u001b[49m\u001b[43m.\u001b[49m\u001b[43mreference_time_property\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 966\u001b[39m \u001b[43m \u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 967\u001b[39m \u001b[43m \u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 968\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
26212622
"\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/updater.py:280\u001b[39m, in \u001b[36mModelUpdater._update\u001b[39m\u001b[34m(self, data, time_prop, time_step, props_to_update)\u001b[39m\n\u001b[32m 276\u001b[39m \u001b[38;5;66;03m# Recompute the properties as needed.\u001b[39;00m\n\u001b[32m 277\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m calc \u001b[38;5;129;01min\u001b[39;00m cell_calculators:\n\u001b[32m 278\u001b[39m \u001b[38;5;66;03m# Depending on the class of the calculator, a different version of\u001b[39;00m\n\u001b[32m 279\u001b[39m \u001b[38;5;66;03m# the enrich() method is called.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m280\u001b[39m \u001b[43mcalc\u001b[49m\u001b[43m.\u001b[49m\u001b[43menrich\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 281\u001b[39m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 282\u001b[39m \u001b[43m \u001b[49m\u001b[43mnodes_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[43mnodes_to_process\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# self._added_cells,\u001b[39;49;00m\n\u001b[32m 283\u001b[39m \u001b[43m \u001b[49m\u001b[43medges_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[43medges_to_process\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# self._added_links,\u001b[39;49;00m\n\u001b[32m 284\u001b[39m \u001b[43m \u001b[49m\u001b[43mlineages_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[43mlins_to_process\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# self._added_lineages | self._modified_lineages\u001b[39;49;00m\n\u001b[32m 285\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 287\u001b[39m \u001b[38;5;66;03m# In case of modifications in the structure of some cell lineages,\u001b[39;00m\n\u001b[32m 288\u001b[39m \u001b[38;5;66;03m# we need to recompute the cycle lineages and their properties.\u001b[39;00m\n\u001b[32m 289\u001b[39m \u001b[38;5;66;03m# TODO: optimize so we don't have to recompute EVERYTHING for cycle lineages?\u001b[39;00m\n\u001b[32m 290\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m lin_ID \u001b[38;5;129;01min\u001b[39;00m lins_to_process:\n",
26222623
"\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/property_calculator.py:181\u001b[39m, in \u001b[36mNodeLocalPropCalculator.enrich\u001b[39m\u001b[34m(self, data, nodes_to_enrich, **kwargs)\u001b[39m\n\u001b[32m 179\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m nid, lin_ID \u001b[38;5;129;01min\u001b[39;00m nodes_to_enrich:\n\u001b[32m 180\u001b[39m lin = lineages[lin_ID]\n\u001b[32m--> \u001b[39m\u001b[32m181\u001b[39m lin.nodes[nid][\u001b[38;5;28mself\u001b[39m.prop.identifier] = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcompute\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlin\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnid\u001b[49m\u001b[43m)\u001b[49m\n",
26232624
"\u001b[31mTypeError\u001b[39m: ParityCalculator_incorrect.compute() takes 2 positional arguments but 3 were given"
@@ -2646,7 +2647,7 @@
26462647
},
26472648
{
26482649
"cell_type": "code",
2649-
"execution_count": 30,
2650+
"execution_count": 12,
26502651
"metadata": {},
26512652
"outputs": [],
26522653
"source": [
@@ -2672,7 +2673,7 @@
26722673
},
26732674
{
26742675
"cell_type": "code",
2675-
"execution_count": 31,
2676+
"execution_count": 13,
26762677
"metadata": {},
26772678
"outputs": [
26782679
{
@@ -2688,7 +2689,7 @@
26882689
"calc = ParityCalculator_correct(prop_correct)\n",
26892690
"model.add_custom_property(calc)\n",
26902691
"\n",
2691-
"# We clean the model by removing the improper property,\n",
2692+
"# We clean the model by removing the incorrect property,\n",
26922693
"# otherwise we won't be able to update the model.\n",
26932694
"model.remove_property(\"node_ID_parity_incorrect\")\n",
26942695
"model.update()\n",
@@ -2735,7 +2736,7 @@
27352736
},
27362737
{
27372738
"cell_type": "code",
2738-
"execution_count": 32,
2739+
"execution_count": 14,
27392740
"metadata": {},
27402741
"outputs": [],
27412742
"source": [
@@ -2747,29 +2748,28 @@
27472748
" prop_type=\"node\",\n",
27482749
" lin_type=\"CellLineage\",\n",
27492750
" dtype=\"float\",\n",
2750-
" unit=model.get_time_step(),\n",
2751+
" unit=model.get_time_unit(),\n",
27512752
")\n",
27522753
"\n",
27532754
"\n",
27542755
"class MyRelativeAgeCalculator(pc.NodeGlobalPropCalculator):\n",
2755-
" def __init__(self, property: pc.Property, time_step: float):\n",
2756+
" def __init__(self, property: pc.Property, time_prop_name: float):\n",
27562757
" # Call the parent __init__ method.\n",
27572758
" super().__init__(property)\n",
27582759
" # Do whatever you want with the additional argument(s).\n",
27592760
" # Usually you store them in the object to be able to use them\n",
27602761
" # in the `compute` method.\n",
2761-
" self.time_step = time_step\n",
2762+
" self.time_prop_name = time_prop_name\n",
27622763
"\n",
27632764
" def compute(self, data: pc.Data, lineage: pc.CellLineage, nid: int):\n",
27642765
" # As said before, the signature of the `compute` method must be respected,\n",
27652766
" # even if we don't use the `data` argument here.\n",
27662767
" first_cell = lineage.get_cell_cycle(nid)[0]\n",
2767-
" first_cell_frame = lineage.nodes[first_cell][\"frame\"]\n",
2768-
" current_cell_frame = lineage.nodes[nid][\"frame\"]\n",
2769-
" age_in_frame = current_cell_frame - first_cell_frame\n",
2770-
" # Here we use the additional time step argument.\n",
2771-
" age_in_time = age_in_frame * self.time_step\n",
2772-
" return age_in_time"
2768+
" # We use the additional time_prop_name argument so that this method can work\n",
2769+
" # with any time property, including the reference time property of the model.\n",
2770+
" first_cell_time = lineage.nodes[first_cell][self.time_prop_name]\n",
2771+
" current_cell_time = lineage.nodes[nid][self.time_prop_name]\n",
2772+
" return current_cell_time - first_cell_time"
27732773
]
27742774
},
27752775
{
@@ -2781,17 +2781,17 @@
27812781
},
27822782
{
27832783
"cell_type": "code",
2784-
"execution_count": 33,
2784+
"execution_count": 15,
27852785
"metadata": {},
27862786
"outputs": [],
27872787
"source": [
2788-
"calc = MyRelativeAgeCalculator(age_prop2, model.get_time_step())\n",
2788+
"calc = MyRelativeAgeCalculator(age_prop2, model.reference_time_property)\n",
27892789
"model.add_custom_property(calc)"
27902790
]
27912791
},
27922792
{
27932793
"cell_type": "code",
2794-
"execution_count": 34,
2794+
"execution_count": 16,
27952795
"metadata": {},
27962796
"outputs": [
27972797
{
@@ -5001,7 +5001,7 @@
50015001
},
50025002
{
50035003
"cell_type": "code",
5004-
"execution_count": 35,
5004+
"execution_count": 17,
50055005
"metadata": {},
50065006
"outputs": [],
50075007
"source": [
@@ -5036,7 +5036,7 @@
50365036
},
50375037
{
50385038
"cell_type": "code",
5039-
"execution_count": 36,
5039+
"execution_count": 18,
50405040
"metadata": {},
50415041
"outputs": [
50425042
{

0 commit comments

Comments
 (0)