diff --git a/.gitignore b/.gitignore index b7fdf30..3fbd880 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ cov.xml # Data sample_data/FakeTracks.tif +sample_data/**.geff diff --git a/notebooks/Custom properties.ipynb b/notebooks/Custom properties.ipynb index 791cf66..dd33126 100644 --- a/notebooks/Custom properties.ipynb +++ b/notebooks/Custom properties.ipynb @@ -2614,13 +2614,13 @@ "evalue": "ParityCalculator_incorrect.compute() takes 2 positional arguments but 3 were given", "output_type": "error", "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[11], line 3\u001b[0m\n\u001b[0;32m 1\u001b[0m calc \u001b[38;5;241m=\u001b[39m ParityCalculator_incorrect(prop_incorrect)\n\u001b[0;32m 2\u001b[0m model\u001b[38;5;241m.\u001b[39madd_custom_property(calc)\n\u001b[1;32m----> 3\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mupdate\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mE:\\Code\\pycellin\\pycellin\\pycellin\\classes\\model.py:533\u001b[0m, in \u001b[0;36mModel.update\u001b[1;34m(self, props_to_update)\u001b[0m\n\u001b[0;32m 525\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[0;32m 527\u001b[0m \u001b[38;5;66;03m# self.data._freeze_lineage_data()\u001b[39;00m\n\u001b[0;32m 528\u001b[0m \n\u001b[0;32m 529\u001b[0m \u001b[38;5;66;03m# TODO: need to handle all the errors that can be raised\u001b[39;00m\n\u001b[0;32m 530\u001b[0m \u001b[38;5;66;03m# by the updater methods to avoid incoherent states.\u001b[39;00m\n\u001b[0;32m 531\u001b[0m \u001b[38;5;66;03m# => saving a copy of the model before the update so we can roll back?\u001b[39;00m\n\u001b[1;32m--> 533\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_updater\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_update\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mE:\\Code\\pycellin\\pycellin\\pycellin\\classes\\updater.py:194\u001b[0m, in \u001b[0;36mModelUpdater._update\u001b[1;34m(self, data, props_to_update)\u001b[0m\n\u001b[0;32m 190\u001b[0m \u001b[38;5;66;03m# Recompute the properties as needed.\u001b[39;00m\n\u001b[0;32m 191\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m calc \u001b[38;5;129;01min\u001b[39;00m cell_calculators:\n\u001b[0;32m 192\u001b[0m \u001b[38;5;66;03m# Depending on the class of the calculator, a different version of\u001b[39;00m\n\u001b[0;32m 193\u001b[0m \u001b[38;5;66;03m# the enrich() method is called.\u001b[39;00m\n\u001b[1;32m--> 194\u001b[0m \u001b[43mcalc\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menrich\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 195\u001b[0m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 196\u001b[0m \u001b[43m \u001b[49m\u001b[43mnodes_to_enrich\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_added_cells\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 197\u001b[0m \u001b[43m \u001b[49m\u001b[43medges_to_enrich\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_added_links\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 198\u001b[0m \u001b[43m \u001b[49m\u001b[43mlineages_to_enrich\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_added_lineages\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m|\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_modified_lineages\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 199\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 201\u001b[0m \u001b[38;5;66;03m# In case of modifications in the structure of some cell lineages,\u001b[39;00m\n\u001b[0;32m 202\u001b[0m \u001b[38;5;66;03m# we need to recompute the cycle lineages and their properties.\u001b[39;00m\n\u001b[0;32m 203\u001b[0m \u001b[38;5;66;03m# TODO: optimize so we don't have to recompute EVERYTHING for cycle lineages?\u001b[39;00m\n\u001b[0;32m 204\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m lin_ID \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_modified_lineages \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_added_lineages) \u001b[38;5;241m-\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_removed_lineages:\n", - "File \u001b[1;32mE:\\Code\\pycellin\\pycellin\\pycellin\\classes\\property_calculator.py:181\u001b[0m, in \u001b[0;36mNodeLocalPropCalculator.enrich\u001b[1;34m(self, data, nodes_to_enrich, **kwargs)\u001b[0m\n\u001b[0;32m 179\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m nid, lin_ID \u001b[38;5;129;01min\u001b[39;00m nodes_to_enrich:\n\u001b[0;32m 180\u001b[0m lin \u001b[38;5;241m=\u001b[39m lineages[lin_ID]\n\u001b[1;32m--> 181\u001b[0m lin\u001b[38;5;241m.\u001b[39mnodes[nid][\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprop\u001b[38;5;241m.\u001b[39midentifier] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;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", - "\u001b[1;31mTypeError\u001b[0m: ParityCalculator_incorrect.compute() takes 2 positional arguments but 3 were given" + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mTypeError\u001b[39m Traceback (most recent call last)", + "\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", + "\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:533\u001b[39m, in \u001b[36mModel.update\u001b[39m\u001b[34m(self, props_to_update)\u001b[39m\n\u001b[32m 525\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[32m 527\u001b[39m \u001b[38;5;66;03m# self.data._freeze_lineage_data()\u001b[39;00m\n\u001b[32m 528\u001b[39m \n\u001b[32m 529\u001b[39m \u001b[38;5;66;03m# TODO: need to handle all the errors that can be raised\u001b[39;00m\n\u001b[32m 530\u001b[39m \u001b[38;5;66;03m# by the updater methods to avoid incoherent states.\u001b[39;00m\n\u001b[32m 531\u001b[39m \u001b[38;5;66;03m# => saving a copy of the model before the update so we can roll back?\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m533\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\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mprops_to_update\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/updater.py:194\u001b[39m, in \u001b[36mModelUpdater._update\u001b[39m\u001b[34m(self, data, props_to_update)\u001b[39m\n\u001b[32m 190\u001b[39m \u001b[38;5;66;03m# Recompute the properties as needed.\u001b[39;00m\n\u001b[32m 191\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m calc \u001b[38;5;129;01min\u001b[39;00m cell_calculators:\n\u001b[32m 192\u001b[39m \u001b[38;5;66;03m# Depending on the class of the calculator, a different version of\u001b[39;00m\n\u001b[32m 193\u001b[39m \u001b[38;5;66;03m# the enrich() method is called.\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m194\u001b[39m \u001b[43mcalc\u001b[49m\u001b[43m.\u001b[49m\u001b[43menrich\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 195\u001b[39m \u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 196\u001b[39m \u001b[43m \u001b[49m\u001b[43mnodes_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_added_cells\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 197\u001b[39m \u001b[43m \u001b[49m\u001b[43medges_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_added_links\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 198\u001b[39m \u001b[43m \u001b[49m\u001b[43mlineages_to_enrich\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_added_lineages\u001b[49m\u001b[43m \u001b[49m\u001b[43m|\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_modified_lineages\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 199\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 201\u001b[39m \u001b[38;5;66;03m# In case of modifications in the structure of some cell lineages,\u001b[39;00m\n\u001b[32m 202\u001b[39m \u001b[38;5;66;03m# we need to recompute the cycle lineages and their properties.\u001b[39;00m\n\u001b[32m 203\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 204\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m lin_ID \u001b[38;5;129;01min\u001b[39;00m (\u001b[38;5;28mself\u001b[39m._modified_lineages | \u001b[38;5;28mself\u001b[39m._added_lineages) - \u001b[38;5;28mself\u001b[39m._removed_lineages:\n", + "\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", + "\u001b[31mTypeError\u001b[39m: ParityCalculator_incorrect.compute() takes 2 positional arguments but 3 were given" ] } ], @@ -6223,7 +6223,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.18" + "version": "3.11.13" } }, "nbformat": 4, diff --git a/notebooks/Getting started.ipynb b/notebooks/Getting started.ipynb index 38e7a47..f297144 100644 --- a/notebooks/Getting started.ipynb +++ b/notebooks/Getting started.ipynb @@ -371,10 +371,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "pixel width = 0.06587 µm\n", - "pixel height = 0.06587 µm\n", - "pixel depth = 0.06587 µm\n", - "frame = 5.0 min\n" + "pixel width = 0.06587 micrometer\n", + "pixel height = 0.06587 micrometer\n", + "pixel depth = 0.06587 micrometer\n", + "frame = 5.0 minute\n" ] } ], @@ -415,7 +415,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "E:\\Code\\pycellin\\pycellin\\pycellin\\io\\trackmate\\loader.py:1225: UserWarning: Unsupported data, 3 cell fusions detected. It is advised to deal with them before any other processing, especially for tracking related properties. Crashes and incorrect results can occur. See documentation for more details.\n", + "/media/lxenard/data/Code/pycellin/pycellin/pycellin/io/utils.py:31: UserWarning: Unsupported data, 3 cell fusions detected. It is advised to deal with them before any other processing, especially for tracking related properties. Crashes and incorrect results can occur. See documentation for more details.\n", " warnings.warn(fusion_warning)\n" ] } @@ -525,9 +525,9 @@ { "data": { "text/plain": [ - "{0: ,\n", - " 1: ,\n", - " 2: }" + "{0: ,\n", + " 1: ,\n", + " 2: }" ] }, "execution_count": 16, @@ -6085,7 +6085,7 @@ " (-2.027093888288606, 0.9125132536486831),\n", " (-1.961223884190833, 1.0442532618442293),\n", " (-1.6977438677997423, 1.0442532618442293)],\n", - " 'TRACK_ID': 1,\n", + " 'lineage_ID': 1,\n", " 'cell_ID': 8985,\n", " 'frame': 0,\n", " 'cell_name': 'ID8985',\n", @@ -6227,8 +6227,8 @@ " 'MEAN_STRAIGHT_LINE_SPEED': 0.09315523695815782,\n", " 'LINEARITY_OF_FORWARD_PROGRESSION': 0.38549094669096795,\n", " 'MEAN_DIRECTIONAL_CHANGE_RATE': 0.23933007611391033,\n", - " 'lineage_name': 'Track_1',\n", " 'lineage_ID': 1,\n", + " 'lineage_name': 'Track_1',\n", " 'lineage_x': 11.299502880687372,\n", " 'lineage_y': 20.138498761300276,\n", " 'lineage_z': 0.0,\n", @@ -6587,9 +6587,9 @@ { "data": { "text/plain": [ - "{0: ,\n", - " 1: ,\n", - " 2: }" + "{0: ,\n", + " 1: ,\n", + " 2: }" ] }, "execution_count": 37, @@ -10365,8 +10365,8 @@ "Lineage properties: TRACK_INDEX, DIVISION_TIME_MEAN, DIVISION_TIME_STD, NUMBER_SPOTS, NUMBER_GAPS, NUMBER_SPLITS, NUMBER_MERGES, NUMBER_COMPLEX, LONGEST_GAP, TRACK_DURATION, TRACK_START, TRACK_STOP, TRACK_DISPLACEMENT, TRACK_MEAN_SPEED, TRACK_MAX_SPEED, TRACK_MIN_SPEED, TRACK_MEDIAN_SPEED, TRACK_STD_SPEED, TRACK_MEAN_QUALITY, TOTAL_DISTANCE_TRAVELED, MAX_DISTANCE_TRAVELED, CONFINEMENT_RATIO, MEAN_STRAIGHT_LINE_SPEED, LINEARITY_OF_FORWARD_PROGRESSION, MEAN_DIRECTIONAL_CHANGE_RATE, lineage_name, lineage_ID, FilteredTrack, lineage_x, lineage_y, lineage_z\n", "\n", "Property(identifier='QUALITY', name='Quality', description='Quality', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='min')\n", - "Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='minute')\n", + "Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='VISIBILITY', name='Visibility', description='Visibility', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='MANUAL_SPOT_COLOR', name='Manual spot color', description='Manual spot color', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='MEAN_INTENSITY_CH1', name='Mean intensity ch1', description='Mean intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", @@ -10385,14 +10385,14 @@ "Property(identifier='SNR_CH1', name='Signal/Noise ratio ch1', description='Signal/Noise ratio ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", "Property(identifier='CONTRAST_CH2', name='Contrast ch2', description='Contrast ch2', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", "Property(identifier='SNR_CH2', name='Signal/Noise ratio ch2', description='Signal/Noise ratio ch2', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='ELLIPSE_THETA', name='Ellipse angle', description='Ellipse angle', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='rad')\n", "Property(identifier='ELLIPSE_ASPECTRATIO', name='Ellipse aspect ratio', description='Ellipse aspect ratio', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm^2')\n", - "Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer^2')\n", + "Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='CIRCULARITY', name='Circularity', description='Circularity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", "Property(identifier='SOLIDITY', name='Solidity', description='Solidity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", "Property(identifier='SHAPE_INDEX', name='Shape index', description='Shape index', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None)\n", @@ -10400,51 +10400,51 @@ "Property(identifier='SPOT_SOURCE_ID', name='Source spot ID', description='Source spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='SPOT_TARGET_ID', name='Target spot ID', description='Target spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='LINK_COST', name='Edge cost', description='Edge cost', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/min')\n", - "Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm/min')\n", - "Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='min')\n", + "Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/minute')\n", + "Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", + "Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='minute')\n", "Property(identifier='MANUAL_EDGE_COLOR', name='Manual edge color', description='Manual edge color', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='TRACK_INDEX', name='Track index', description='Track index', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", - "Property(identifier='DIVISION_TIME_MEAN', name='Mean cell division time', description='Mean cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min')\n", - "Property(identifier='DIVISION_TIME_STD', name='Std cell division time', description='Std cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min')\n", + "Property(identifier='DIVISION_TIME_MEAN', name='Mean cell division time', description='Mean cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute')\n", + "Property(identifier='DIVISION_TIME_STD', name='Std cell division time', description='Std cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute')\n", "Property(identifier='NUMBER_SPOTS', name='Number of spots in track', description='Number of spots in track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='NUMBER_GAPS', name='Number of gaps', description='Number of gaps', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='NUMBER_SPLITS', name='Number of split events', description='Number of split events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='NUMBER_MERGES', name='Number of merge events', description='Number of merge events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='NUMBER_COMPLEX', name='Number of complex points', description='Number of complex points', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='LONGEST_GAP', name='Longest gap', description='Longest gap', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", - "Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min')\n", - "Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min')\n", - "Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min')\n", - "Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", - "Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", - "Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", - "Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", - "Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", + "Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute')\n", + "Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute')\n", + "Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute')\n", + "Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", + "Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", + "Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", + "Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", + "Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", "Property(identifier='TRACK_MEAN_QUALITY', name='Track mean quality', description='Track mean quality', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='CONFINEMENT_RATIO', name='Confinement ratio', description='Confinement ratio', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min')\n", + "Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute')\n", "Property(identifier='LINEARITY_OF_FORWARD_PROGRESSION', name='Linearity of forward progression', description='Linearity of forward progression', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None)\n", - "Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/min')\n", + "Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/minute')\n", "Property(identifier='lineage_name', name='lineage name', description='Name of the track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='string', unit=None)\n", "Property(identifier='cell_ID', name='cell ID', description='Unique identifier of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None)\n", - "Property(identifier='cell_x', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='cell_y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='cell_z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='cell_x', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='cell_y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='cell_z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='frame', name='Frame', description='Frame', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None)\n", - "Property(identifier='ROI_coords', name='ROI coords', description='List of coordinates of the region of interest', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='link_x', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='link_y', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='link_z', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='ROI_coords', name='ROI coords', description='List of coordinates of the region of interest', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='link_x', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='link_y', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='link_z', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='lineage_ID', name='Track ID', description='Unique identifier of the lineage', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", "Property(identifier='FilteredTrack', name='FilteredTrack', description='True if the track was not filtered out in TrackMate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None)\n", - "Property(identifier='lineage_x', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='lineage_y', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", - "Property(identifier='lineage_z', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm')\n", + "Property(identifier='lineage_x', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='lineage_y', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", + "Property(identifier='lineage_z', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer')\n", "Property(identifier='cycle_ID', name='cycle ID', description='Unique identifier of the cell cycle, i.e. cell_ID of the last cell in the cell cycle', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='int', unit=None)\n", "Property(identifier='cells', name='cells', description='cell_IDs of the cells in the cell cycle, in chronological order', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='list[int]', unit=None)\n", "Property(identifier='cycle_length', name='cycle length', description='Number of cells in the cell cycle, minding gaps', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='int', unit=None)\n", @@ -13585,7 +13585,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "(529, 41)\n", + "(529, 40)\n", "Index(['lineage_ID', 'frame', 'cell_ID', 'STD_INTENSITY_CH1', 'SOLIDITY',\n", " 'STD_INTENSITY_CH2', 'QUALITY', 'POSITION_T', 'TOTAL_INTENSITY_CH2',\n", " 'TOTAL_INTENSITY_CH1', 'CONTRAST_CH1', 'ELLIPSE_MINOR', 'ELLIPSE_THETA',\n", @@ -13595,7 +13595,7 @@ " 'SNR_CH1', 'ELLIPSE_X0', 'SHAPE_INDEX', 'SNR_CH2',\n", " 'MEDIAN_INTENSITY_CH1', 'VISIBILITY', 'RADIUS', 'MEDIAN_INTENSITY_CH2',\n", " 'ELLIPSE_ASPECTRATIO', 'PERIMETER', 'ROI_N_POINTS', 'ROI_coords',\n", - " 'TRACK_ID', 'cell_name', 'cell_x', 'cell_y', 'cell_z'],\n", + " 'cell_name', 'cell_x', 'cell_y', 'cell_z'],\n", " dtype='object')\n" ] } @@ -13643,12 +13643,12 @@ " TOTAL_INTENSITY_CH2\n", " TOTAL_INTENSITY_CH1\n", " ...\n", + " RADIUS\n", " MEDIAN_INTENSITY_CH2\n", " ELLIPSE_ASPECTRATIO\n", " PERIMETER\n", " ROI_N_POINTS\n", " ROI_coords\n", - " TRACK_ID\n", " cell_name\n", " cell_x\n", " cell_y\n", @@ -13717,12 +13717,12 @@ " 70098.0\n", " 26356.0\n", " ...\n", + " 0.921242\n", " 114.0\n", " 6.703090\n", " 10.285699\n", " 52\n", " [(-1.5894197951076556, 1.1551815744407925), (-...\n", - " 0.0\n", " ID8993\n", " 15.389186\n", " 19.363325\n", @@ -13741,12 +13741,12 @@ " 34098.0\n", " 4245.0\n", " ...\n", + " 0.638839\n", " 115.0\n", " 4.279183\n", " 5.944787\n", " 30\n", " [(-0.727319272545234, 0.8544524671426856), (-0...\n", - " 0.0\n", " ID9013\n", " 16.832535\n", " 18.214914\n", @@ -13765,12 +13765,12 @@ " 33523.0\n", " 4350.0\n", " ...\n", + " 0.633956\n", " 115.0\n", " 3.903724\n", " 5.747940\n", " 34\n", " [(-0.6141453589387584, 0.5208483829178867), (-...\n", - " 0.0\n", " ID9014\n", " 14.282171\n", " 19.470698\n", @@ -13778,7 +13778,7 @@ " \n", " \n", "\n", - "

5 rows × 41 columns

\n", + "

5 rows × 40 columns

\n", "" ], "text/plain": [ @@ -13796,19 +13796,19 @@ "3 293.0 5.0 34098.0 4245.0 ... \n", "4 291.0 5.0 33523.0 4350.0 ... \n", "\n", - " MEDIAN_INTENSITY_CH2 ELLIPSE_ASPECTRATIO PERIMETER ROI_N_POINTS \\\n", - "0 NaN NaN NaN NaN \n", - "1 NaN NaN NaN NaN \n", - "2 114.0 6.703090 10.285699 52 \n", - "3 115.0 4.279183 5.944787 30 \n", - "4 115.0 3.903724 5.747940 34 \n", + " RADIUS MEDIAN_INTENSITY_CH2 ELLIPSE_ASPECTRATIO PERIMETER \\\n", + "0 NaN NaN NaN NaN \n", + "1 NaN NaN NaN NaN \n", + "2 0.921242 114.0 6.703090 10.285699 \n", + "3 0.638839 115.0 4.279183 5.944787 \n", + "4 0.633956 115.0 3.903724 5.747940 \n", "\n", - " ROI_coords TRACK_ID cell_name \\\n", - "0 NaN NaN NaN \n", - "1 NaN NaN NaN \n", - "2 [(-1.5894197951076556, 1.1551815744407925), (-... 0.0 ID8993 \n", - "3 [(-0.727319272545234, 0.8544524671426856), (-0... 0.0 ID9013 \n", - "4 [(-0.6141453589387584, 0.5208483829178867), (-... 0.0 ID9014 \n", + " ROI_N_POINTS ROI_coords cell_name \\\n", + "0 NaN NaN NaN \n", + "1 NaN NaN NaN \n", + "2 52 [(-1.5894197951076556, 1.1551815744407925), (-... ID8993 \n", + "3 30 [(-0.727319272545234, 0.8544524671426856), (-0... ID9013 \n", + "4 34 [(-0.6141453589387584, 0.5208483829178867), (-... ID9014 \n", "\n", " cell_x cell_y cell_z \n", "0 1.000000 2.000000 0.0 \n", @@ -13817,7 +13817,7 @@ "3 16.832535 18.214914 0.0 \n", "4 14.282171 19.470698 0.0 \n", "\n", - "[5 rows x 41 columns]" + "[5 rows x 40 columns]" ] }, "execution_count": 66, @@ -13870,9 +13870,9 @@ "output_type": "stream", "text": [ "(524, 13)\n", - "Index(['lineage_ID', 'source_cell_ID', 'target_cell_ID', 'EDGE_TIME', 'link_y',\n", - " 'SPEED', 'DIRECTIONAL_CHANGE_RATE', 'DISPLACEMENT', 'SPOT_SOURCE_ID',\n", - " 'LINK_COST', 'link_x', 'SPOT_TARGET_ID', 'link_z'],\n", + "Index(['lineage_ID', 'source_cell_ID', 'target_cell_ID', 'DISPLACEMENT',\n", + " 'link_z', 'DIRECTIONAL_CHANGE_RATE', 'SPOT_SOURCE_ID', 'SPEED',\n", + " 'EDGE_TIME', 'SPOT_TARGET_ID', 'LINK_COST', 'link_y', 'link_x'],\n", " dtype='object')\n" ] } @@ -13912,16 +13912,16 @@ " lineage_ID\n", " source_cell_ID\n", " target_cell_ID\n", - " EDGE_TIME\n", - " link_y\n", - " SPEED\n", - " DIRECTIONAL_CHANGE_RATE\n", " DISPLACEMENT\n", + " link_z\n", + " DIRECTIONAL_CHANGE_RATE\n", " SPOT_SOURCE_ID\n", + " SPEED\n", + " EDGE_TIME\n", + " SPOT_TARGET_ID\n", " LINK_COST\n", + " link_y\n", " link_x\n", - " SPOT_TARGET_ID\n", - " link_z\n", " \n", " \n", " \n", @@ -13930,106 +13930,106 @@ " 0\n", " 9216.0\n", " 9290.0\n", - " 92.5\n", - " 18.700692\n", - " 0.087533\n", - " 0.556800\n", " 0.437667\n", + " 0.0\n", + " 0.556800\n", " 9216.0\n", + " 0.087533\n", + " 92.5\n", + " 9290.0\n", " 0.397900\n", + " 18.700692\n", " 15.776474\n", - " 9290.0\n", - " 0.0\n", " \n", " \n", " 1\n", " 0\n", " 9218.0\n", " 9294.0\n", - " 92.5\n", - " 15.808225\n", - " 0.284526\n", - " 0.608195\n", " 1.422631\n", + " 0.0\n", + " 0.608195\n", " 9218.0\n", + " 0.284526\n", + " 92.5\n", + " 9294.0\n", " 0.467842\n", + " 15.808225\n", " 20.106089\n", - " 9294.0\n", - " 0.0\n", " \n", " \n", " 2\n", " 0\n", " 9222.0\n", " 9306.0\n", - " 92.5\n", - " 22.138865\n", - " 0.126264\n", - " 0.055708\n", " 0.631321\n", + " 0.0\n", + " 0.055708\n", " 9222.0\n", + " 0.126264\n", + " 92.5\n", + " 9306.0\n", " 0.403972\n", + " 22.138865\n", " 11.621851\n", - " 9306.0\n", - " 0.0\n", " \n", " \n", " 3\n", " 0\n", " 9223.0\n", " 9293.0\n", - " 92.5\n", - " 15.591736\n", - " 0.273041\n", - " 0.606456\n", " 1.365206\n", + " 0.0\n", + " 0.606456\n", " 9223.0\n", + " 0.273041\n", + " 92.5\n", + " 9293.0\n", " 0.491012\n", + " 15.591736\n", " 18.851734\n", - " 9293.0\n", - " 0.0\n", " \n", " \n", " 4\n", " 0\n", - " 9332.0\n", - " 9492.0\n", - " 102.5\n", - " 18.724855\n", - " 0.159162\n", - " 0.614101\n", - " 0.795810\n", - " 9332.0\n", - " 0.710950\n", - " 15.892708\n", - " 9492.0\n", + " 9227.0\n", + " 9308.0\n", + " 2.009642\n", " 0.0\n", + " 0.011716\n", + " 9227.0\n", + " 0.401928\n", + " 92.5\n", + " 9308.0\n", + " 0.616573\n", + " 12.818797\n", + " 22.279749\n", " \n", " \n", "\n", "" ], "text/plain": [ - " lineage_ID source_cell_ID target_cell_ID EDGE_TIME link_y SPEED \\\n", - "0 0 9216.0 9290.0 92.5 18.700692 0.087533 \n", - "1 0 9218.0 9294.0 92.5 15.808225 0.284526 \n", - "2 0 9222.0 9306.0 92.5 22.138865 0.126264 \n", - "3 0 9223.0 9293.0 92.5 15.591736 0.273041 \n", - "4 0 9332.0 9492.0 102.5 18.724855 0.159162 \n", + " lineage_ID source_cell_ID target_cell_ID DISPLACEMENT link_z \\\n", + "0 0 9216.0 9290.0 0.437667 0.0 \n", + "1 0 9218.0 9294.0 1.422631 0.0 \n", + "2 0 9222.0 9306.0 0.631321 0.0 \n", + "3 0 9223.0 9293.0 1.365206 0.0 \n", + "4 0 9227.0 9308.0 2.009642 0.0 \n", "\n", - " DIRECTIONAL_CHANGE_RATE DISPLACEMENT SPOT_SOURCE_ID LINK_COST \\\n", - "0 0.556800 0.437667 9216.0 0.397900 \n", - "1 0.608195 1.422631 9218.0 0.467842 \n", - "2 0.055708 0.631321 9222.0 0.403972 \n", - "3 0.606456 1.365206 9223.0 0.491012 \n", - "4 0.614101 0.795810 9332.0 0.710950 \n", + " DIRECTIONAL_CHANGE_RATE SPOT_SOURCE_ID SPEED EDGE_TIME \\\n", + "0 0.556800 9216.0 0.087533 92.5 \n", + "1 0.608195 9218.0 0.284526 92.5 \n", + "2 0.055708 9222.0 0.126264 92.5 \n", + "3 0.606456 9223.0 0.273041 92.5 \n", + "4 0.011716 9227.0 0.401928 92.5 \n", "\n", - " link_x SPOT_TARGET_ID link_z \n", - "0 15.776474 9290.0 0.0 \n", - "1 20.106089 9294.0 0.0 \n", - "2 11.621851 9306.0 0.0 \n", - "3 18.851734 9293.0 0.0 \n", - "4 15.892708 9492.0 0.0 " + " SPOT_TARGET_ID LINK_COST link_y link_x \n", + "0 9290.0 0.397900 18.700692 15.776474 \n", + "1 9294.0 0.467842 15.808225 20.106089 \n", + "2 9306.0 0.403972 22.138865 11.621851 \n", + "3 9293.0 0.491012 15.591736 18.851734 \n", + "4 9308.0 0.616573 12.818797 22.279749 " ] }, "execution_count": 69, @@ -14513,7 +14513,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "(152, 41)\n" + "(152, 40)\n" ] }, { @@ -14548,12 +14548,12 @@ " TOTAL_INTENSITY_CH2\n", " TOTAL_INTENSITY_CH1\n", " ...\n", + " RADIUS\n", " MEDIAN_INTENSITY_CH2\n", " ELLIPSE_ASPECTRATIO\n", " PERIMETER\n", " ROI_N_POINTS\n", " ROI_coords\n", - " TRACK_ID\n", " cell_name\n", " cell_x\n", " cell_y\n", @@ -14574,12 +14574,12 @@ " 70098.0\n", " 26356.0\n", " ...\n", + " 0.921242\n", " 114.0\n", " 6.703090\n", " 10.285699\n", " 52\n", " [(-1.5894197951076556, 1.1551815744407925), (-...\n", - " 0\n", " ID8993\n", " 15.389186\n", " 19.363325\n", @@ -14598,12 +14598,12 @@ " 34098.0\n", " 4245.0\n", " ...\n", + " 0.638839\n", " 115.0\n", " 4.279183\n", " 5.944787\n", " 30\n", " [(-0.727319272545234, 0.8544524671426856), (-0...\n", - " 0\n", " ID9013\n", " 16.832535\n", " 18.214914\n", @@ -14622,12 +14622,12 @@ " 33523.0\n", " 4350.0\n", " ...\n", + " 0.633956\n", " 115.0\n", " 3.903724\n", " 5.747940\n", " 34\n", " [(-0.6141453589387584, 0.5208483829178867), (-...\n", - " 0\n", " ID9014\n", " 14.282171\n", " 19.470698\n", @@ -14646,12 +14646,12 @@ " 39655.0\n", " 1014.0\n", " ...\n", + " 0.682225\n", " 117.0\n", " 4.359176\n", " 6.355476\n", " 31\n", " [(-0.6357009198638028, 0.43737422107603763), (...\n", - " 0\n", " ID8986\n", " 13.908507\n", " 19.290692\n", @@ -14670,12 +14670,12 @@ " 32526.0\n", " 1124.0\n", " ...\n", + " 0.625181\n", " 115.0\n", " 5.287340\n", " 6.341672\n", " 35\n", " [(-0.801263212391051, 0.8421507178785816), (-0...\n", - " 0\n", " ID8989\n", " 16.774739\n", " 18.029605\n", @@ -14683,7 +14683,7 @@ " \n", " \n", "\n", - "

5 rows × 41 columns

\n", + "

5 rows × 40 columns

\n", "" ], "text/plain": [ @@ -14701,19 +14701,19 @@ "3 338.0 10.0 39655.0 1014.0 ... \n", "4 288.0 10.0 32526.0 1124.0 ... \n", "\n", - " MEDIAN_INTENSITY_CH2 ELLIPSE_ASPECTRATIO PERIMETER ROI_N_POINTS \\\n", - "0 114.0 6.703090 10.285699 52 \n", - "1 115.0 4.279183 5.944787 30 \n", - "2 115.0 3.903724 5.747940 34 \n", - "3 117.0 4.359176 6.355476 31 \n", - "4 115.0 5.287340 6.341672 35 \n", + " RADIUS MEDIAN_INTENSITY_CH2 ELLIPSE_ASPECTRATIO PERIMETER \\\n", + "0 0.921242 114.0 6.703090 10.285699 \n", + "1 0.638839 115.0 4.279183 5.944787 \n", + "2 0.633956 115.0 3.903724 5.747940 \n", + "3 0.682225 117.0 4.359176 6.355476 \n", + "4 0.625181 115.0 5.287340 6.341672 \n", "\n", - " ROI_coords TRACK_ID cell_name \\\n", - "0 [(-1.5894197951076556, 1.1551815744407925), (-... 0 ID8993 \n", - "1 [(-0.727319272545234, 0.8544524671426856), (-0... 0 ID9013 \n", - "2 [(-0.6141453589387584, 0.5208483829178867), (-... 0 ID9014 \n", - "3 [(-0.6357009198638028, 0.43737422107603763), (... 0 ID8986 \n", - "4 [(-0.801263212391051, 0.8421507178785816), (-0... 0 ID8989 \n", + " ROI_N_POINTS ROI_coords cell_name \\\n", + "0 52 [(-1.5894197951076556, 1.1551815744407925), (-... ID8993 \n", + "1 30 [(-0.727319272545234, 0.8544524671426856), (-0... ID9013 \n", + "2 34 [(-0.6141453589387584, 0.5208483829178867), (-... ID9014 \n", + "3 31 [(-0.6357009198638028, 0.43737422107603763), (... ID8986 \n", + "4 35 [(-0.801263212391051, 0.8421507178785816), (-0... ID8989 \n", "\n", " cell_x cell_y cell_z \n", "0 15.389186 19.363325 0.0 \n", @@ -14722,7 +14722,7 @@ "3 13.908507 19.290692 0.0 \n", "4 16.774739 18.029605 0.0 \n", "\n", - "[5 rows x 41 columns]" + "[5 rows x 40 columns]" ] }, "execution_count": 77, @@ -14961,7 +14961,7 @@ { "data": { "text/plain": [ - "Model(model_metadata={'name': 'FakeTracks', 'file_location': '../sample_data/FakeTracks.xml', 'provenance': 'TrackMate', 'date': '2025-09-10 17:22:15.792372', 'space_unit': 'pixel', 'time_unit': 'sec', 'pycellin_version': '0.4.0', 'TrackMate_version': '8.0.0-SNAPSHOT-f411154ed1a4b9de350bbfe91c230cf3ae7639a3', 'time_step': 1.0, 'pixel_size': {'width': 1.0, 'height': 1.0, 'depth': 1.0}, 'Log': \"0.1101/2021.09.03.458852\\nand / or:\\nTinevez, JY.; Perry, N. & Schindelin, J. et al. (2017), 'TrackMate: An open and extensible platform for single-particle tracking.', Methods 115: 80-90, PMID 27713081.\\nhttps://www.sciencedirect.com/science/article/pii/S1046202316303346\\n\\nNumerical feature analyzers:\\nSpot feature analyzers:\\n - Manual spot color provides: Spot color; is manual.\\n - Spot intensity provides: Mean ch1, Median ch1, Min ch1, Max ch1, Sum ch1, Std ch1.\\n - Spot contrast and SNR provides: Ctrst ch1, SNR ch1.\\n - Spot fit 2D ellipse provides: El. x0, El. y0, El. long axis, El. sh. axis, El. angle, El. a.r.\\n - Spot 2D shape descriptors provides: Area, Perim., Circ., Solidity.\\nEdge feature analyzers:\\n - Directional change provides: γ rate.\\n - Edge speed provides: Speed, Disp.\\n - Edge target provides: Source ID, Target ID, Cost.\\n - Edge location provides: Edge T, Edge X, Edge Y, Edge Z.\\n - Manual edge color provides: Edge color; is manual.\\nTrack feature analyzers:\\n - Branching analyzer provides: N spots, N gaps, N splits, N merges, N complex, Lgst gap.\\n - Track duration provides: Duration, Track start, Track stop, Track disp.\\n - Track index provides: Index, ID.\\n - Track location provides: Track X, Track Y, Track Z.\\n - Track speed provides: Mean sp., Max speed, Min speed, Med. speed, Std speed.\\n - Track quality provides: Mean Q.\\n - Track motility analysis provides: Total dist., Max dist., Cfn. ratio, Mn. v. line, Fwd. progr., Mn. γ rate.\\n\\nImage region of interest:\\nImage data:\\nFor the image named: FakeTracks.tif.\\nMatching file FakeTracks.tif in current folder.\\nGeometry:\\n X = 0 - 127, dx = 1.00000\\n Y = 0 - 127, dy = 1.00000\\n Z = 0 - 0, dz = 1.00000\\n T = 0 - 49, dt = 1.00000\\n\\nConfigured detector StarDist detector with settings:\\n - target channel: 1\\n\\nStarting detection process using 24 threads.\\nDetection processes 1 frame simultaneously and allocates 24 threads per frame.\\nFound 107 spots.\\nDetection done in 2.3 s.\\n\\nComputing spot quality histogram...\\nInitial thresholding with a quality threshold above 0.4 ...\\nStarting initial filtering process.\\nRetained 107 spots out of 107.\\n\\nAdding morphology analyzers...\\n - Spot fit 2D ellipse provides: El. x0, El. y0, El. long axis, El. sh. axis, El. angle, El. a.r.\\n - Spot 2D shape descriptors provides: Area, Perim., Circ., Solidity.\\n\\nCalculating spot features...\\nCalculating features done in 0.1 s.\\n\\nPerforming spot filtering on the following features:\\n - on Area below 49.9\\nKept 106 spots out of 107.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 5.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 5.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 5.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 17 tracks.\\n - avg size: 5.1 spots.\\n - min size: 2 spots.\\n - max size: 12 spots.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 8.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 5.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 5.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 9 tracks.\\n - avg size: 10.7 spots.\\n - min size: 2 spots.\\n - max size: 42 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 23.9\\nKept 4 spots out of 9.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 23.9\\nKept 4 spots out of 9.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 8.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 8.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 10.0\\n - merging feature penalties: \\n - splitting max distance: 10.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 10.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\nSaving data...\\nComputing edge features:\\n - Directional change in 2 ms.\\n - Edge speed in 0 ms.\\n - Edge target in 1 ms.\\n - Edge location in 1 ms.\\nComputation done in 4 ms.\\nComputing track features:\\n - Branching analyzer in 0 ms.\\n - Track duration in 0 ms.\\n - Track index in 0 ms.\\n - Track location in 4 ms.\\n - Track speed in 0 ms.\\n - Track quality in 1 ms.\\n - Track motility analysis in 0 ms.\\nComputation done in 6 ms.\\n--------------------\\nWarnings occurred during reading the file:\\n--------------------\\nCannot read image file: /mnt/data/Code/pycellin/sample_data/FakeTracks.tif.\\n--------------------\\nFile loaded on Tue, 17 Oct 2023 18:15:18\\nTrackMate v8.0.0-SNAPSHOT-f411154ed1a4b9de350bbfe91c230cf3ae7639a3\\nPlease note that TrackMate is available through Fiji, and is based on a publication. If you use it successfully for your research please be so kind to cite our work:\\nErshov, D., Phan, MS., Pylvänäinen, J.W., Rigaud S.U., et al. TrackMate 7: integrating state-of-the-art segmentation algorithms into tracking pipelines. Nat Methods (2022). https://doi.org/10.1038/s41592-022-01507-1\\nhttps://doi.org/10.1038/s41592-022-01507-1\\nand / or:\\nTinevez, JY.; Perry, N. & Schindelin, J. et al. (2017), 'TrackMate: An open and extensible platform for single-particle tracking.', Methods 115: 80-90, PMID 27713081.\\nhttps://www.sciencedirect.com/science/article/pii/S1046202316303346\\nSaving data...\\nWarning: The source image does not match a file on the system.TrackMate won't be able to reload it when opening this XML file.\\nTo fix this, save the source image to a TIF file before saving the TrackMate session.\\nComputing edge features:\\n - Directional change in 4 ms.\\n - Edge speed in 14 ms.\\n - Edge target in 11 ms.\\n - Edge location in 5 ms.\\nComputation done in 34 ms.\\nComputing track features:\\n - Branching analyzer in 0 ms.\\n - Track duration in 3 ms.\\n - Track index in 0 ms.\\n - Track location in 0 ms.\\n - Track speed in 1 ms.\\n - Track quality in 0 ms.\\n - Track motility analysis in 1 ms.\\nComputation done in 5 ms.\\n \", 'Settings': '\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n ', 'GUIState': '\\n ', 'DisplaySettings': '{\\n \"name\": \"CurrentDisplaySettings\",\\n \"spotUniformColor\": \"204, 51, 204, 255\",\\n \"spotColorByType\": \"TRACKS\",\\n \"spotColorByFeature\": \"TRACK_INDEX\",\\n \"spotDisplayRadius\": 1.0,\\n \"spotDisplayedAsRoi\": true,\\n \"spotMin\": 0.0,\\n \"spotMax\": 75.0,\\n \"spotShowName\": false,\\n \"trackMin\": 0.0,\\n \"trackMax\": 10.0,\\n \"trackColorByType\": \"TRACKS\",\\n \"trackColorByFeature\": \"TRACK_INDEX\",\\n \"trackUniformColor\": \"204, 204, 51, 255\",\\n \"undefinedValueColor\": \"0, 0, 0, 255\",\\n \"missingValueColor\": \"89, 89, 89, 255\",\\n \"highlightColor\": \"51, 230, 51, 255\",\\n \"trackDisplayMode\": \"LOCAL\",\\n \"colormap\": \"Jet\",\\n \"limitZDrawingDepth\": false,\\n \"drawingZDepth\": 10.0,\\n \"fadeTracks\": true,\\n \"fadeTrackRange\": 15,\\n \"useAntialiasing\": true,\\n \"spotVisible\": true,\\n \"trackVisible\": true,\\n \"font\": {\\n \"name\": \"Arial\",\\n \"style\": 1,\\n \"size\": 12,\\n \"pointSize\": 12.0,\\n \"fontSerializedDataVersion\": 1\\n },\\n \"lineThickness\": 1.0,\\n \"selectionLineThickness\": 4.0,\\n \"trackschemeBackgroundColor1\": \"128, 128, 128, 255\",\\n \"trackschemeBackgroundColor2\": \"192, 192, 192, 255\",\\n \"trackschemeForegroundColor\": \"0, 0, 0, 255\",\\n \"trackschemeDecorationColor\": \"0, 0, 0, 255\",\\n \"trackschemeFillBox\": false,\\n \"spotFilled\": true,\\n \"spotTransparencyAlpha\": 0.42000000000000004\\n}\\n'}, props_metadata=PropsMetadata(props={'QUALITY': Property(identifier='QUALITY', name='Quality', description='Quality', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'POSITION_T': Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='sec'), 'RADIUS': Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'VISIBILITY': Property(identifier='VISIBILITY', name='Visibility', description='Visibility', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'MANUAL_SPOT_COLOR': Property(identifier='MANUAL_SPOT_COLOR', name='Manual spot color', description='Manual spot color', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'MEAN_INTENSITY_CH1': Property(identifier='MEAN_INTENSITY_CH1', name='Mean intensity ch1', description='Mean intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MEDIAN_INTENSITY_CH1': Property(identifier='MEDIAN_INTENSITY_CH1', name='Median intensity ch1', description='Median intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MIN_INTENSITY_CH1': Property(identifier='MIN_INTENSITY_CH1', name='Min intensity ch1', description='Min intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MAX_INTENSITY_CH1': Property(identifier='MAX_INTENSITY_CH1', name='Max intensity ch1', description='Max intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'TOTAL_INTENSITY_CH1': Property(identifier='TOTAL_INTENSITY_CH1', name='Sum intensity ch1', description='Sum intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'STD_INTENSITY_CH1': Property(identifier='STD_INTENSITY_CH1', name='Std intensity ch1', description='Std intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'CONTRAST_CH1': Property(identifier='CONTRAST_CH1', name='Contrast ch1', description='Contrast ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SNR_CH1': Property(identifier='SNR_CH1', name='Signal/Noise ratio ch1', description='Signal/Noise ratio ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'ELLIPSE_X0': Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_Y0': Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_MAJOR': Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_MINOR': Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_THETA': Property(identifier='ELLIPSE_THETA', name='Ellipse angle', description='Ellipse angle', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='rad'), 'ELLIPSE_ASPECTRATIO': Property(identifier='ELLIPSE_ASPECTRATIO', name='Ellipse aspect ratio', description='Ellipse aspect ratio', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'AREA': Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel^2'), 'PERIMETER': Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'CIRCULARITY': Property(identifier='CIRCULARITY', name='Circularity', description='Circularity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SOLIDITY': Property(identifier='SOLIDITY', name='Solidity', description='Solidity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SHAPE_INDEX': Property(identifier='SHAPE_INDEX', name='Shape index', description='Shape index', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SPOT_SOURCE_ID': Property(identifier='SPOT_SOURCE_ID', name='Source spot ID', description='Source spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'SPOT_TARGET_ID': Property(identifier='SPOT_TARGET_ID', name='Target spot ID', description='Target spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'LINK_COST': Property(identifier='LINK_COST', name='Edge cost', description='Edge cost', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit=None), 'DIRECTIONAL_CHANGE_RATE': Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/sec'), 'SPEED': Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'DISPLACEMENT': Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_TIME': Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='sec'), 'MANUAL_EDGE_COLOR': Property(identifier='MANUAL_EDGE_COLOR', name='Manual edge color', description='Manual edge color', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'TRACK_INDEX': Property(identifier='TRACK_INDEX', name='Track index', description='Track index', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_SPOTS': Property(identifier='NUMBER_SPOTS', name='Number of spots in track', description='Number of spots in track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_GAPS': Property(identifier='NUMBER_GAPS', name='Number of gaps', description='Number of gaps', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_SPLITS': Property(identifier='NUMBER_SPLITS', name='Number of split events', description='Number of split events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_MERGES': Property(identifier='NUMBER_MERGES', name='Number of merge events', description='Number of merge events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_COMPLEX': Property(identifier='NUMBER_COMPLEX', name='Number of complex points', description='Number of complex points', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'LONGEST_GAP': Property(identifier='LONGEST_GAP', name='Longest gap', description='Longest gap', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'TRACK_DURATION': Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_START': Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_STOP': Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_DISPLACEMENT': Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_MEAN_SPEED': Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MAX_SPEED': Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MIN_SPEED': Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MEDIAN_SPEED': Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_STD_SPEED': Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MEAN_QUALITY': Property(identifier='TRACK_MEAN_QUALITY', name='Track mean quality', description='Track mean quality', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'TOTAL_DISTANCE_TRAVELED': Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'MAX_DISTANCE_TRAVELED': Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'CONFINEMENT_RATIO': Property(identifier='CONFINEMENT_RATIO', name='Confinement ratio', description='Confinement ratio', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'MEAN_STRAIGHT_LINE_SPEED': Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'LINEARITY_OF_FORWARD_PROGRESSION': Property(identifier='LINEARITY_OF_FORWARD_PROGRESSION', name='Linearity of forward progression', description='Linearity of forward progression', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'MEAN_DIRECTIONAL_CHANGE_RATE': Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/sec'), 'TRACK_ID': Property(identifier='TRACK_ID', name='Track ID', description='Track ID', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'FRAME': Property(identifier='FRAME', name='Frame', description='Frame', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'POSITION_X': Property(identifier='POSITION_X', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_X_LOCATION': Property(identifier='EDGE_X_LOCATION', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_X_LOCATION': Property(identifier='TRACK_X_LOCATION', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'POSITION_Y': Property(identifier='POSITION_Y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_Y_LOCATION': Property(identifier='EDGE_Y_LOCATION', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_Y_LOCATION': Property(identifier='TRACK_Y_LOCATION', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'POSITION_Z': Property(identifier='POSITION_Z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_Z_LOCATION': Property(identifier='EDGE_Z_LOCATION', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_Z_LOCATION': Property(identifier='TRACK_Z_LOCATION', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel')}), data=Data(cell_data={0: , 4: }, cycle_data=None))" + "Model(model_metadata={'name': 'FakeTracks', 'file_location': '../sample_data/FakeTracks.xml', 'provenance': 'TrackMate', 'date': '2025-09-10 17:22:15.792372', 'space_unit': 'pixel', 'time_unit': 'sec', 'pycellin_version': '0.4.0', 'TrackMate_version': '8.0.0-SNAPSHOT-f411154ed1a4b9de350bbfe91c230cf3ae7639a3', 'time_step': 1.0, 'pixel_size': {'width': 1.0, 'height': 1.0, 'depth': 1.0}, 'Log': \"0.1101/2021.09.03.458852\\nand / or:\\nTinevez, JY.; Perry, N. & Schindelin, J. et al. (2017), 'TrackMate: An open and extensible platform for single-particle tracking.', Methods 115: 80-90, PMID 27713081.\\nhttps://www.sciencedirect.com/science/article/pii/S1046202316303346\\n\\nNumerical feature analyzers:\\nSpot feature analyzers:\\n - Manual spot color provides: Spot color; is manual.\\n - Spot intensity provides: Mean ch1, Median ch1, Min ch1, Max ch1, Sum ch1, Std ch1.\\n - Spot contrast and SNR provides: Ctrst ch1, SNR ch1.\\n - Spot fit 2D ellipse provides: El. x0, El. y0, El. long axis, El. sh. axis, El. angle, El. a.r.\\n - Spot 2D shape descriptors provides: Area, Perim., Circ., Solidity.\\nEdge feature analyzers:\\n - Directional change provides: γ rate.\\n - Edge speed provides: Speed, Disp.\\n - Edge target provides: Source ID, Target ID, Cost.\\n - Edge location provides: Edge T, Edge X, Edge Y, Edge Z.\\n - Manual edge color provides: Edge color; is manual.\\nTrack feature analyzers:\\n - Branching analyzer provides: N spots, N gaps, N splits, N merges, N complex, Lgst gap.\\n - Track duration provides: Duration, Track start, Track stop, Track disp.\\n - Track index provides: Index, ID.\\n - Track location provides: Track X, Track Y, Track Z.\\n - Track speed provides: Mean sp., Max speed, Min speed, Med. speed, Std speed.\\n - Track quality provides: Mean Q.\\n - Track motility analysis provides: Total dist., Max dist., Cfn. ratio, Mn. v. line, Fwd. progr., Mn. γ rate.\\n\\nImage region of interest:\\nImage data:\\nFor the image named: FakeTracks.tif.\\nMatching file FakeTracks.tif in current folder.\\nGeometry:\\n X = 0 - 127, dx = 1.00000\\n Y = 0 - 127, dy = 1.00000\\n Z = 0 - 0, dz = 1.00000\\n T = 0 - 49, dt = 1.00000\\n\\nConfigured detector StarDist detector with settings:\\n - target channel: 1\\n\\nStarting detection process using 24 threads.\\nDetection processes 1 frame simultaneously and allocates 24 threads per frame.\\nFound 107 spots.\\nDetection done in 2.3 s.\\n\\nComputing spot quality histogram...\\nInitial thresholding with a quality threshold above 0.4 ...\\nStarting initial filtering process.\\nRetained 107 spots out of 107.\\n\\nAdding morphology analyzers...\\n - Spot fit 2D ellipse provides: El. x0, El. y0, El. long axis, El. sh. axis, El. angle, El. a.r.\\n - Spot 2D shape descriptors provides: Area, Perim., Circ., Solidity.\\n\\nCalculating spot features...\\nCalculating features done in 0.1 s.\\n\\nPerforming spot filtering on the following features:\\n - on Area below 49.9\\nKept 106 spots out of 107.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 5.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 5.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 5.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 17 tracks.\\n - avg size: 5.1 spots.\\n - min size: 2 spots.\\n - max size: 12 spots.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 8.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 5.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 5.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 9 tracks.\\n - avg size: 10.7 spots.\\n - min size: 2 spots.\\n - max size: 42 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 23.9\\nKept 4 spots out of 9.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 23.9\\nKept 4 spots out of 9.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 8.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 5.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 8.0\\n - merging feature penalties: \\n - splitting max distance: 8.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 8.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\n\\nConfigured tracker LAP Tracker with settings:\\n - max frame gap: 2\\n - alternative linking cost factor: 1.05\\n - linking feature penalties: \\n - linking max distance: 10.0\\n - gap closing max distance: 10.0\\n - merging feature penalties: \\n - splitting max distance: 10.0\\n - blocking value: Infinity\\n - allow gap closing: true\\n - allow track splitting: true\\n - allow track merging: true\\n - merging max distance: 10.0\\n - splitting feature penalties: \\n - cutoff percentile: 0.9\\n - gap closing feature penalties: \\n\\nStarting tracking process.\\nTracking done in 0.0 s.\\nFound 7 tracks.\\n - avg size: 13.7 spots.\\n - min size: 2 spots.\\n - max size: 74 spots.\\n\\nCalculating features done in 0.0 s.\\n\\nPerforming track filtering on the following features:\\n - on Track start below 78.0\\n - on Total distance traveled above 20.8\\nKept 2 spots out of 7.\\nSaving data...\\nComputing edge features:\\n - Directional change in 2 ms.\\n - Edge speed in 0 ms.\\n - Edge target in 1 ms.\\n - Edge location in 1 ms.\\nComputation done in 4 ms.\\nComputing track features:\\n - Branching analyzer in 0 ms.\\n - Track duration in 0 ms.\\n - Track index in 0 ms.\\n - Track location in 4 ms.\\n - Track speed in 0 ms.\\n - Track quality in 1 ms.\\n - Track motility analysis in 0 ms.\\nComputation done in 6 ms.\\n--------------------\\nWarnings occurred during reading the file:\\n--------------------\\nCannot read image file: /mnt/data/Code/pycellin/sample_data/FakeTracks.tif.\\n--------------------\\nFile loaded on Tue, 17 Oct 2023 18:15:18\\nTrackMate v8.0.0-SNAPSHOT-f411154ed1a4b9de350bbfe91c230cf3ae7639a3\\nPlease note that TrackMate is available through Fiji, and is based on a publication. If you use it successfully for your research please be so kind to cite our work:\\nErshov, D., Phan, MS., Pylvänäinen, J.W., Rigaud S.U., et al. TrackMate 7: integrating state-of-the-art segmentation algorithms into tracking pipelines. Nat Methods (2022). https://doi.org/10.1038/s41592-022-01507-1\\nhttps://doi.org/10.1038/s41592-022-01507-1\\nand / or:\\nTinevez, JY.; Perry, N. & Schindelin, J. et al. (2017), 'TrackMate: An open and extensible platform for single-particle tracking.', Methods 115: 80-90, PMID 27713081.\\nhttps://www.sciencedirect.com/science/article/pii/S1046202316303346\\nSaving data...\\nWarning: The source image does not match a file on the system.TrackMate won't be able to reload it when opening this XML file.\\nTo fix this, save the source image to a TIF file before saving the TrackMate session.\\nComputing edge features:\\n - Directional change in 4 ms.\\n - Edge speed in 14 ms.\\n - Edge target in 11 ms.\\n - Edge location in 5 ms.\\nComputation done in 34 ms.\\nComputing track features:\\n - Branching analyzer in 0 ms.\\n - Track duration in 3 ms.\\n - Track index in 0 ms.\\n - Track location in 0 ms.\\n - Track speed in 1 ms.\\n - Track quality in 0 ms.\\n - Track motility analysis in 1 ms.\\nComputation done in 5 ms.\\n \", 'Settings': '\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n ', 'GUIState': '\\n ', 'DisplaySettings': '{\\n \"name\": \"CurrentDisplaySettings\",\\n \"spotUniformColor\": \"204, 51, 204, 255\",\\n \"spotColorByType\": \"TRACKS\",\\n \"spotColorByFeature\": \"TRACK_INDEX\",\\n \"spotDisplayRadius\": 1.0,\\n \"spotDisplayedAsRoi\": true,\\n \"spotMin\": 0.0,\\n \"spotMax\": 75.0,\\n \"spotShowName\": false,\\n \"trackMin\": 0.0,\\n \"trackMax\": 10.0,\\n \"trackColorByType\": \"TRACKS\",\\n \"trackColorByFeature\": \"TRACK_INDEX\",\\n \"trackUniformColor\": \"204, 204, 51, 255\",\\n \"undefinedValueColor\": \"0, 0, 0, 255\",\\n \"missingValueColor\": \"89, 89, 89, 255\",\\n \"highlightColor\": \"51, 230, 51, 255\",\\n \"trackDisplayMode\": \"LOCAL\",\\n \"colormap\": \"Jet\",\\n \"limitZDrawingDepth\": false,\\n \"drawingZDepth\": 10.0,\\n \"fadeTracks\": true,\\n \"fadeTrackRange\": 15,\\n \"useAntialiasing\": true,\\n \"spotVisible\": true,\\n \"trackVisible\": true,\\n \"font\": {\\n \"name\": \"Arial\",\\n \"style\": 1,\\n \"size\": 12,\\n \"pointSize\": 12.0,\\n \"fontSerializedDataVersion\": 1\\n },\\n \"lineThickness\": 1.0,\\n \"selectionLineThickness\": 4.0,\\n \"trackschemeBackgroundColor1\": \"128, 128, 128, 255\",\\n \"trackschemeBackgroundColor2\": \"192, 192, 192, 255\",\\n \"trackschemeForegroundColor\": \"0, 0, 0, 255\",\\n \"trackschemeDecorationColor\": \"0, 0, 0, 255\",\\n \"trackschemeFillBox\": false,\\n \"spotFilled\": true,\\n \"spotTransparencyAlpha\": 0.42000000000000004\\n}\\n'}, props_metadata=PropsMetadata(props={'QUALITY': Property(identifier='QUALITY', name='Quality', description='Quality', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'POSITION_T': Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='sec'), 'RADIUS': Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'VISIBILITY': Property(identifier='VISIBILITY', name='Visibility', description='Visibility', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'MANUAL_SPOT_COLOR': Property(identifier='MANUAL_SPOT_COLOR', name='Manual spot color', description='Manual spot color', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'MEAN_INTENSITY_CH1': Property(identifier='MEAN_INTENSITY_CH1', name='Mean intensity ch1', description='Mean intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MEDIAN_INTENSITY_CH1': Property(identifier='MEDIAN_INTENSITY_CH1', name='Median intensity ch1', description='Median intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MIN_INTENSITY_CH1': Property(identifier='MIN_INTENSITY_CH1', name='Min intensity ch1', description='Min intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'MAX_INTENSITY_CH1': Property(identifier='MAX_INTENSITY_CH1', name='Max intensity ch1', description='Max intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'TOTAL_INTENSITY_CH1': Property(identifier='TOTAL_INTENSITY_CH1', name='Sum intensity ch1', description='Sum intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'STD_INTENSITY_CH1': Property(identifier='STD_INTENSITY_CH1', name='Std intensity ch1', description='Std intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'CONTRAST_CH1': Property(identifier='CONTRAST_CH1', name='Contrast ch1', description='Contrast ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SNR_CH1': Property(identifier='SNR_CH1', name='Signal/Noise ratio ch1', description='Signal/Noise ratio ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'ELLIPSE_X0': Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_Y0': Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_MAJOR': Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_MINOR': Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'ELLIPSE_THETA': Property(identifier='ELLIPSE_THETA', name='Ellipse angle', description='Ellipse angle', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='rad'), 'ELLIPSE_ASPECTRATIO': Property(identifier='ELLIPSE_ASPECTRATIO', name='Ellipse aspect ratio', description='Ellipse aspect ratio', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'AREA': Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel^2'), 'PERIMETER': Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'CIRCULARITY': Property(identifier='CIRCULARITY', name='Circularity', description='Circularity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SOLIDITY': Property(identifier='SOLIDITY', name='Solidity', description='Solidity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SHAPE_INDEX': Property(identifier='SHAPE_INDEX', name='Shape index', description='Shape index', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None), 'SPOT_SOURCE_ID': Property(identifier='SPOT_SOURCE_ID', name='Source spot ID', description='Source spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'SPOT_TARGET_ID': Property(identifier='SPOT_TARGET_ID', name='Target spot ID', description='Target spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'LINK_COST': Property(identifier='LINK_COST', name='Edge cost', description='Edge cost', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit=None), 'DIRECTIONAL_CHANGE_RATE': Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/sec'), 'SPEED': Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'DISPLACEMENT': Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_TIME': Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='sec'), 'MANUAL_EDGE_COLOR': Property(identifier='MANUAL_EDGE_COLOR', name='Manual edge color', description='Manual edge color', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None), 'TRACK_INDEX': Property(identifier='TRACK_INDEX', name='Track index', description='Track index', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_SPOTS': Property(identifier='NUMBER_SPOTS', name='Number of spots in track', description='Number of spots in track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_GAPS': Property(identifier='NUMBER_GAPS', name='Number of gaps', description='Number of gaps', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_SPLITS': Property(identifier='NUMBER_SPLITS', name='Number of split events', description='Number of split events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_MERGES': Property(identifier='NUMBER_MERGES', name='Number of merge events', description='Number of merge events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'NUMBER_COMPLEX': Property(identifier='NUMBER_COMPLEX', name='Number of complex points', description='Number of complex points', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'LONGEST_GAP': Property(identifier='LONGEST_GAP', name='Longest gap', description='Longest gap', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'TRACK_DURATION': Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_START': Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_STOP': Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='sec'), 'TRACK_DISPLACEMENT': Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_MEAN_SPEED': Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MAX_SPEED': Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MIN_SPEED': Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MEDIAN_SPEED': Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_STD_SPEED': Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'TRACK_MEAN_QUALITY': Property(identifier='TRACK_MEAN_QUALITY', name='Track mean quality', description='Track mean quality', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'TOTAL_DISTANCE_TRAVELED': Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'MAX_DISTANCE_TRAVELED': Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'CONFINEMENT_RATIO': Property(identifier='CONFINEMENT_RATIO', name='Confinement ratio', description='Confinement ratio', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'MEAN_STRAIGHT_LINE_SPEED': Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel/sec'), 'LINEARITY_OF_FORWARD_PROGRESSION': Property(identifier='LINEARITY_OF_FORWARD_PROGRESSION', name='Linearity of forward progression', description='Linearity of forward progression', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None), 'MEAN_DIRECTIONAL_CHANGE_RATE': Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/sec'), 'TRACK_ID': Property(identifier='TRACK_ID', name='Track ID', description='Track ID', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None), 'FRAME': Property(identifier='FRAME', name='Frame', description='Frame', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None), 'POSITION_X': Property(identifier='POSITION_X', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_X_LOCATION': Property(identifier='EDGE_X_LOCATION', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_X_LOCATION': Property(identifier='TRACK_X_LOCATION', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'POSITION_Y': Property(identifier='POSITION_Y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_Y_LOCATION': Property(identifier='EDGE_Y_LOCATION', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_Y_LOCATION': Property(identifier='TRACK_Y_LOCATION', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel'), 'POSITION_Z': Property(identifier='POSITION_Z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='pixel'), 'EDGE_Z_LOCATION': Property(identifier='EDGE_Z_LOCATION', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='pixel'), 'TRACK_Z_LOCATION': Property(identifier='TRACK_Z_LOCATION', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='pixel')}), data=Data(cell_data={0: , 4: }, cycle_data=None))" ] }, "execution_count": 80, @@ -15001,7 +15001,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.18" + "version": "3.11.13" } }, "nbformat": 4, diff --git a/notebooks/Managing properties.ipynb b/notebooks/Managing properties.ipynb index 78a5eb1..e68f843 100644 --- a/notebooks/Managing properties.ipynb +++ b/notebooks/Managing properties.ipynb @@ -153,7 +153,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'RADIUS'\n", " Name: Radius\n", " Description: Radius\n", @@ -161,7 +161,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'VISIBILITY'\n", " Name: Visibility\n", " Description: Visibility\n", @@ -313,7 +313,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'ELLIPSE_Y0'\n", " Name: Ellipse center y0\n", " Description: Ellipse center y0\n", @@ -321,7 +321,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'ELLIPSE_MAJOR'\n", " Name: Ellipse long axis\n", " Description: Ellipse long axis\n", @@ -329,7 +329,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'ELLIPSE_MINOR'\n", " Name: Ellipse short axis\n", " Description: Ellipse short axis\n", @@ -337,7 +337,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'ELLIPSE_THETA'\n", " Name: Ellipse angle\n", " Description: Ellipse angle\n", @@ -361,7 +361,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm^2\n", + " Unit: micrometer^2\n", "Property 'PERIMETER'\n", " Name: Perimeter\n", " Description: Perimeter\n", @@ -369,7 +369,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'CIRCULARITY'\n", " Name: Circularity\n", " Description: Circularity\n", @@ -433,7 +433,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: rad/min\n", + " Unit: rad/minute\n", "Property 'SPEED'\n", " Name: Speed\n", " Description: Speed\n", @@ -441,7 +441,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'DISPLACEMENT'\n", " Name: Displacement\n", " Description: Displacement\n", @@ -449,7 +449,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'EDGE_TIME'\n", " Name: Edge time\n", " Description: Edge time\n", @@ -457,7 +457,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'MANUAL_EDGE_COLOR'\n", " Name: Manual edge color\n", " Description: Manual edge color\n", @@ -481,7 +481,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'DIVISION_TIME_STD'\n", " Name: Std cell division time\n", " Description: Std cell division time\n", @@ -489,7 +489,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'NUMBER_SPOTS'\n", " Name: Number of spots in track\n", " Description: Number of spots in track\n", @@ -545,7 +545,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'TRACK_START'\n", " Name: Track start\n", " Description: Track start\n", @@ -553,7 +553,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'TRACK_STOP'\n", " Name: Track stop\n", " Description: Track stop\n", @@ -561,7 +561,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: min\n", + " Unit: minute\n", "Property 'TRACK_DISPLACEMENT'\n", " Name: Track displacement\n", " Description: Track displacement\n", @@ -569,7 +569,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'TRACK_MEAN_SPEED'\n", " Name: Track mean speed\n", " Description: Track mean speed\n", @@ -577,7 +577,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'TRACK_MAX_SPEED'\n", " Name: Track max speed\n", " Description: Track max speed\n", @@ -585,7 +585,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'TRACK_MIN_SPEED'\n", " Name: Track min speed\n", " Description: Track min speed\n", @@ -593,7 +593,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'TRACK_MEDIAN_SPEED'\n", " Name: Track median speed\n", " Description: Track median speed\n", @@ -601,7 +601,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'TRACK_STD_SPEED'\n", " Name: Track std speed\n", " Description: Track std speed\n", @@ -609,7 +609,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'TRACK_MEAN_QUALITY'\n", " Name: Track mean quality\n", " Description: Track mean quality\n", @@ -625,7 +625,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'MAX_DISTANCE_TRAVELED'\n", " Name: Max distance traveled\n", " Description: Max distance traveled\n", @@ -633,7 +633,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'CONFINEMENT_RATIO'\n", " Name: Confinement ratio\n", " Description: Confinement ratio\n", @@ -649,7 +649,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n", + " Unit: micrometer/minute\n", "Property 'LINEARITY_OF_FORWARD_PROGRESSION'\n", " Name: Linearity of forward progression\n", " Description: Linearity of forward progression\n", @@ -665,7 +665,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: rad/min\n", + " Unit: rad/minute\n", "Property 'lineage_name'\n", " Name: lineage name\n", " Description: Name of the track\n", @@ -689,7 +689,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'cell_y'\n", " Name: Y\n", " Description: Y coordinate of the cell\n", @@ -697,7 +697,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'cell_z'\n", " Name: Z\n", " Description: Z coordinate of the cell\n", @@ -705,7 +705,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'frame'\n", " Name: Frame\n", " Description: Frame\n", @@ -721,7 +721,7 @@ " Type: node\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'link_x'\n", " Name: Edge X\n", " Description: X coordinate of the link, i.e. mean coordinate of its two cells\n", @@ -729,7 +729,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'link_y'\n", " Name: Edge Y\n", " Description: Y coordinate of the link, i.e. mean coordinate of its two cells\n", @@ -737,7 +737,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'link_z'\n", " Name: Edge Z\n", " Description: Z coordinate of the link, i.e. mean coordinate of its two cells\n", @@ -745,7 +745,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'lineage_ID'\n", " Name: Track ID\n", " Description: Unique identifier of the lineage\n", @@ -769,7 +769,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'lineage_y'\n", " Name: Track mean Y\n", " Description: Y coordinate of the lineage, i.e. mean coordinate of its cells\n", @@ -777,7 +777,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n", + " Unit: micrometer\n", "Property 'lineage_z'\n", " Name: Track mean Z\n", " Description: Z coordinate of the lineage, i.e. mean coordinate of its cells\n", @@ -785,7 +785,7 @@ " Type: lineage\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm\n" + " Unit: micrometer\n" ] } ], @@ -3098,7 +3098,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "E:\\Code\\pycellin\\pycellin\\pycellin\\classes\\model.py:936: UserWarning:\n", + "/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:936: UserWarning:\n", "\n", "A Property 'absolute_age' already exists with the same type. Not overwriting the old Property.\n", "\n" @@ -3141,8 +3141,8 @@ "data": { "text/plain": [ "{'QUALITY': Property(identifier='QUALITY', name='Quality', description='Quality', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'POSITION_T': Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='min'),\n", - " 'RADIUS': Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'POSITION_T': Property(identifier='POSITION_T', name='T', description='T', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='minute'),\n", + " 'RADIUS': Property(identifier='RADIUS', name='Radius', description='Radius', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'VISIBILITY': Property(identifier='VISIBILITY', name='Visibility', description='Visibility', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None),\n", " 'MANUAL_SPOT_COLOR': Property(identifier='MANUAL_SPOT_COLOR', name='Manual spot color', description='Manual spot color', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None),\n", " 'MEAN_INTENSITY_CH1': Property(identifier='MEAN_INTENSITY_CH1', name='Mean intensity ch1', description='Mean intensity ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", @@ -3161,14 +3161,14 @@ " 'SNR_CH1': Property(identifier='SNR_CH1', name='Signal/Noise ratio ch1', description='Signal/Noise ratio ch1', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", " 'CONTRAST_CH2': Property(identifier='CONTRAST_CH2', name='Contrast ch2', description='Contrast ch2', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", " 'SNR_CH2': Property(identifier='SNR_CH2', name='Signal/Noise ratio ch2', description='Signal/Noise ratio ch2', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'ELLIPSE_X0': Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'ELLIPSE_Y0': Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'ELLIPSE_MAJOR': Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'ELLIPSE_MINOR': Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'ELLIPSE_X0': Property(identifier='ELLIPSE_X0', name='Ellipse center x0', description='Ellipse center x0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'ELLIPSE_Y0': Property(identifier='ELLIPSE_Y0', name='Ellipse center y0', description='Ellipse center y0', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'ELLIPSE_MAJOR': Property(identifier='ELLIPSE_MAJOR', name='Ellipse long axis', description='Ellipse long axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'ELLIPSE_MINOR': Property(identifier='ELLIPSE_MINOR', name='Ellipse short axis', description='Ellipse short axis', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'ELLIPSE_THETA': Property(identifier='ELLIPSE_THETA', name='Ellipse angle', description='Ellipse angle', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='rad'),\n", " 'ELLIPSE_ASPECTRATIO': Property(identifier='ELLIPSE_ASPECTRATIO', name='Ellipse aspect ratio', description='Ellipse aspect ratio', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'AREA': Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm^2'),\n", - " 'PERIMETER': Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'AREA': Property(identifier='AREA', name='Area', description='Area', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer^2'),\n", + " 'PERIMETER': Property(identifier='PERIMETER', name='Perimeter', description='Perimeter', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'CIRCULARITY': Property(identifier='CIRCULARITY', name='Circularity', description='Circularity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", " 'SOLIDITY': Property(identifier='SOLIDITY', name='Solidity', description='Solidity', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", " 'SHAPE_INDEX': Property(identifier='SHAPE_INDEX', name='Shape index', description='Shape index', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit=None),\n", @@ -3176,54 +3176,54 @@ " 'SPOT_SOURCE_ID': Property(identifier='SPOT_SOURCE_ID', name='Source spot ID', description='Source spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None),\n", " 'SPOT_TARGET_ID': Property(identifier='SPOT_TARGET_ID', name='Target spot ID', description='Target spot ID', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None),\n", " 'LINK_COST': Property(identifier='LINK_COST', name='Edge cost', description='Edge cost', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'DIRECTIONAL_CHANGE_RATE': Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/min'),\n", - " 'SPEED': Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", - " 'DISPLACEMENT': Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'EDGE_TIME': Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='min'),\n", + " 'DIRECTIONAL_CHANGE_RATE': Property(identifier='DIRECTIONAL_CHANGE_RATE', name='Directional change rate', description='Directional change rate', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='rad/minute'),\n", + " 'SPEED': Property(identifier='SPEED', name='Speed', description='Speed', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", + " 'DISPLACEMENT': Property(identifier='DISPLACEMENT', name='Displacement', description='Displacement', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'EDGE_TIME': Property(identifier='EDGE_TIME', name='Edge time', description='Edge time', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='minute'),\n", " 'MANUAL_EDGE_COLOR': Property(identifier='MANUAL_EDGE_COLOR', name='Manual edge color', description='Manual edge color', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='int', unit=None),\n", " 'TRACK_INDEX': Property(identifier='TRACK_INDEX', name='Track index', description='Track index', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", - " 'DIVISION_TIME_MEAN': Property(identifier='DIVISION_TIME_MEAN', name='Mean cell division time', description='Mean cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min'),\n", - " 'DIVISION_TIME_STD': Property(identifier='DIVISION_TIME_STD', name='Std cell division time', description='Std cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min'),\n", + " 'DIVISION_TIME_MEAN': Property(identifier='DIVISION_TIME_MEAN', name='Mean cell division time', description='Mean cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute'),\n", + " 'DIVISION_TIME_STD': Property(identifier='DIVISION_TIME_STD', name='Std cell division time', description='Std cell division time', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute'),\n", " 'NUMBER_SPOTS': Property(identifier='NUMBER_SPOTS', name='Number of spots in track', description='Number of spots in track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'NUMBER_GAPS': Property(identifier='NUMBER_GAPS', name='Number of gaps', description='Number of gaps', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'NUMBER_SPLITS': Property(identifier='NUMBER_SPLITS', name='Number of split events', description='Number of split events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'NUMBER_MERGES': Property(identifier='NUMBER_MERGES', name='Number of merge events', description='Number of merge events', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'NUMBER_COMPLEX': Property(identifier='NUMBER_COMPLEX', name='Number of complex points', description='Number of complex points', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'LONGEST_GAP': Property(identifier='LONGEST_GAP', name='Longest gap', description='Longest gap', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", - " 'TRACK_DURATION': Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min'),\n", - " 'TRACK_START': Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min'),\n", - " 'TRACK_STOP': Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='min'),\n", - " 'TRACK_DISPLACEMENT': Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'TRACK_MEAN_SPEED': Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", - " 'TRACK_MAX_SPEED': Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", - " 'TRACK_MIN_SPEED': Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", - " 'TRACK_MEDIAN_SPEED': Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", - " 'TRACK_STD_SPEED': Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", + " 'TRACK_DURATION': Property(identifier='TRACK_DURATION', name='Track duration', description='Track duration', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute'),\n", + " 'TRACK_START': Property(identifier='TRACK_START', name='Track start', description='Track start', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute'),\n", + " 'TRACK_STOP': Property(identifier='TRACK_STOP', name='Track stop', description='Track stop', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='minute'),\n", + " 'TRACK_DISPLACEMENT': Property(identifier='TRACK_DISPLACEMENT', name='Track displacement', description='Track displacement', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'TRACK_MEAN_SPEED': Property(identifier='TRACK_MEAN_SPEED', name='Track mean speed', description='Track mean speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", + " 'TRACK_MAX_SPEED': Property(identifier='TRACK_MAX_SPEED', name='Track max speed', description='Track max speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", + " 'TRACK_MIN_SPEED': Property(identifier='TRACK_MIN_SPEED', name='Track min speed', description='Track min speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", + " 'TRACK_MEDIAN_SPEED': Property(identifier='TRACK_MEDIAN_SPEED', name='Track median speed', description='Track median speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", + " 'TRACK_STD_SPEED': Property(identifier='TRACK_STD_SPEED', name='Track std speed', description='Track std speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", " 'TRACK_MEAN_QUALITY': Property(identifier='TRACK_MEAN_QUALITY', name='Track mean quality', description='Track mean quality', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'TOTAL_DISTANCE_TRAVELED': Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'MAX_DISTANCE_TRAVELED': Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'TOTAL_DISTANCE_TRAVELED': Property(identifier='TOTAL_DISTANCE_TRAVELED', name='Total distance traveled', description='Total distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'MAX_DISTANCE_TRAVELED': Property(identifier='MAX_DISTANCE_TRAVELED', name='Max distance traveled', description='Max distance traveled', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'CONFINEMENT_RATIO': Property(identifier='CONFINEMENT_RATIO', name='Confinement ratio', description='Confinement ratio', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'MEAN_STRAIGHT_LINE_SPEED': Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm/min'),\n", + " 'MEAN_STRAIGHT_LINE_SPEED': Property(identifier='MEAN_STRAIGHT_LINE_SPEED', name='Mean straight line speed', description='Mean straight line speed', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer/minute'),\n", " 'LINEARITY_OF_FORWARD_PROGRESSION': Property(identifier='LINEARITY_OF_FORWARD_PROGRESSION', name='Linearity of forward progression', description='Linearity of forward progression', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit=None),\n", - " 'MEAN_DIRECTIONAL_CHANGE_RATE': Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/min'),\n", + " 'MEAN_DIRECTIONAL_CHANGE_RATE': Property(identifier='MEAN_DIRECTIONAL_CHANGE_RATE', name='Mean directional change rate', description='Mean directional change rate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='rad/minute'),\n", " 'lineage_name': Property(identifier='lineage_name', name='lineage name', description='Name of the track', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='string', unit=None),\n", " 'cell_ID': Property(identifier='cell_ID', name='cell ID', description='Unique identifier of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None),\n", - " 'cell_x': Property(identifier='cell_x', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'cell_y': Property(identifier='cell_y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'cell_z': Property(identifier='cell_z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'cell_x': Property(identifier='cell_x', name='X', description='X coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'cell_y': Property(identifier='cell_y', name='Y', description='Y coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'cell_z': Property(identifier='cell_z', name='Z', description='Z coordinate of the cell', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'frame': Property(identifier='frame', name='Frame', description='Frame', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='int', unit=None),\n", - " 'ROI_coords': Property(identifier='ROI_coords', name='ROI coords', description='List of coordinates of the region of interest', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'link_x': Property(identifier='link_x', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'link_y': Property(identifier='link_y', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'link_z': Property(identifier='link_z', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'ROI_coords': Property(identifier='ROI_coords', name='ROI coords', description='List of coordinates of the region of interest', provenance='TrackMate', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'link_x': Property(identifier='link_x', name='Edge X', description='X coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'link_y': Property(identifier='link_y', name='Edge Y', description='Y coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'link_z': Property(identifier='link_z', name='Edge Z', description='Z coordinate of the link, i.e. mean coordinate of its two cells', provenance='TrackMate', prop_type='edge', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'lineage_ID': Property(identifier='lineage_ID', name='Track ID', description='Unique identifier of the lineage', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", " 'FilteredTrack': Property(identifier='FilteredTrack', name='FilteredTrack', description='True if the track was not filtered out in TrackMate', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='int', unit=None),\n", - " 'lineage_x': Property(identifier='lineage_x', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'lineage_y': Property(identifier='lineage_y', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", - " 'lineage_z': Property(identifier='lineage_z', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='µm'),\n", + " 'lineage_x': Property(identifier='lineage_x', name='Track mean X', description='X coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'lineage_y': Property(identifier='lineage_y', name='Track mean Y', description='Y coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", + " 'lineage_z': Property(identifier='lineage_z', name='Track mean Z', description='Z coordinate of the lineage, i.e. mean coordinate of its cells', provenance='TrackMate', prop_type='lineage', lin_type='CellLineage', dtype='float', unit='micrometer'),\n", " 'absolute_age': Property(identifier='absolute_age', name='absolute age', description='Age of the cell since the beginning of the lineage', provenance='pycellin', prop_type='node', lin_type='CellLineage', dtype='int', unit='frame'),\n", " 'relative_age': Property(identifier='relative_age', name='Relative age', description='Age of the cell since the beginning of the current cell cycle', provenance='pycellin', prop_type='node', lin_type='CellLineage', dtype='int', unit='frame'),\n", - " 'rod_length': Property(identifier='rod_length', name='Rod length', description='Length of the cell, for rod-shaped cells only', provenance='pycellin', prop_type='node', lin_type='CellLineage', dtype='float', unit='µm')}" + " 'rod_length': Property(identifier='rod_length', name='Rod length', description='Length of the cell, for rod-shaped cells only', provenance='pycellin', prop_type='node', lin_type='CellLineage', dtype='float', unit='micrometer')}" ] }, "execution_count": 9, @@ -3268,7 +3268,7 @@ " Type: edge\n", " Lineage type: CellLineage\n", " Data type: float\n", - " Unit: µm/min\n" + " Unit: micrometer/minute\n" ] } ], @@ -3305,12 +3305,12 @@ "evalue": "Cycle lineages have not been computed yet. Please compute the cycle lineages first with `model.add_cycle_data()`.", "output_type": "error", "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[11], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43madd_division_time\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mE:\\Code\\pycellin\\pycellin\\pycellin\\classes\\model.py:1237\u001b[0m, in \u001b[0;36mModel.add_division_time\u001b[1;34m(self, in_time_unit, custom_identifier)\u001b[0m\n\u001b[0;32m 1232\u001b[0m prop \u001b[38;5;241m=\u001b[39m tracking\u001b[38;5;241m.\u001b[39mcreate_division_time_property(\n\u001b[0;32m 1233\u001b[0m custom_identifier\u001b[38;5;241m=\u001b[39mcustom_identifier,\n\u001b[0;32m 1234\u001b[0m unit\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_metadata[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtime_unit\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m in_time_unit \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mframe\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 1235\u001b[0m )\n\u001b[0;32m 1236\u001b[0m time_step \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel_metadata[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtime_step\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m in_time_unit \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m-> 1237\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43madd_custom_property\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtracking\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mDivisionTime\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[1;32mE:\\Code\\pycellin\\pycellin\\pycellin\\classes\\model.py:932\u001b[0m, in \u001b[0;36mModel.add_custom_property\u001b[1;34m(self, calculator)\u001b[0m\n\u001b[0;32m 912\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 913\u001b[0m \u001b[38;5;124;03mAdd a custom property to the model.\u001b[39;00m\n\u001b[0;32m 914\u001b[0m \n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 929\u001b[0m \u001b[38;5;124;03m have not been computed yet.\u001b[39;00m\n\u001b[0;32m 930\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 931\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m calculator\u001b[38;5;241m.\u001b[39mprop\u001b[38;5;241m.\u001b[39mlin_type \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCycleLineage\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata\u001b[38;5;241m.\u001b[39mcycle_data:\n\u001b[1;32m--> 932\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m 933\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCycle lineages have not been computed yet. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 934\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPlease compute the cycle lineages first with `model.add_cycle_data()`.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 935\u001b[0m )\n\u001b[0;32m 936\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprops_metadata\u001b[38;5;241m.\u001b[39m_add_prop(calculator\u001b[38;5;241m.\u001b[39mprop)\n\u001b[0;32m 937\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_updater\u001b[38;5;241m.\u001b[39mregister_calculator(calculator)\n", - "\u001b[1;31mValueError\u001b[0m: Cycle lineages have not been computed yet. Please compute the cycle lineages first with `model.add_cycle_data()`." + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m.\u001b[49m\u001b[43madd_division_time\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:1237\u001b[39m, in \u001b[36mModel.add_division_time\u001b[39m\u001b[34m(self, in_time_unit, custom_identifier)\u001b[39m\n\u001b[32m 1232\u001b[39m prop = tracking.create_division_time_property(\n\u001b[32m 1233\u001b[39m custom_identifier=custom_identifier,\n\u001b[32m 1234\u001b[39m unit=\u001b[38;5;28mself\u001b[39m.model_metadata[\u001b[33m\"\u001b[39m\u001b[33mtime_unit\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m in_time_unit \u001b[38;5;28;01melse\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33mframe\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 1235\u001b[39m )\n\u001b[32m 1236\u001b[39m time_step = \u001b[38;5;28mself\u001b[39m.model_metadata[\u001b[33m\"\u001b[39m\u001b[33mtime_step\u001b[39m\u001b[33m\"\u001b[39m] \u001b[38;5;28;01mif\u001b[39;00m in_time_unit \u001b[38;5;28;01melse\u001b[39;00m \u001b[32m1\u001b[39m\n\u001b[32m-> \u001b[39m\u001b[32m1237\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43madd_custom_property\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtracking\u001b[49m\u001b[43m.\u001b[49m\u001b[43mDivisionTime\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtime_step\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:932\u001b[39m, in \u001b[36mModel.add_custom_property\u001b[39m\u001b[34m(self, calculator)\u001b[39m\n\u001b[32m 912\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 913\u001b[39m \u001b[33;03mAdd a custom property to the model.\u001b[39;00m\n\u001b[32m 914\u001b[39m \n\u001b[32m (...)\u001b[39m\u001b[32m 929\u001b[39m \u001b[33;03m have not been computed yet.\u001b[39;00m\n\u001b[32m 930\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 931\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m calculator.prop.lin_type == \u001b[33m\"\u001b[39m\u001b[33mCycleLineage\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m.data.cycle_data:\n\u001b[32m--> \u001b[39m\u001b[32m932\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 933\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mCycle lineages have not been computed yet. \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 934\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mPlease compute the cycle lineages first with `model.add_cycle_data()`.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 935\u001b[39m )\n\u001b[32m 936\u001b[39m \u001b[38;5;28mself\u001b[39m.props_metadata._add_prop(calculator.prop)\n\u001b[32m 937\u001b[39m \u001b[38;5;28mself\u001b[39m._updater.register_calculator(calculator)\n", + "\u001b[31mValueError\u001b[39m: Cycle lineages have not been computed yet. Please compute the cycle lineages first with `model.add_cycle_data()`." ] } ], @@ -4583,7 +4583,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "E:\\Code\\pycellin\\pycellin\\pycellin\\classes\\model.py:936: UserWarning:\n", + "/media/lxenard/data/Code/pycellin/pycellin/pycellin/classes/model.py:936: UserWarning:\n", "\n", "A Property 'division_time' already exists with the same type. Not overwriting the old Property.\n", "\n" @@ -4654,7 +4654,7 @@ " 'level': Property(identifier='level', name='level', description='Level of the cell cycle in the lineage, i.e. number of cell cycles upstream of the current one', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='int', unit=None),\n", " 'division_time': Property(identifier='division_time', name='Division time', description='Time between two successive divisions', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='int', unit='frame'),\n", " 'cycle_completeness': Property(identifier='cycle_completeness', name='Cycle completeness', description='Completeness of the cell cycle', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='bool', unit=None),\n", - " 'branch_total_displacement': Property(identifier='branch_total_displacement', name='Branch total displacement', description='Displacement of the cell during the cell cycle', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='float', unit='µm')}" + " 'branch_total_displacement': Property(identifier='branch_total_displacement', name='Branch total displacement', description='Displacement of the cell during the cell cycle', provenance='pycellin', prop_type='node', lin_type='CycleLineage', dtype='float', unit='micrometer')}" ] }, "execution_count": 19, @@ -4755,7 +4755,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "WARNING: Circular skeleton on node 9350 of track 2! The object is probably roundish and the radius is a better metric in that case. Setting the length and width to NaN.\n" + "WARNING: One pixel skeleton on node 9350! The object is probably roundish and the radius is a better metric in that case. Setting the length and width to NaN.\n" ] } ], @@ -8287,10 +8287,10 @@ "evalue": "'absolute_age'", "output_type": "error", "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[27], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mlin0\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnodes\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m9013\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mabsolute_age\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\n", - "\u001b[1;31mKeyError\u001b[0m: 'absolute_age'" + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[27]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mlin0\u001b[49m\u001b[43m.\u001b[49m\u001b[43mnodes\u001b[49m\u001b[43m[\u001b[49m\u001b[32;43m9013\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mabsolute_age\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\n", + "\u001b[31mKeyError\u001b[39m: 'absolute_age'" ] } ], @@ -8315,7 +8315,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.18" + "version": "3.11.13" } }, "nbformat": 4, diff --git a/pycellin/__init__.py b/pycellin/__init__.py index f07fe57..bca9831 100644 --- a/pycellin/__init__.py +++ b/pycellin/__init__.py @@ -18,6 +18,8 @@ from .io.trackmate.exporter import export_TrackMate_XML from .io.trackpy.loader import load_trackpy_dataframe from .io.trackpy.exporter import export_trackpy_dataframe +from .io.geff.loader import load_GEFF +from .io.geff.exporter import export_GEFF from .graph.properties.utils import ( get_pycellin_cell_lineage_properties, @@ -44,6 +46,8 @@ "export_TrackMate_XML", "load_trackpy_dataframe", "export_trackpy_dataframe", + "load_GEFF", + "export_GEFF", "get_pycellin_cell_lineage_properties", "get_pycellin_cycle_lineage_properties", ] diff --git a/pycellin/classes/__init__.py b/pycellin/classes/__init__.py index b437478..af6fa92 100644 --- a/pycellin/classes/__init__.py +++ b/pycellin/classes/__init__.py @@ -13,3 +13,18 @@ EdgeGlobalPropCalculator, LineageGlobalPropCalculator, ) + +__all__ = [ + "Data", + "CellLineage", + "CycleLineage", + "Property", + "PropsMetadata", + "Model", + "NodeLocalPropCalculator", + "EdgeLocalPropCalculator", + "LineageLocalPropCalculator", + "NodeGlobalPropCalculator", + "EdgeGlobalPropCalculator", + "LineageGlobalPropCalculator", +] diff --git a/pycellin/classes/model.py b/pycellin/classes/model.py index 2ba6499..8272c3b 100644 --- a/pycellin/classes/model.py +++ b/pycellin/classes/model.py @@ -1497,6 +1497,17 @@ def add_cycle_data(self) -> None: self.data._add_cycle_lineages() self.props_metadata._add_cycle_lineage_props() + def has_cycle_data(self) -> bool: + """ + Check if the model has cycle lineages. + + Returns + ------- + bool + True if the model has cycle lineages, False otherwise. + """ + return bool(self.data.cycle_data) + def _categorize_props(self, props: list[str] | None) -> tuple[list[str], list[str], list[str]]: """ Categorize properties by type (node, edge, lineage). diff --git a/pycellin/graph/__init__.py b/pycellin/graph/__init__.py index e69de29..d687359 100644 --- a/pycellin/graph/__init__.py +++ b/pycellin/graph/__init__.py @@ -0,0 +1,9 @@ +from .properties.utils import ( + get_pycellin_cell_lineage_properties, + get_pycellin_cycle_lineage_properties, +) + +__all__ = [ + "get_pycellin_cell_lineage_properties", + "get_pycellin_cycle_lineage_properties", +] diff --git a/pycellin/graph/properties/core.py b/pycellin/graph/properties/core.py index 8464dcf..1afddb1 100644 --- a/pycellin/graph/properties/core.py +++ b/pycellin/graph/properties/core.py @@ -43,7 +43,9 @@ def create_lineage_id_property(provenance: str = "pycellin") -> Property: ) -def create_cell_coord_property(unit: str, axis: str, provenance: str = "pycellin") -> Property: +def create_cell_coord_property( + unit: str | None, axis: str, provenance: str = "pycellin" +) -> Property: return Property( identifier=f"cell_{axis}", name=f"cell {axis}", diff --git a/pycellin/io/__init__.py b/pycellin/io/__init__.py index 4aa1a30..aaf4a9d 100644 --- a/pycellin/io/__init__.py +++ b/pycellin/io/__init__.py @@ -4,3 +4,16 @@ from .trackmate.exporter import export_TrackMate_XML from .trackpy.loader import load_trackpy_dataframe from .trackpy.exporter import export_trackpy_dataframe +from .geff.loader import load_GEFF +from .geff.exporter import export_GEFF + +__all__ = [ + "load_CTC_file", + "export_CTC_file", + "load_TrackMate_XML", + "export_TrackMate_XML", + "load_trackpy_dataframe", + "export_trackpy_dataframe", + "load_GEFF", + "export_GEFF", +] diff --git a/pycellin/io/cell_tracking_challenge/__init__.py b/pycellin/io/cell_tracking_challenge/__init__.py index 6f4cb7a..e3ddb10 100644 --- a/pycellin/io/cell_tracking_challenge/__init__.py +++ b/pycellin/io/cell_tracking_challenge/__init__.py @@ -1,2 +1,4 @@ from .loader import load_CTC_file from .exporter import export_CTC_file + +__all__ = ["load_CTC_file", "export_CTC_file"] diff --git a/pycellin/io/geff/__init__.py b/pycellin/io/geff/__init__.py new file mode 100644 index 0000000..89fb0de --- /dev/null +++ b/pycellin/io/geff/__init__.py @@ -0,0 +1,4 @@ +from .loader import load_GEFF +from .exporter import export_GEFF + +__all__ = ["load_GEFF", "export_GEFF"] diff --git a/pycellin/io/geff/exporter.py b/pycellin/io/geff/exporter.py new file mode 100644 index 0000000..00136fd --- /dev/null +++ b/pycellin/io/geff/exporter.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +exporter.py + +This module is part of the pycellin package. +It provides functionality to export a pycellin model to the GEFF format. + +References: +- geff GitHub: https://github.com/live-image-tracking-tools/geff +- geff Documentation: https://live-image-tracking-tools.github.io/geff/latest/ +""" + +import copy + +import networkx as nx +from geff import write_nx +from geff.metadata_schema import Axis, DisplayHint, GeffMetadata, PropMetadata + +from pycellin.classes import CellLineage, Model, Property + + +def _find_node_overlaps(lineages: list[CellLineage]) -> dict[int, list[int]]: + """ + Find overlapping node IDs across lineages. + + Parameters + ---------- + lineages : list[CellLineage] + List of lineage graphs. + + Returns + ------- + dict[int, list[int]] + A dictionary mapping node IDs to the list of lineage indices they belong to. + """ + node_to_lineages: dict[int, int] = {} + overlaps: dict[int, list[int]] = {} + + for lin_index, lin in enumerate(lineages): + for nid in lin.nodes: + if nid in node_to_lineages: # Overlap found + if nid not in overlaps: + overlaps[nid] = [node_to_lineages[nid], lin_index] + else: + overlaps[nid].append(lin_index) + else: + node_to_lineages[nid] = lin_index + + return overlaps + + +def _get_next_available_id(lineages: list[CellLineage]) -> int: + """ + Get the next available node ID across all lineages. + + Parameters + ---------- + lineages : list[CellLineage] + List of lineage graphs to check. + + Returns + ------- + int + The next available node ID. + """ + if not lineages: + return 0 + + max_node_id = -1 + for lineage in lineages: + if lineage.nodes: + lineage_max = max(lineage.nodes) + if lineage_max > max_node_id: + max_node_id = lineage_max + + return max_node_id + 1 + + +def _relabel_nodes( + lineages: list[CellLineage], + overlaps: dict[int, list[int]], +) -> None: + """ + Relabel nodes in each lineage to ensure unique IDs across all lineages. + + Parameters + ---------- + lineages : list[CellLineage] + List of lineage graphs to relabel in place. + overlaps : dict[int, list[int]] + Dictionary mapping overlapping node IDs to the list of lineage indices they belong to. + """ + next_available_id = _get_next_available_id(lineages) + for nid, lids in overlaps.items(): + for lid in lids[1:]: + mapping = {nid: next_available_id} + nx.relabel_nodes(lineages[lid], mapping, copy=False) + next_available_id += 1 + + +def _solve_node_overlaps(lineages: list[CellLineage]) -> None: + """ + Detect and resolve overlapping node IDs across lineages by reassigning unique IDs. + + Parameters + ---------- + lineages : list[CellLineage] + List of lineage graphs to check and modify in place. + """ + overlaps = _find_node_overlaps(lineages) + if overlaps: + print("Overlapping node IDs found:") + for nid, lids in overlaps.items(): + print(f" Node ID {nid} in lineages {lids}") + _relabel_nodes(lineages, overlaps) + + # Verify no more overlaps + # TODO: remove this, it's only for debug, or put in verbose + overlaps = _find_node_overlaps(lineages) + if overlaps: + print("Overlapping node IDs found after relabeling:") + for nid, lids in overlaps.items(): + print(f" Node ID {nid} in lineages {lids}") + else: + print("No overlapping node IDs found after relabeling.") + + else: + print("No overlapping node IDs found.") + + +def _build_axes( + has_x: bool, + has_y: bool, + has_z: bool, + has_t: bool, + space_unit: str | None, + time_unit: str | None, +) -> list[Axis]: + """ + Build a list of Axis objects for GEFF metadata. + + Parameters + ---------- + has_x : bool + Whether the x-axis is present. + has_y : bool + Whether the y-axis is present. + has_z : bool + Whether the z-axis is present. + has_t : bool + Whether the time axis is present. + space_unit : str | None + Unit for spatial axes (e.g., "micrometer"). + time_unit : str | None + Unit for time axis (e.g., "second"). + + Returns + ------- + list[Axis] + List of Axis objects representing spatial and temporal dimensions. + """ + axes = [] + if has_x: + axes.append(Axis(name="cell_x", type="space", unit=space_unit)) + if has_y: + axes.append(Axis(name="cell_y", type="space", unit=space_unit)) + if has_z: + axes.append(Axis(name="cell_z", type="space", unit=space_unit)) + if has_t: + axes.append(Axis(name="frame", type="time", unit=time_unit)) + return axes + + +def _build_display_hints( + has_x: bool, + has_y: bool, + has_z: bool, + has_t: bool, +) -> DisplayHint | None: + """ + Build display hints for GEFF metadata. + + Parameters + ---------- + has_x : bool + Whether the x-axis is present. + has_y : bool + Whether the y-axis is present. + has_z : bool + Whether the z-axis is present. + has_t : bool + Whether the time axis is present. + + Returns + ------- + DisplayHint | None + DisplayHint object if x and y axes are present, otherwise None. + """ + if has_x and has_y: + display_hints = DisplayHint(display_horizontal="cell_x", display_vertical="cell_y") + if has_z: + display_hints.display_depth = "cell_z" + if has_t: + display_hints.display_time = "frame" + else: + display_hints = None + return display_hints + + +def _build_props_metadata( + properties: dict[str, Property], +) -> tuple[dict[str, PropMetadata], dict[str, PropMetadata]]: + """ + Build property metadata for GEFF from a pycellin model. + + Parameters + ---------- + properties : dict[str, Property] + Dictionary of property identifiers to Property objects. + + Returns + ------- + tuple[dict[str, PropMetadata], dict[str, PropMetadata]] + A tuple containing two dictionaries: + - Node properties metadata + - Edge properties metadata + + Raises + ------ + ValueError + If an unknown property type is encountered. + """ + node_props_md: dict[str, PropMetadata] = {} + edge_props_md: dict[str, PropMetadata] = {} + + for prop_id, prop in properties.items(): + prop_md = PropMetadata( + identifier=prop_id, + dtype=prop.dtype, + unit=prop.unit, + name=prop.name, + description=prop.description, + ) + match prop.prop_type: + case "node": + node_props_md[prop_id] = prop_md + case "edge": + edge_props_md[prop_id] = prop_md + case "lineage": + pass # not supported in GEFF 0.5.0 + case _: + raise ValueError(f"Unknown property type: {prop.prop_type}") + + return node_props_md, edge_props_md + + +def _build_geff_metadata(model: Model) -> GeffMetadata: + """ + Build GEFF metadata from a pycellin model. + + Parameters + ---------- + model : Model + The pycellin model to extract metadata from. + + Returns + ------- + GeffMetadata + The GEFF metadata object. + """ + # Generic metadata + has_x = model.has_property("cell_x") + has_y = model.has_property("cell_y") + has_z = model.has_property("cell_z") + has_t = model.has_property("frame") + axes = _build_axes( + has_x=has_x, + has_y=has_y, + has_z=has_z, + has_t=has_t, + space_unit=model.get_space_unit(), + time_unit=model.get_time_unit(), + ) + display_hints = _build_display_hints( + has_x=has_x, + has_y=has_y, + has_z=has_z, + has_t=has_t, + ) + + # Property metadata + props = model.get_cell_lineage_properties() + node_props_md, edge_props_md = _build_props_metadata(props) + + # Define identifiers of lineage and cell cycle + track_node_props = {"lineage": "lineage_ID"} + if model.has_cycle_data(): + track_node_props["tracklet"] = "cycle_ID" + + return GeffMetadata( + directed=True, + axes=axes, + display_hints=display_hints, + track_node_props=track_node_props, + node_props_metadata=node_props_md, + edge_props_metadata=edge_props_md, + ) + + +def export_GEFF(model: Model, geff_out: str) -> None: + """ + Export a pycellin model to GEFF format. + + Parameters + ---------- + model : Model + The pycellin model to export. + geff_out : str + Path to the output GEFF file. + + Raises + ------ + ValueError + If the model contains no lineage data. + OSError + If there are file I/O issues with the output path. + RuntimeError + If the GEFF export process fails. + """ + # Validate that model has data to export + if not model.data.cell_data: + raise ValueError("Model contains no lineage data to export") + + try: + # We don't want to modify the original model. + model_copy = copy.deepcopy(model) + lineages = list(model_copy.data.cell_data.values()) + + for graph in lineages: + print(len(graph.nodes), len(graph.edges)) + + # TODO: remove when GEFF can handle variable length properties + if model_copy.has_property("ROI_coords"): + model_copy.remove_property("ROI_coords") + + # For GEFF compatibility, we need to put all the lineages in the same graph, + # but some nodes can have the same identifier across different lineages. + _solve_node_overlaps(lineages) + geff_graph = nx.compose_all(lineages) + print(len(geff_graph)) + + metadata = _build_geff_metadata(model_copy) + print(metadata) + + write_nx( + geff_graph, + geff_out, + metadata=metadata, + ) + + except Exception as e: + raise RuntimeError(f"Failed to export GEFF file to '{geff_out}': {e}") from e + + +if __name__ == "__main__": + xml_in = "sample_data/Ecoli_growth_on_agar_pad.xml" + # xml_in = "sample_data/Celegans-5pc-17timepoints.xml" + # xml_in = "sample_data/FakeTracks.xml" + # ctc_in = "sample_data/FakeTracks_TMtoCTC.txt" + # ctc_in = "sample_data/Ecoli_growth_on_agar_pad_TMtoCTC.txt" + # geff_out = "E:/Janelia_Cell_Trackathon/test_pycellin_geff/test.geff" + geff_out = ( + "/media/lxenard/data/Janelia_Cell_Trackathon/test_pycellin_geff/pycellin_to_geff.geff" + ) + + # Remove existing folder + import os + import shutil + + if os.path.exists(geff_out): + shutil.rmtree(geff_out) + + # Load data + from pycellin.io.cell_tracking_challenge.loader import load_CTC_file + from pycellin.io.trackmate.loader import load_TrackMate_XML + + model = load_TrackMate_XML(xml_in) + # model = load_CTC_file(ctc_in) + # model.add_cycle_data() + print(model) + print(model.get_cell_lineage_properties().keys()) + print(model.data.cell_data.keys()) + # To test overlapping node IDs + prop_values = {"cell_x": 10, "cell_y": 15, "cell_z": 20} + model.add_cell(lid=0, cid=9510, frame=0, prop_values=prop_values) + model.add_cell(lid=1, cid=9510, frame=0, prop_values=prop_values) + model.add_cell(lid=1, cid=9509, frame=0, prop_values=prop_values) + model.add_cell(lid=2, cid=9498, frame=0, prop_values=prop_values) + + export_GEFF(model, geff_out) diff --git a/pycellin/io/geff/loader.py b/pycellin/io/geff/loader.py new file mode 100644 index 0000000..8ad5545 --- /dev/null +++ b/pycellin/io/geff/loader.py @@ -0,0 +1,913 @@ +#!/usr/bin/env python3 + +""" +loader.py + +This module is part of the pycellin package. +It provides functionality to load a GEFF file into a pycellin model. + +References: +- geff GitHub: https://github.com/live-image-tracking-tools/geff +- geff Documentation: https://live-image-tracking-tools.github.io/geff/latest/ +""" + +import importlib.metadata +import warnings +from datetime import datetime +from pathlib import Path +from typing import Any, Literal + +import geff +import networkx as nx +from geff.metadata_schema import GeffMetadata + +from pycellin.classes import CellLineage, Data, Model, Property, PropsMetadata +from pycellin.custom_types import PropertyType +from pycellin.graph.properties.core import ( + create_cell_coord_property, + create_cell_id_property, + create_frame_property, + create_lineage_id_property, +) +from pycellin.io.utils import ( + _split_graph_into_lineages, + _update_lineages_IDs_key, + _update_node_prop_key, + check_fusions, +) + + +def _recursive_dict_search(data: dict[str, Any], target_key: str) -> dict[str, Any] | None: + """ + Recursively search for a target key in a nested dictionary structure. + + Parameters + ---------- + data : dict + The dictionary to search through. + target_key : str + The key to search for. + + Returns + ------- + dict[str, Any] | None + The dict associated with the target key if found, None otherwise. + """ + if not isinstance(data, dict): + return None + if target_key in data: # search in the current level + return data[target_key] + for value in data.values(): # recursive search in nested dictionaries + if isinstance(value, dict): + result = _recursive_dict_search(value, target_key) + if result is not None: + return result + return None + + +def _has_node_prop_key(graph: nx.Graph, key: str) -> bool: + """ + Check if all nodes in the graph have a specific property key. + + Parameters + ---------- + graph : nx.Graph + The graph to check. + key : str + The property key to look for. + + Returns + ------- + bool + True if all nodes have the property key, False otherwise. + """ + return all(key in graph.nodes[node] for node in graph.nodes) + + +def _set_lineage_id(lineages: list[CellLineage]) -> None: + """ + Set lineage IDs for the provided lineages. + + Parameters + ---------- + lineages : list[CellLineage] + List of CellLineage objects to set lineage IDs for. + + Warns + ----- + UserWarning + If no lineage identifier is found, a warning is issued and lineage IDs + are autogenerated based on weakly connected components. + """ + for lin_id, lin in enumerate(lineages): + lin.graph["lineage_ID"] = lin_id + for node in lin.nodes: + lin.nodes[node]["lineage_ID"] = lin_id + warnings.warn( + "No lineage identifier found. Lineage IDs have been autogenerated.", + stacklevel=3, + ) + + +def _identify_lin_id_key( + lineage_id_key: str | None, + geff_track_node_props: dict[Literal["lineage", "tracklet"], str] | None, + geff_graph: nx.Graph, +) -> str | None: + """ + Identify the lineage ID key from user input or geff metadata. + + If the lineage_id_key is provided by the user, the function will check + that it exists in the graph. If not provided, the function will try to infer it + from the geff track_node_props. If that fails, the function will create a new + 'lineage_ID' property. + + Parameters + ---------- + lineage_id_key : str | None + The key provided by the user to identify lineages. + geff_track_node_props : dict[Literal["lineage", "tracklet"], str] | None + The track_node_props from geff metadata. + geff_graph : nx.Graph + The geff graph. + + Returns + ------- + str | None + The identified lineage ID key, or None if not found or inferred. + + Warns + ----- + UserWarning + If the provided lineage_id_key is not found in the graph. + """ + lin_id_key: str | None = None + if lineage_id_key is not None: + if _has_node_prop_key(geff_graph, lineage_id_key): + lin_id_key = lineage_id_key + else: + warnings.warn( + f"The provided lineage_id_key '{lineage_id_key}' is not found in the graph.", + stacklevel=3, + ) + lineage_id_key = None + + if lineage_id_key is None: + if geff_track_node_props is not None: + lin_id_key = geff_track_node_props.get("lineage") + else: + if _has_node_prop_key(geff_graph, "lineage_ID"): + lin_id_key = "lineage_ID" + else: + lin_id_key = None + + return lin_id_key + + +def _identify_time_key( + time_key: str | None, + geff_display_hints: geff.metadata_schema.DisplayHint | None, + geff_graph: nx.Graph, +) -> str: + """ + Identify the time key from user input or geff metadata. + + If the time_key is provided by the user, the function will check + that it exists in the graph. If not provided, it will try to infer it + from the geff display hints or common conventions. + + Parameters + ---------- + time_key : str | None + The key provided by the user to identify time points. + geff_display_hints : geff.metadata_schema.DisplayHint | None + The display_hints from geff metadata. + geff_graph : nx.Graph + The geff graph. + + Returns + ------- + str + The identified time key. + + Raises + ------ + ValueError + If the provided time_key is not found in the graph. + ValueError + If no time key is found or inferred. + NotImplementedError + If the identified time key cannot be matched to 'frame'. + Only 'frame' is currently supported. + """ + # If I end up checking for a lot of keys, there are probably better ways to do it. + # Use pattern matching? + if time_key is not None: + if not _has_node_prop_key(geff_graph, time_key): + raise ValueError(f"The provided time_key '{time_key}' is not found in the graph.") + else: + if geff_display_hints is not None: + time_key = getattr(geff_display_hints, "display_time", None) + elif _has_node_prop_key(geff_graph, "frame"): + time_key = "frame" + # elif _has_node_prop_key(geff_graph, "t"): + # time_key = "t" + # elif _has_node_prop_key(geff_graph, "time"): + # time_key = "time" + elif _has_node_prop_key(geff_graph, "FRAME"): + time_key = "FRAME" + elif _has_node_prop_key(geff_graph, "Frame"): + time_key = "Frame" + + if time_key is None: + raise ValueError( + "No time key found. Please provide a valid time_key argument or ensure " + "that the geff file contains a time display hint." + ) + + # TODO: to update when pycellin can support any time key + if time_key != "frame" and time_key.lower() == "frame": + time_key = "frame" + if time_key != "frame": + raise NotImplementedError( + f"Time key '{time_key}' cannot be matched to 'frame'. " + "Pycellin currently only supports frame-like time keys." + ) + + return time_key + + +def _extract_space_key_from_display_hints( + hint_field: str, + geff_display_hints: geff.metadata_schema.DisplayHint | None, + geff_graph: nx.Graph, +) -> str | None: + """ + Extract a space key from geff display hints. + + Parameters + ---------- + hint_field : str + The field in the display hints to extract ('display_horizontal', 'display_vertical', + or 'display_depth'). + geff_display_hints : geff.metadata_schema.DisplayHint | None + The display_hints from geff metadata. + geff_graph : nx.Graph + The geff graph. + + Returns + ------- + str | None + The extracted space key, or None if not found. + + Warns + ----- + UserWarning + If the inferred space key is not found in the graph. In this case, + the property is ignored. + UserWarning + If no display hint is found and no key is provided. In this case, + the property is ignored. + """ + mapping = { + "display_horizontal": "x", + "display_vertical": "y", + "display_depth": "z", + } + + if geff_display_hints is not None: + space_key = getattr(geff_display_hints, hint_field, None) + if space_key is not None and not _has_node_prop_key(geff_graph, space_key): + warnings.warn( + f"The inferred space key '{space_key}' is not found in the graph. " + "Ignoring this property.", + stacklevel=4, + ) + else: + space_key = None + warnings.warn( + f"No cell_{mapping[hint_field]}_key provided and no display hint found. " + "Ignoring this property.", + stacklevel=4, + ) + + return space_key + + +def _identify_space_keys( + cell_x_key: str | None, + cell_y_key: str | None, + cell_z_key: str | None, + geff_display_hints: geff.metadata_schema.DisplayHint | None, + geff_graph: nx.Graph, +) -> tuple[str | None, str | None, str | None]: + """ + Identify the space keys (x, y, z) from user input or geff metadata. + + If the space keys are provided by the user, the function will check + that they exist in the graph. If not provided, it will try to infer them + from the geff display hints. + + Parameters + ---------- + cell_x_key : str | None + The key provided by the user to identify the x-coordinate. + cell_y_key : str | None + The key provided by the user to identify the y-coordinate. + cell_z_key : str | None + The key provided by the user to identify the z-coordinate. + geff_display_hints : geff.metadata_schema.DisplayHint | None + The display_hints from geff metadata. + geff_graph : nx.Graph + The geff graph. + + Returns + ------- + tuple[str | None, str | None, str | None] + The identified space keys (cell_x_key, cell_y_key, cell_z_key). + + Warns + ----- + UserWarning + If any of the provided space keys are not found in the graph. + """ + space_keys = [cell_x_key, cell_y_key, cell_z_key] + + # Validate provided keys and warn if they don't exist in the graph + for dim, key in zip(["x", "y", "z"], space_keys): + if key is not None: + if not _has_node_prop_key(geff_graph, key): + warnings.warn( + f"The provided cell_{dim}_key '{key}' is not found in the graph. " + "Ignoring this property.", + stacklevel=3, + ) + + # Update keys if they are None by extracting from display hints + hint_fields = ["display_horizontal", "display_vertical", "display_depth"] + for i, (hint_field, key) in enumerate(zip(hint_fields, space_keys)): + if key is None: + space_keys[i] = _extract_space_key_from_display_hints( + hint_field, geff_display_hints, geff_graph + ) + + return space_keys[0], space_keys[1], space_keys[2] + + +def _extract_props_metadata( + md: dict[str, geff.metadata_schema.PropMetadata], + props_dict: dict[str, Property], + prop_type: PropertyType, +) -> None: + """ + Extract properties metadata from a given dictionary and update the props_dict. + + Parameters + ---------- + md : dict[str, geff.metadata_schema.PropMetadata] + The dictionary containing properties metadata. + props_dict : dict[str, Property] + The dictionary to update with extracted properties metadata. + prop_type : PropertyType + The type of property being extracted ('node' or 'edge'). + + Raises + ------ + ValueError + If an unsupported property type is provided. + KeyError + If a property identifier already exists in props_dict for the same property + type. + """ + for key, prop in md.items(): + if key not in props_dict: + props_dict[key] = Property( + identifier=key, + name=prop.name or key, + description=prop.description or prop.name or key, + provenance="geff", + prop_type=prop_type, + lin_type="CellLineage", + dtype=prop.dtype, + unit=prop.unit or None, + ) + else: + if props_dict[key].prop_type != prop_type: + # The key must be unique but it already exists for nodes or edges, + # so it needs to be renamed. + if prop_type == "node": + current_prefix = "cell" + other_prefix = "link" + elif prop_type == "edge": + current_prefix = "link" + other_prefix = "cell" + else: + raise ValueError( + f"Unsupported property type: {prop_type}. Expected 'node' or 'edge'." + ) + # Rename the new property to be added. + new_key = f"{current_prefix}_{key}" + props_dict[new_key] = Property( + identifier=new_key, + name=prop.name or key, + description=prop.description or prop.name or key, + provenance="geff", + prop_type=prop_type, + lin_type="CellLineage", + dtype=prop.dtype, + unit=prop.unit or None, + ) + # Rename the other property as well for clarity. + other_key = f"{other_prefix}_{key}" + other_prop = props_dict.pop(key) + other_prop.identifier = other_key + props_dict[other_key] = other_prop + else: + # GEFF ensure uniqueness of property keys for nodes and edges separately, + # so this should never happen. + raise KeyError( + f"Property '{key}' already exists in props_dict for {prop_type}s. " + "Please ensure unique property identifiers." + ) + + +def _extract_lin_props_metadata( + md: dict[str, Any], + props_dict: dict[str, Property], +) -> None: + """ + Extract lineage properties metadata from a given dictionary and update the props_dict. + + Parameters + ---------- + md : dict[str, Any] + The dictionary containing lineage properties metadata. + props_dict : dict[str, Property] + The dictionary to update with extracted lineage properties metadata. + + Raises + ------ + KeyError + If a property identifier already exists in props_dict for lineages. + """ + for key, prop in md.items(): + if key not in props_dict: + props_dict[key] = Property( + identifier=key, + name=prop.get("name") or key, + description=prop.get("description") or prop.get("name") or key, + provenance="geff", + prop_type="lineage", + lin_type="CellLineage", + dtype=prop.get("dtype"), + unit=prop.get("unit") or None, + ) + else: + if props_dict[key].prop_type != "lineage": + # The key must be unique but it already exists for nodes or edges, + # so it needs to be renamed. + new_key = f"lin_{key}" + props_dict[new_key] = Property( + identifier=new_key, + name=prop.get("name") or key, + description=prop.get("description") or prop.get("name") or key, + provenance="geff", + prop_type="lineage", + lin_type="CellLineage", + dtype=prop.get("dtype"), + unit=prop.get("unit") or None, + ) + else: + raise KeyError( + f"Property '{key}' already exists in props_dict for lineages. " + "Please ensure unique property identifiers." + ) + + +def _build_props_metadata(geff_md: geff.metadata_schema.GeffMetadata) -> dict[str, Property]: + """ + Read and extract properties metadata from geff metadata. + + Parameters + ---------- + geff_md : geff.metadata_schema.GeffMetadata + The geff metadata object containing properties metadata. + + Returns + ------- + dict[str, Property] + Dictionary mapping property identifiers to Property objects. + """ + props_dict: dict[str, Property] = {} + if geff_md.node_props_metadata is not None: + _extract_props_metadata(geff_md.node_props_metadata, props_dict, "node") + if geff_md.edge_props_metadata is not None: + _extract_props_metadata(geff_md.edge_props_metadata, props_dict, "edge") + + # TODO: for now lineage properties are not associated to a specific tag but stored + # somewhere in the "extra" field. We need to check recursively if there is a dict + # key called "lineage_props_metadata" in the "extra" field. + if geff_md.extra is not None: + # Recursive search for the "lineage_props_metadata" key through the "extra" + # field dict of dicts of dicts... + lin_props_metadata = _recursive_dict_search(geff_md.extra, "lineage_props_metadata") + if lin_props_metadata is not None: + _extract_lin_props_metadata(lin_props_metadata, props_dict) + + return props_dict + + +def _extract_units_from_axes(geff_md: GeffMetadata) -> dict[str, Any]: + """ + Extract and validate space and time units from geff metadata axes. + + Parameters + ---------- + geff_md : geff.metadata_schema.GeffMetadata + The geff metadata object containing axes information. + + Returns + ------- + dict[str, Any] + Dictionary containing space_unit and time_unit keys. + + Raises + ------ + ValueError + If multiple space units or time units are found in axes. + """ + units_metadata = {} + + if geff_md.axes is not None: + # Check unicity of space time unit + space_units = { + axis.unit for axis in geff_md.axes if axis.type == "space" and axis.unit is not None + } + if len(space_units) > 1: + raise ValueError( + f"Multiple space units found in axes: {space_units}. " + f"Pycellin assumes a single space unit." + ) + units_metadata["space_unit"] = space_units.pop() if space_units else None + + # Check unicity of time unit + time_units = { + axis.unit for axis in geff_md.axes if axis.type == "time" and axis.unit is not None + } + if len(time_units) > 1: + raise ValueError( + f"Multiple time units found in axes: {time_units}. " + f"Pycellin assumes a single time unit." + ) + units_metadata["time_unit"] = time_units.pop() if time_units else None + + else: + units_metadata["space_unit"] = None + units_metadata["time_unit"] = None + + return units_metadata + + +def _extract_generic_metadata( + geff_file: Path | str, geff_md: geff.metadata_schema.GeffMetadata +) -> dict[str, Any]: + """ + Extract generic metadata for the model based on the geff file and its metadata. + + Parameters + ---------- + geff_file : Path | str + Path to the geff file. + geff_md : geff.metadata_schema.GeffMetadata + The geff metadata object. + + Returns + ------- + dict[str, Any] + Dictionary containing generic metadata. + + Raises + ------ + importlib.metadata.PackageNotFoundError + If the pycellin package is not found when trying to get its version. + """ + metadata: dict[str, Any] = {} + metadata["name"] = Path(geff_file).stem + metadata["file_location"] = geff_file + metadata["provenance"] = "geff" + metadata["date"] = str(datetime.now()) + try: + version = importlib.metadata.version("pycellin") + except importlib.metadata.PackageNotFoundError: + version = "unknown" + metadata["pycellin_version"] = version + metadata["geff_version"] = geff_md.geff_version + if geff_md.extra is not None: + metadata["geff_extra"] = geff_md.extra + + return metadata + + +def _build_generic_metadata(geff_file: Path | str, geff_md: GeffMetadata) -> dict[str, Any]: + """ + Build and return a dictionary containing generic pycellin metadata. + + Parameters + ---------- + geff_file : Path | str + Path to the geff file. + geff_md : geff.metadata_schema.GeffMetadata + The geff metadata object to read from. + + Returns + ------- + dict[str, Any] + Dictionary containing generic pycellin metadata. + """ + metadata = _extract_generic_metadata(geff_file, geff_md) + units_metadata = _extract_units_from_axes(geff_md) + metadata.update(units_metadata) + + return metadata + + +def _normalize_properties_data( + lineages: list[CellLineage], + lin_id_key: str, + cell_x_key: str | None, + cell_y_key: str | None, + cell_z_key: str | None, + time_key: str, + cell_id_key: str | None, +) -> None: + """ + Normalize properties data in lineages to match pycellin conventions. + + This function updates the property keys in lineage node data to use + standardized pycellin naming conventions (e.g., 'cell_x', 'cell_y', 'frame'). + + Parameters + ---------- + lineages : list[CellLineage] + List of CellLineage objects to normalize. + lin_id_key : str + The current lineage ID key name. + cell_x_key : str | None + The current x-coordinate key name, if any. + cell_y_key : str | None + The current y-coordinate key name, if any. + cell_z_key : str | None + The current z-coordinate key name, if any. + time_key : str + The current time key name. + cell_id_key : str | None + The current cell ID key name, if any. + """ + if lin_id_key != "lineage_ID": + _update_lineages_IDs_key(lineages, lin_id_key) + for lin in lineages: + if cell_x_key is not None and cell_x_key != "cell_x": + _update_node_prop_key(lin, old_key=cell_x_key, new_key="cell_x") + if cell_y_key is not None and cell_y_key != "cell_y": + _update_node_prop_key(lin, old_key=cell_y_key, new_key="cell_y") + if cell_z_key is not None and cell_z_key != "cell_z": + _update_node_prop_key(lin, old_key=cell_z_key, new_key="cell_z") + if time_key != "frame": + _update_node_prop_key(lin, old_key=time_key, new_key="frame") + if cell_id_key is None: + for node in lin.nodes: + lin.nodes[node]["cell_ID"] = node + elif cell_id_key != "cell_ID": + _update_node_prop_key(lin, old_key=cell_id_key, new_key="cell_ID") + + +def _normalize_properties_metadata( + props_md: dict[str, Property], + cell_x_key: str | None, + cell_y_key: str | None, + cell_z_key: str | None, + space_unit: str | None, +) -> None: + """ + Normalize properties metadata to match pycellin conventions. + + This function ensures that standard pycellin properties exist in the metadata + and renames them to follow pycellin conventions. + + Parameters + ---------- + props_md : dict[str, Property] + The properties metadata dictionary to normalize. + cell_x_key : str | None + The current x-coordinate key name, if any. + cell_y_key : str | None + The current y-coordinate key name, if any. + cell_z_key : str | None + The current z-coordinate key name, if any. + space_unit : str | None + The space unit to use for coordinate properties. + """ + # Ensure standard pycellin properties exist + if "cell_ID" not in props_md: + props_md["cell_ID"] = create_cell_id_property(provenance="geff") + if "lineage_ID" not in props_md: + props_md["lineage_ID"] = create_lineage_id_property(provenance="geff") + if "frame" not in props_md: + props_md["frame"] = create_frame_property(provenance="geff") + + # Create or normalize coordinate property keys + for axis, geff_key in [ + ("x", cell_x_key), + ("y", cell_y_key), + ("z", cell_z_key), + ]: + pycellin_key = f"cell_{axis}" + if geff_key is not None: + if geff_key in props_md and geff_key != pycellin_key: + props_md[pycellin_key] = props_md.pop(geff_key) + props_md[pycellin_key].identifier = pycellin_key + else: + props_md[pycellin_key] = create_cell_coord_property( + unit=space_unit, axis=axis, provenance="geff" + ) + + +def load_GEFF( + geff_file: Path | str, + lineage_id_key: str | None = None, + cell_id_key: str | None = None, + cell_x_key: str | None = None, + cell_y_key: str | None = None, + cell_z_key: str | None = None, + time_key: str | None = None, + validate_geff: bool = True, +) -> Model: + """ + Load a geff file and return a pycellin model containing the data. + + Parameters + ---------- + geff_file : Path | str + Path to the geff file to load. + lineage_id_key: str | None, optional + The key used to identify lineages in the geff file. If None, the function + will try to infer it from the geff metadata or autogenerate lineage IDs + based on weakly connected components. + cell_id_key : str | None, optional + The key used to identify cells in the geff file. If None, the default + key 'cell_ID' will be created and populated based on the node IDs. + cell_x_key : str | None, optional + The key used to identify the x-coordinate of cells in the geff file. + cell_y_key : str | None, optional + The key used to identify the y-coordinate of cells in the geff file. + cell_z_key : str | None, optional + The key used to identify the z-coordinate of cells in the geff file. + time_key : str | None, optional + The key used to identify the time point of cells in the geff file. + If None, the function will try to infer it from the geff metadata. + validate_geff : bool, optional + Whether to validate the GEFF file against its schema, i.e. is the GEFF + file well-formed and compliant with the GEFF specification. Default is True. + + Returns + ------- + Model + A pycellin model containing the data from the geff file. + """ + + # Read the geff file + geff_graph, geff_md = geff.read_nx(geff_file, validate=validate_geff) + if not geff_md.directed: + raise ValueError( + "The geff graph is undirected: pycellin does not support undirected graphs." + ) + for node in geff_graph.nodes: + print(geff_graph.nodes[node]) + break + + # Extract and dispatch metadata + generic_md = _build_generic_metadata(geff_file, geff_md) + props_md = _build_props_metadata(geff_md) + + # Identify specific props keys + lin_id_key = _identify_lin_id_key(lineage_id_key, geff_md.track_node_props, geff_graph) + # print("lin_id_key:", lin_id_key) + time_key = _identify_time_key(time_key, geff_md.display_hints, geff_graph) + # print("time_key:", time_key) + cell_x_key, cell_y_key, cell_z_key = _identify_space_keys( + cell_x_key, cell_y_key, cell_z_key, geff_md.display_hints, geff_graph + ) + # print("cell_x_key:", cell_x_key) + # print("cell_y_key:", cell_y_key) + # print("cell_z_key:", cell_z_key) + + # Split the graph into lineages + lineages = _split_graph_into_lineages(geff_graph, lineage_ID_key=lin_id_key) + # print(f"Number of lineages: {len(lineages)}") + if lin_id_key is None: + _set_lineage_id(lineages) + lin_id_key = "lineage_ID" + + # Rename properties to match pycellin conventions + _normalize_properties_data( + lineages, lin_id_key, cell_x_key, cell_y_key, cell_z_key, time_key, cell_id_key + ) + _normalize_properties_metadata( + props_md, cell_x_key, cell_y_key, cell_z_key, generic_md["space_unit"] + ) + + # Create the model + model = Model( + model_metadata=generic_md, + props_metadata=PropsMetadata(props=props_md), + data=Data({lin.graph["lineage_ID"]: lin for lin in lineages}), + ) + check_fusions(model) # pycellin DOES NOT support fusion events + + return model + + +if __name__ == "__main__": + geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/reader_test_graph.geff" + geff_file = "E:/Janelia_Cell_Trackathon/reader_test_graph.geff" + # geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/mouse-20250719.zarr/tracks" + # geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/test_pycellin_geff/test.zarr" + # geff_file = ( + # "/media/lxenard/data/Janelia_Cell_Trackathon/test_pycellin_geff/pycellin_to_geff.geff" + # ) + # geff_file = "E:/Janelia_Cell_Trackathon/test_pycellin_geff/pycellin_to_geff.geff" + # geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/test_trackmate_to_geff/FakeTracks.geff" + # geff_file = "E:/Janelia_Cell_Trackathon/test_trackmate_to_geff/FakeTracks.geff" + + # Yohsuke's file for geffception + # geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/cell_segmentation.zarr/tree.geff" + # geff_file = "E:/Janelia_Cell_Trackathon/cell_segmentation.zarr/tree.geff" + # geff_file = "/media/lxenard/data/Janelia_Cell_Trackathon/cell_segmentation.zarr/tree.geff/linage_tree.geff" + # geff_file = "E:/Janelia_Cell_Trackathon/cell_segmentation.zarr/tree.geff/linage_tree.geff" + + import plotly.io as pio + + # Plotly: set the default renderer to browser so I can visualize plots + pio.renderers.default = "browser" + + print(geff_file) + model = load_GEFF(geff_file) # , cell_x_key="x", cell_y_key="y") + # print(model) + # print("props_dict", model.props_metadata.props) + # for k in model.props_metadata.props.keys(): + # print(f"{k}") + lineages = model.get_cell_lineages() + # print(f"Number of lineages: {len(lineages)}") + # for lin in lineages: + # print(lin) + lin0 = lineages[0] + # lin7 = model.get_cell_lineage_from_ID(7) + # lin7.plot( + # node_hover_props=[ + # "cell_ID", + # "lineage_ID", + # "frame", + # "cell_x", + # "cell_y", + # "track_id", + # "seg_id", + # "tree_id", + # ] + # ) + # print(lin0.nodes(data=True)) + for node in lin0.nodes(data=True): + print(node) + break + lin0.plot() + + # cell_id_key + # lineage_id_key + # time_key + # cell_x_key + # cell_y_key + # cell_z_key + + # geff_graph, geff_md = geff.read_nx(geff_file, validate=True) + # print(geff_graph) + # # Check how many weakly connected components there are. + # print( + # f"Number of weakly connected components: {len(list(nx.weakly_connected_components(geff_graph)))}" + # ) + # for k, v in geff_graph.graph.items(): + # print(f"{k}: {v}") + # # print(graph.graph["axes"][0].unit) + + # if geff_md.directed: + # print("The graph is directed.") + + # metadata = {} # type: dict[str, Any] + # metadata["name"] = Path(geff_file).stem + # metadata["file_location"] = geff_file + # metadata["provenance"] = "geff" + # metadata["date"] = str(datetime.now()) + # # metadata["space_unit"] = + # # metadata["time_unit"] = + # metadata["pycellin_version"] = version("pycellin") + # metadata["geff_version"] = geff_md.geff_version + # for md in geff_md: + # print(md) diff --git a/pycellin/io/trackmate/__init__.py b/pycellin/io/trackmate/__init__.py index a5347f2..33d8ce8 100644 --- a/pycellin/io/trackmate/__init__.py +++ b/pycellin/io/trackmate/__init__.py @@ -1,2 +1,4 @@ from .loader import load_TrackMate_XML from .exporter import export_TrackMate_XML + +__all__ = ["load_TrackMate_XML", "export_TrackMate_XML"] diff --git a/pycellin/io/trackmate/loader.py b/pycellin/io/trackmate/loader.py index 1b8e446..481c4c2 100644 --- a/pycellin/io/trackmate/loader.py +++ b/pycellin/io/trackmate/loader.py @@ -1,25 +1,32 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- +import importlib +import warnings from copy import deepcopy from datetime import datetime import importlib.metadata from pathlib import Path from typing import Any -import warnings -from lxml import etree as ET import networkx as nx +from lxml import etree as ET from pycellin.classes import ( + CellLineage, + Data, Model, - PropsMetadata, Property, - Data, - CellLineage, + PropsMetadata, ) from pycellin.custom_types import PropertyType from pycellin.graph.properties.core import create_cell_id_property +from pycellin.io.utils import ( + _split_graph_into_lineages, + check_fusions, + _update_node_prop_key, + _update_lineage_prop_key, + _update_lineages_IDs_key, +) def _get_units( @@ -618,100 +625,6 @@ def _get_filtered_tracks_ID( return filtered_tracks_ID -def _add_tracks_info( - lineages: list[CellLineage], - tracks_attributes: list[dict[str, Any]], -) -> None: - """ - Update each CellLineage in the list with corresponding track attributes. - - This function iterates over a list of CellLineage objects, - attempting to match each lineage with its corresponding track - attributes based on the 'TRACK_ID' attribute present in the - lineage nodes. It then updates the lineage graph with these - attributes. - - Parameters - ---------- - lineages : list[CellLineage] - A list of the lineages to update. - tracks_attributes : list[dict[str, Any]] - A list of dictionaries, where each dictionary contains - attributes for a specific track, identified by a 'TRACK_ID' key. - - Raises - ------ - ValueError - If a lineage is found to contain nodes with multiple distinct - 'TRACK_ID' values, indicating an inconsistency in track ID - assignment. - """ - for lin in lineages: - # Finding the dict of attributes matching the track. - tmp = set(t_id for _, t_id in lin.nodes(data="TRACK_ID")) - - if not tmp: - # 'tmp' is empty because there's no nodes in the current graph. - # Even if it can't be updated, we still want to return this graph. - continue - elif tmp == {None}: - # Happens when all the nodes do not have a TRACK_ID attribute. - continue - elif None in tmp: - # Happens when at least one node does not have a TRACK_ID - # attribute, so we clean 'tmp' and carry on. - tmp.remove(None) - elif len(tmp) != 1: - raise ValueError("Impossible state: several IDs for one track.") - - current_track_id = list(tmp)[0] - current_track_attr = [ - d_attr for d_attr in tracks_attributes if d_attr["TRACK_ID"] == current_track_id - ][0] - - # Adding the attributes to the lineage. - for k, v in current_track_attr.items(): - lin.graph[k] = v - - -def _split_graph_into_lineages( - graph: nx.DiGraph, - tracks_attributes: list[dict[str, Any]], -) -> list[CellLineage]: - """ - Split a graph into several subgraphs, each representing a lineage. - - Parameters - ---------- - lineage : nx.DiGraph - The graph to split. - tracks_attributes : list[dict[str, Any]] - A list of dictionaries, where each dictionary contains TrackMate - attributes for a specific track, identified by a 'TRACK_ID' key. - - Returns - ------- - list[CellLineage] - A list of subgraphs, each representing a lineage. - """ - # One subgraph is created per lineage, so each subgraph is - # a connected component of `graph`. - lineages = [ - CellLineage(graph.subgraph(c).copy()) for c in nx.weakly_connected_components(graph) - ] - del graph # Redondant with the subgraphs. - - # Adding TrackMate tracks attributes to each lineage. - try: - _add_tracks_info(lineages, tracks_attributes) - except ValueError as err: - print(err) - # The program is in an impossible state so we need to stop. - raise - - return lineages - - def _update_props_metadata( props_md: PropsMetadata, units: dict[str, str], @@ -780,76 +693,6 @@ def _update_props_metadata( props_md._change_prop_description(f"lineage_{axis}", desc) -def _update_node_prop_key( - lineage: CellLineage, - old_key: str, - new_key: str, -) -> None: - """ - Update the key of a property in all the nodes of a lineage. - - Parameters - ---------- - lineage : CellLineage - The lineage to update. - old_key : str - The old key of the property. - new_key : str - The new key of the property. - """ - for node in lineage.nodes: - if old_key in lineage.nodes[node]: - lineage.nodes[node][new_key] = lineage.nodes[node].pop(old_key) - - -def _update_lineage_prop_key( - lineage: CellLineage, - old_key: str, - new_key: str, -) -> None: - """ - Update the key of a property in the graph of a lineage. - - Parameters - ---------- - lineage : CellLineage - The lineage to update. - old_key : str - The old key of the property. - new_key : str - The new key of the property. - """ - if old_key in lineage.graph: - lineage.graph[new_key] = lineage.graph.pop(old_key) - - -def _update_TRACK_ID( - lineage: CellLineage, -) -> None: - """ - Update the TRACK_ID property in the nodes and in the graph of a lineage. - - In the case of a one-node lineage, TRACK_ID does not exist in the graph - nor in the nodes. So we define the lineage_ID as minus the node ID. - That way, it is easy to discriminate between one-node lineages - (negative IDs) and multi-nodes lineages (positive IDs). - - Parameters - ---------- - lineage : CellLineage - The lineage to update. - """ - if "TRACK_ID" in lineage.graph: - lineage.graph["lineage_ID"] = lineage.graph.pop("TRACK_ID") - else: - # One-node graph don't have the TRACK_ID property in the graph - # or in the nodes, so we have to create it. - # We set the ID of a one-node lineage to the negative of the node ID. - assert len(lineage) == 1, "TRACK_ID not found and not a one-node lineage." - node = [n for n in lineage.nodes][0] - lineage.graph["lineage_ID"] = -node - - def _update_location_related_props( lineage: CellLineage, ) -> None: @@ -995,21 +838,26 @@ def _parse_model_tag( # We want one lineage per track, so we need to split the graph # into its connected components. - lineages = _split_graph_into_lineages(graph, tracks_attributes) + lineages = _split_graph_into_lineages( + graph, + lin_props=tracks_attributes, + lineage_ID_key="TRACK_ID", + ) # For pycellin compatibility, some TrackMate properties have to be renamed. # We only rename properties that are either essential to the functioning of # pycellin or confusing (e.g. "name" is a spot and a track property). _update_props_metadata(props_md, units, segmentation) + _update_lineages_IDs_key(lineages, "TRACK_ID") for lin in lineages: for key_name, new_key in [ + ("TRACK_ID", "lineage_ID"), # mandatory ("ID", "cell_ID"), # mandatory ("FRAME", "frame"), # mandatory ("name", "cell_name"), # confusing ]: _update_node_prop_key(lin, key_name, new_key) _update_lineage_prop_key(lin, "name", "lineage_name") - _update_TRACK_ID(lin) _update_location_related_props(lin) # Adding if each track was present in the 'FilteredTracks' tag @@ -1211,37 +1059,27 @@ def load_TrackMate_XML( metadata[tag_name] = element_string model = Model(metadata, props_md, data) - - # Pycellin DOES NOT support fusion events. - all_fusions = model.get_fusions() - if all_fusions: - # TODO: link toward correct documentation when it is written. - fusion_warning = ( - f"Unsupported data, {len(all_fusions)} cell fusions detected. " - "It is advised to deal with them before any other processing, " - "especially for tracking related properties. Crashes and incorrect " - "results can occur. See documentation for more details." - ) - warnings.warn(fusion_warning) + check_fusions(model) # pycellin DOES NOT support fusion events return model if __name__ == "__main__": - xml = "sample_data/FakeTracks.xml" + # xml = "sample_data/FakeTracks.xml" # xml = "sample_data/FakeTracks_no_tracks.xml" - # xml = "sample_data/Ecoli_growth_on_agar_pad.xml" + xml = "sample_data/Ecoli_growth_on_agar_pad.xml" # xml = "sample_data/Ecoli_growth_on_agar_pad_with_fusions.xml" # xml = "sample_data/Celegans-5pc-17timepoints.xml" model = load_TrackMate_XML(xml) # , keep_all_spots=True, keep_all_tracks=True) print(model) - print(model.props_metadata) print(model.model_metadata["pycellin_version"]) print(model.model_metadata) # print(model.props_md.node_props.keys()) # print(model.data) + # for lin in model.get_cell_lineages(): + # print(lin) # lineage = model.data.cell_data[0] # lineage.plot(node_hover_props=["cell_ID", "cell_name"]) diff --git a/pycellin/io/trackpy/__init__.py b/pycellin/io/trackpy/__init__.py index b7bb51b..58cd1bf 100644 --- a/pycellin/io/trackpy/__init__.py +++ b/pycellin/io/trackpy/__init__.py @@ -1,2 +1,4 @@ from .loader import load_trackpy_dataframe from .exporter import export_trackpy_dataframe + +__all__ = ["load_trackpy_dataframe", "export_trackpy_dataframe"] diff --git a/pycellin/io/utils.py b/pycellin/io/utils.py new file mode 100644 index 0000000..d0a6611 --- /dev/null +++ b/pycellin/io/utils.py @@ -0,0 +1,238 @@ +import warnings +from typing import Any + +import networkx as nx + +from pycellin.classes import CellLineage, Model + + +def check_fusions(model: Model) -> None: + """ + Check if the model contains fusions and issue a warning if so. + + Parameters + ---------- + model : Model + The pycellin model to check for fusions. + + Returns + ------- + None + """ + all_fusions = model.get_fusions() + if all_fusions: + # TODO: link toward correct documentation when it is written. + fusion_warning = ( + f"Unsupported data, {len(all_fusions)} cell fusions detected. " + "It is advised to deal with them before any other processing, " + "especially for tracking related properties. Crashes and incorrect " + "results can occur. See documentation for more details." + ) + warnings.warn(fusion_warning) + + +def _add_lineage_props( + lineages: list[CellLineage], + lin_props: list[dict[str, Any]], + lineage_ID_key: str | None = "lineage_ID", +) -> None: + """ + Update each CellLineage in the list with corresponding lineage properties. + + This function iterates over a list of CellLineage objects, + attempting to match each lineage with its corresponding lineage + properties based on the 'lineage_ID_key' property present in the + lineage nodes. It then updates the lineage graph with these + attributes. + + Parameters + ---------- + lineages : list[CellLineage] + A list of the lineages to update. + lin_props : list[dict[str, Any]] + A list of dictionaries, where each dictionary contains properties + for a specific lineage, identified by a the given 'lineage_ID_key' key. + lineage_ID_key : str | None, optional + The key used to identify the lineage in the attributes. + + Raises + ------ + ValueError + If a lineage is found to contain nodes with multiple distinct + 'lineage_ID_key' values, indicating an inconsistency in lineage ID + assignment. + """ + for lin in lineages: + # Finding the dict of properties matching the lineage. + tmp = set(t_id for _, t_id in lin.nodes(data=lineage_ID_key)) + + if not tmp: + # 'tmp' is empty because there's no nodes in the current graph. + # Even if it can't be updated, we still want to return this graph. + continue + elif tmp == {None}: + # Happens when all the nodes do not have a 'lineage_ID_key' property. + continue + elif None in tmp: + # Happens when at least one node does not have a 'lineage_ID_key' + # property, so we clean 'tmp' and carry on. + tmp.remove(None) + elif len(tmp) != 1: + raise ValueError("Impossible state: several IDs for one lineage.") + + current_lineage_id = list(tmp)[0] + current_lineage_attr = [ + d_attr for d_attr in lin_props if d_attr[lineage_ID_key] == current_lineage_id + ][0] + + # Adding the properties to the lineage. + for k, v in current_lineage_attr.items(): + lin.graph[k] = v + + +def _split_graph_into_lineages( + graph: nx.Graph | nx.DiGraph, + lin_props: list[dict[str, Any]] | None = None, + lineage_ID_key: str | None = "lineage_ID", +) -> list[CellLineage]: + """ + Split a graph into several subgraphs, each representing a lineage. + + Parameters + ---------- + lineage : nx.DiGraph + The graph to split. + lin_props : list[dict[str, Any]] | None + A list of dictionaries, where each dictionary contains TrackMate + attributes for a specific track, identified by a 'TRACK_ID' key. + If None, no attributes will be added to the lineages. + + Returns + ------- + list[CellLineage] + A list of subgraphs, each representing a lineage. + """ + # One subgraph is created per lineage, so each subgraph is + # a connected component of `graph`. + lineages = [ + CellLineage(graph.subgraph(c).copy()) for c in nx.weakly_connected_components(graph) + ] + del graph # Redondant with the subgraphs. + if not lin_props: + # We need to create and add a lineage_ID key to each lineage. + for i, lin in enumerate(lineages): + lin.graph["lineage_ID"] = i + else: + # Adding lineage properties to each lineage. + try: + _add_lineage_props(lineages, lin_props, lineage_ID_key) + except ValueError as err: + print(err) + # The program is in an impossible state so we need to stop. + raise + + return lineages + + +def _update_node_prop_key( + lineage: CellLineage, + old_key: str, + new_key: str, + enforce_old_key_existence: bool = False, + set_default_if_missing: bool = False, + default_value: Any | None = None, +) -> None: + """ + Update the key of a property in all the nodes of a lineage. + + Parameters + ---------- + lineage : CellLineage + The lineage to update. + old_key : str + The old key of the property. + new_key : str + The new key of the property. + enforce_old_key_existence : bool, optional + If True, raises an error when the old key does not exist in a node. + If False, the function will skip nodes that do not have the old key. + Defaults to False. + set_default_if_missing : bool, optional + If True, set the new key to `default_value` when the old key does not exist. + If False, the new key will not be set when the old key does not exist. + Defaults to False. + default_value : Any | None, optional + The default value to set if the old key does not exist + and set_default_if_missing is True. Defaults to None. + + Raises + ------ + ValueError + If enforce_old_key_existence is True and the old key does not exist + in a node. + """ + for node in lineage.nodes: + if old_key in lineage.nodes[node]: + lineage.nodes[node][new_key] = lineage.nodes[node].pop(old_key) + else: + if enforce_old_key_existence: + raise ValueError(f"Node {node} does not have the required key '{old_key}'.") + if set_default_if_missing: + lineage.nodes[node][new_key] = default_value + + +def _update_lineage_prop_key( + lineage: CellLineage, + old_key: str, + new_key: str, +) -> None: + """ + Update the key of a property in the graph of a lineage. + + Parameters + ---------- + lineage : CellLineage + The lineage to update. + old_key : str + The old key of the property. + new_key : str + The new key of the property. + """ + if old_key in lineage.graph: + lineage.graph[new_key] = lineage.graph.pop(old_key) + + +def _update_lineages_IDs_key( + lineages: list[CellLineage], + lineage_ID_key: str | None, +) -> None: + """ + Update the lineage ID key of lineage graphs to match pycellin convention. + + In the case of a one-node lineage, it is possible that the lineage does not have + a key to identify it. So we define the lineage_ID as minus the node ID. + That way, it is easy to discriminate between one-node lineages + (negative IDs) and multi-nodes lineages (positive IDs). + If the lineage_ID_key is not present in the lineage graph, a "lineage_ID" key + is created and set to the next available lineage ID. + + Parameters + ---------- + lineages : list[CellLineage] + The lineages to update. + lineage_ID_key : str | None + The key that is the lineage identifier in lineage graphs. + """ + ids = [lin.graph[lineage_ID_key] for lin in lineages if lineage_ID_key in lin.graph] + next_id = max(ids) + 1 if ids else 0 + + for lin in lineages: + try: + lin.graph["lineage_ID"] = lin.graph.pop(lineage_ID_key) + except KeyError: + if len(lin) == 1: + node = list(lin.nodes)[0] + lin.graph["lineage_ID"] = -node + else: + lin.graph["lineage_ID"] = next_id + next_id += 1 diff --git a/pycellin/utils.py b/pycellin/utils.py index d09978c..2498b82 100644 --- a/pycellin/utils.py +++ b/pycellin/utils.py @@ -1,10 +1,72 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from typing import get_args, get_origin, Literal +from typing import Literal, get_args, get_origin + +import networkx as nx +import networkx.algorithms.isomorphism as iso def check_literal_type(value, literal_type) -> bool: if get_origin(literal_type) is Literal: return value in get_args(literal_type) raise TypeError(f"{literal_type} is not a Literal type") + + +# TODO: this function should move into a tests/utils.py file +def is_equal(obt, exp): + """Check if two graphs are perfectly identical. + + It checks that the graphs are isomorphic, and that their graph, + nodes and edges attributes are all identical. + + Args: + obt (nx.DiGraph): The obtained graph, built from XML_reader.py. + exp (nx.DiGraph): The expected graph, built from here. + + Returns: + bool: True if the graphs are identical, False otherwise. + """ + edges_attr = list(set([k for (n1, n2, d) in exp.edges.data() for k in d])) + edges_default = len(edges_attr) * [0] + em = iso.categorical_edge_match(edges_attr, edges_default) + nodes_attr = list(set([k for (n, d) in exp.nodes.data() for k in d])) + nodes_default = len(nodes_attr) * [0] + nm = iso.categorical_node_match(nodes_attr, nodes_default) + + if not obt.nodes.data() and not exp.nodes.data(): + same_nodes = True + elif len(obt.nodes.data()) != len(exp.nodes.data()): + same_nodes = False + else: + for data1, data2 in zip(sorted(obt.nodes.data()), sorted(exp.nodes.data())): + n1, attr1 = data1 + n2, attr2 = data2 + if sorted(attr1) == sorted(attr2) and n1 == n2: + same_nodes = True + else: + same_nodes = False + + if not obt.edges.data() and not exp.edges.data(): + same_edges = True + elif len(obt.edges.data()) != len(exp.edges.data()): + same_edges = False + else: + for data1, data2 in zip(sorted(obt.edges.data()), sorted(exp.edges.data())): + n11, n12, attr1 = data1 + n21, n22, attr2 = data2 + if sorted(attr1) == sorted(attr2) and sorted((n11, n12)) == sorted( + (n21, n22) + ): + same_edges = True + else: + same_edges = False + + if ( + nx.is_isomorphic(obt, exp, edge_match=em, node_match=nm) + and obt.graph == exp.graph + and same_nodes + and same_edges + ): + return True + else: + return False diff --git a/pyproject.toml b/pyproject.toml index 8010e56..10b59ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ description = "Graph-based framework to manipulate and analyze cell lineages fro readme = "README.md" requires-python = ">=3.10" dependencies = [ + "geff>=0.5.0", "igraph>=0.9", "lxml>=5", "matplotlib>=3", @@ -51,6 +52,10 @@ packages = [ "pycellin.graph.properties", "pycellin.io", "pycellin.io.cell_tracking_challenge", + "pycellin.io.geff", "pycellin.io.trackmate", "pycellin.io.trackpy", ] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/sample_data/Ecoli_growth_on_agar_pad.xml b/sample_data/Ecoli_growth_on_agar_pad.xml index 01cdca0..191970b 100644 --- a/sample_data/Ecoli_growth_on_agar_pad.xml +++ b/sample_data/Ecoli_growth_on_agar_pad.xml @@ -330,7 +330,7 @@ Computing track features: - Track quality in 0 ms. - Track motility analysis in 1 ms. Computation done in 6 ms. - + diff --git a/sample_data/FakeTracks.xml b/sample_data/FakeTracks.xml index dfbc76c..ed81f7a 100644 --- a/sample_data/FakeTracks.xml +++ b/sample_data/FakeTracks.xml @@ -306,7 +306,7 @@ Computing track features: - Track quality in 0 ms. - Track motility analysis in 1 ms. Computation done in 5 ms. - + diff --git a/tests/io/test_utils.py b/tests/io/test_utils.py new file mode 100644 index 0000000..f5b2805 --- /dev/null +++ b/tests/io/test_utils.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python3 + +"""Unit test for IO utilities functions.""" + +import networkx as nx +import pytest + +from pycellin.classes import CellLineage +from pycellin.io.utils import ( + _add_lineage_props, + _split_graph_into_lineages, + _update_node_prop_key, + _update_lineage_prop_key, + _update_lineages_IDs_key, +) +from pycellin.utils import is_equal + + +# _update_node_prop_key #################################################### + + +# def _update_node_prop_key( +# lineage: CellLineage, +# old_key: str, +# new_key: str, +# enforce_old_key_existence: bool = False, +# set_default_if_missing: bool = False, +# default_value: Any | None = None, +# ) -> None: +# """ +# Update the key of a property in all the nodes of a lineage. + +# Parameters +# ---------- +# lineage : CellLineage +# The lineage to update. +# old_key : str +# The old key of the property. +# new_key : str +# The new key of the property. +# enforce_old_key_existence : bool, optional +# If True, raises an error when the old key does not exist in a node. +# If False, the function will skip nodes that do not have the old key. +# Defaults to False. +# set_default_if_missing : bool, optional +# If True, set the new key to `default_value` when the old key does not exist. +# If False, the new key will not be set when the old key does not exist. +# Defaults to False. +# default_value : Any | None, optional +# The default value to set if the old key does not exist +# and set_default_if_missing is True. Defaults to None. +# """ +# for node in lineage.nodes: +# if old_key in lineage.nodes[node]: +# lineage.nodes[node][new_key] = lineage.nodes[node].pop(old_key) +# else: +# if enforce_old_key_existence: +# raise ValueError( +# f"Node {node} does not have the required key '{old_key}'." +# ) +# if set_default_if_missing: +# lineage.nodes[node][new_key] = default_value + + +def test_update_node_prop_key(): + """Test updating a node property key.""" + lineage = CellLineage() + old_key_values = ["value1", "value2", "value3"] + lineage.add_node(1, old_key=old_key_values[0]) + lineage.add_node(2, old_key=old_key_values[1]) + lineage.add_node(3, old_key=old_key_values[2]) + + _update_node_prop_key(lineage, "old_key", "new_key") + + for i, node in enumerate(lineage.nodes): + assert "new_key" in lineage.nodes[node] + assert "old_key" not in lineage.nodes[node] + assert lineage.nodes[node]["new_key"] == old_key_values[i] + + +def test_update_node_prop_key_missing_old_key_skip(): + """Test that nodes without old_key are skipped when enforce_old_key_existence=False.""" + lineage = CellLineage() + lineage.add_node(1, old_key="value1") + lineage.add_node(2) # No old_key + lineage.add_node(3, old_key="value3") + + _update_node_prop_key(lineage, "old_key", "new_key") + + assert lineage.nodes[1]["new_key"] == "value1" + assert "old_key" not in lineage.nodes[1] + assert "new_key" not in lineage.nodes[2] + assert "old_key" not in lineage.nodes[2] + assert lineage.nodes[3]["new_key"] == "value3" + assert "old_key" not in lineage.nodes[3] + + +def test_update_node_prop_key_enforce_old_key_existence(): + """Test that missing old_key raises error when enforce_old_key_existence=True.""" + lineage = CellLineage() + lineage.add_node(1, old_key="value1") + lineage.add_node(2) # No old_key + + with pytest.raises(ValueError, match="Node 2 does not have the required key 'old_key'"): + _update_node_prop_key(lineage, "old_key", "new_key", enforce_old_key_existence=True) + + +def test_update_node_prop_key_set_default_if_missing(): + """Test setting default value when old_key is missing and set_default_if_missing=True.""" + lineage = CellLineage() + lineage.add_node(1, old_key="value1") + lineage.add_node(2) # No old_key + lineage.add_node(3, old_key="value3") + + _update_node_prop_key( + lineage, + "old_key", + "new_key", + set_default_if_missing=True, + default_value="default", + ) + + assert lineage.nodes[1]["new_key"] == "value1" + assert "old_key" not in lineage.nodes[1] + assert lineage.nodes[2]["new_key"] == "default" + assert "old_key" not in lineage.nodes[2] + assert lineage.nodes[3]["new_key"] == "value3" + assert "old_key" not in lineage.nodes[3] + + +def test_update_node_prop_key_set_default_none(): + """Test setting None as default value when old_key is missing.""" + lineage = CellLineage() + lineage.add_node(1, old_key="value1") + lineage.add_node(2) # No old_key + + _update_node_prop_key(lineage, "old_key", "new_key", set_default_if_missing=True) + + assert lineage.nodes[1]["new_key"] == "value1" + assert lineage.nodes[2]["new_key"] is None + + +def test_update_node_prop_key_empty_lineage(): + """Test function with empty lineage (no nodes).""" + lineage = CellLineage() + # Should not raise an error and do nothing + _update_node_prop_key(lineage, "old_key", "new_key") + assert len(lineage.nodes) == 0 + + +def test_update_node_prop_key_same_key_name(): + """Test updating a key to itself (should work without issues).""" + lineage = CellLineage() + lineage.add_node(1, test_key="value1") + lineage.add_node(2, test_key="value2") + + _update_node_prop_key(lineage, "test_key", "test_key") + + assert lineage.nodes[1]["test_key"] == "value1" + assert lineage.nodes[2]["test_key"] == "value2" + + +# _update_lineage_prop_key ################################################# + + +def test_update_lineage_prop_key(): + lineage = CellLineage() + lineage.graph["old_key"] = "old_value" + _update_lineage_prop_key(lineage, "old_key", "new_key") + + assert "new_key" in lineage.graph + assert lineage.graph["new_key"] == "old_value" + assert "old_key" not in lineage.graph + + +# _update_lineages_IDs_key ################################################# + + +def test_update_lineages_IDs_key(): + """Test updating lineage IDs key.""" + lin1 = CellLineage() + lin1.add_nodes_from([1, 2, 3]) + lin1.graph["TRACK_ID"] = 10 + lin2 = CellLineage() + lin2.add_nodes_from([4, 5]) + lin2.graph["TRACK_ID"] = 20 + + _update_lineages_IDs_key([lin1, lin2], "TRACK_ID") + assert lin1.graph["lineage_ID"] == 10 + assert lin2.graph["lineage_ID"] == 20 + assert "TRACK_ID" not in lin1.graph + assert "TRACK_ID" not in lin2.graph + + +def test_update_lineages_IDs_key_no_key_multi_node(): + """Test updating lineage IDs key when no TRACK_ID key is present in a multi-node lineage.""" + lin1 = CellLineage() + lin1.add_nodes_from([1, 2, 3]) + lin2 = CellLineage() + lin2.add_nodes_from([4, 5]) + lin2.graph["TRACK_ID"] = 20 + + _update_lineages_IDs_key([lin1, lin2], "TRACK_ID") + assert lin1.graph["lineage_ID"] == 21 + assert lin2.graph["lineage_ID"] == 20 + assert "TRACK_ID" not in lin1.graph + assert "TRACK_ID" not in lin2.graph + + +def test_update_lineages_IDs_key_no_key_one_node(): + """Test updating lineage IDs key when no TRACK_ID key is present in a one-node lineage.""" + lin1 = CellLineage() + lin1.add_node(1) + lin2 = CellLineage() + lin2.add_nodes_from([4, 5]) + lin2.graph["TRACK_ID"] = 20 + _update_lineages_IDs_key([lin1, lin2], "TRACK_ID") + assert lin1.graph["lineage_ID"] == -1 + assert lin2.graph["lineage_ID"] == 20 + + +def test_update_lineages_IDs_key_all_lineages_no_key(): + """Test updating lineage IDs key when no lineages have the key.""" + lin1 = CellLineage() + lin1.add_nodes_from([1, 2, 3]) + lin2 = CellLineage() + lin2.add_nodes_from([4, 5]) + lin3 = CellLineage() + lin3.add_node(6) + + _update_lineages_IDs_key([lin1, lin2, lin3], "TRACK_ID") + assert lin1.graph["lineage_ID"] == 0 + assert lin2.graph["lineage_ID"] == 1 + assert lin3.graph["lineage_ID"] == -6 + assert "TRACK_ID" not in lin1.graph + assert "TRACK_ID" not in lin2.graph + assert "TRACK_ID" not in lin3.graph + + +def test_update_lineages_IDs_key_empty_list(): + """Test updating lineage IDs key with empty lineages list.""" + _update_lineages_IDs_key([], "TRACK_ID") + + +def test_update_lineages_IDs_key_mixed_scenarios(): + """Test with mix of single-node, multi-node, and lineages with existing keys.""" + lin1 = CellLineage() # single node, no key + lin1.add_node(1) + lin2 = CellLineage() # multi-node, no key + lin2.add_nodes_from([2, 3]) + lin3 = CellLineage() # has key + lin3.add_node(4) + lin3.graph["TRACK_ID"] = 10 + lin4 = CellLineage() # single node, no key + lin4.add_node(5) + + _update_lineages_IDs_key([lin1, lin2, lin3, lin4], "TRACK_ID") + assert lin1.graph["lineage_ID"] == -1 + assert lin2.graph["lineage_ID"] == 11 + assert lin3.graph["lineage_ID"] == 10 + assert lin4.graph["lineage_ID"] == -5 + + +def test_update_lineages_IDs_key_preserves_other_graph_attributes(): + """Test that other graph attributes are preserved.""" + lin1 = CellLineage() + lin1.add_node(1) + lin1.graph["TRACK_ID"] = 10 + lin1.graph["other_attr"] = "value" + + _update_lineages_IDs_key([lin1], "TRACK_ID") + assert lin1.graph["lineage_ID"] == 10 + assert lin1.graph["other_attr"] == "value" + assert "TRACK_ID" not in lin1.graph + + +# _add_lineages_props ############################################################ + + +def test_add_lineages_props(): + g1_attr = {"name": "blob", "lineage_ID": 0} + g2_attr = {"name": "blub", "lineage_ID": 1} + + g1_obt = nx.DiGraph() + g1_obt.add_node(1, lineage_ID=0) + g2_obt = nx.DiGraph() + g2_obt.add_node(2, lineage_ID=1) + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr]) + + g1_exp = nx.DiGraph() + g1_exp.graph["name"] = "blob" + g1_exp.graph["lineage_ID"] = 0 + g1_exp.add_node(1, lineage_ID=0) + g2_exp = nx.DiGraph() + g2_exp.graph["name"] = "blub" + g2_exp.graph["lineage_ID"] = 1 + g2_exp.add_node(2, lineage_ID=1) + + assert is_equal(g1_obt, g1_exp) + assert is_equal(g2_obt, g2_exp) + + +def test_add_lineages_props_different_lin_ID_key(): + g1_attr = {"name": "blob", "TRACK_ID": 0} + g2_attr = {"name": "blub", "TRACK_ID": 1} + + g1_obt = nx.DiGraph() + g1_obt.add_node(1, TRACK_ID=0) + g2_obt = nx.DiGraph() + g2_obt.add_node(2, TRACK_ID=1) + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr], lineage_ID_key="TRACK_ID") + + g1_exp = nx.DiGraph() + g1_exp.graph["name"] = "blob" + g1_exp.graph["TRACK_ID"] = 0 + g1_exp.add_node(1, TRACK_ID=0) + g2_exp = nx.DiGraph() + g2_exp.graph["name"] = "blub" + g2_exp.graph["TRACK_ID"] = 1 + g2_exp.add_node(2, TRACK_ID=1) + + assert is_equal(g1_obt, g1_exp) + assert is_equal(g2_obt, g2_exp) + + +def test_add_lineages_props_no_lin_ID_on_all_nodes(): + g1_attr = {"name": "blob", "lineage_ID": 0} + g2_attr = {"name": "blub", "lineage_ID": 1} + + g1_obt = nx.DiGraph() + g1_obt.add_node(1) + g1_obt.add_node(3) + g2_obt = nx.DiGraph() + g2_obt.add_node(2, lineage_ID=1) + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr], lineage_ID_key="lineage_ID") + + g1_exp = nx.DiGraph() + g1_exp.add_node(1) + g1_exp.add_node(3) + g2_exp = nx.DiGraph() + g2_exp.graph["name"] = "blub" + g2_exp.graph["lineage_ID"] = 1 + g2_exp.add_node(2, lineage_ID=1) + + assert is_equal(g1_obt, g1_exp) + assert is_equal(g2_obt, g2_exp) + + +def test_add_lineages_props_no_lin_ID_on_one_node(): + g1_attr = {"name": "blob", "lineage_ID": 0} + g2_attr = {"name": "blub", "lineage_ID": 1} + + g1_obt = nx.DiGraph() + g1_obt.add_node(1) + g1_obt.add_node(3) + g1_obt.add_node(4, lineage_ID=0) + + g2_obt = nx.DiGraph() + g2_obt.add_node(2, lineage_ID=1) + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr]) + + g1_exp = nx.DiGraph() + g1_exp.graph["name"] = "blob" + g1_exp.graph["lineage_ID"] = 0 + g1_exp.add_node(1) + g1_exp.add_node(3) + g1_exp.add_node(4, lineage_ID=0) + g2_exp = nx.DiGraph() + g2_exp.graph["name"] = "blub" + g2_exp.graph["lineage_ID"] = 1 + g2_exp.add_node(2, lineage_ID=1) + + assert is_equal(g1_obt, g1_exp) + assert is_equal(g2_obt, g2_exp) + + +def test_add_lineages_props_different_ID_for_one_track(): + g1_attr = {"name": "blob", "lineage_ID": 0} + g2_attr = {"name": "blub", "lineage_ID": 1} + + g1_obt = nx.DiGraph() + g1_obt.add_node(1, lineage_ID=0) + g1_obt.add_node(3, lineage_ID=2) + g1_obt.add_node(4, lineage_ID=0) + + g2_obt = nx.DiGraph() + g2_obt.add_node(2, lineage_ID=1) + with pytest.raises(ValueError): + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr]) + + +def test_add_lineages_props_no_nodes(): + g1_attr = {"name": "blob", "lineage_ID": 0} + g2_attr = {"name": "blub", "lineage_ID": 1} + + g1_obt = nx.DiGraph() + g2_obt = nx.DiGraph() + g2_obt.add_node(2, lineage_ID=1) + _add_lineage_props([g1_obt, g2_obt], [g1_attr, g2_attr]) + + g1_exp = nx.DiGraph() + g2_exp = nx.DiGraph() + g2_exp.graph["name"] = "blub" + g2_exp.graph["lineage_ID"] = 1 + g2_exp.add_node(2, lineage_ID=1) + + assert is_equal(g1_obt, g1_exp) + assert is_equal(g2_obt, g2_exp) + + +# _split_graph_into_lineages ################################################## + + +def test_split_graph_into_lineages(): + g1_attr = {"name": "blob", "lineage_ID": 1} + g2_attr = {"name": "blub", "lineage_ID": 2} + + g = nx.DiGraph() + g.add_node(1, lineage_ID=1) + g.add_node(2, lineage_ID=1) + g.add_edge(1, 2) + g.add_node(3, lineage_ID=2) + g.add_node(4, lineage_ID=2) + g.add_edge(3, 4) + obtained = _split_graph_into_lineages(g, [g1_attr, g2_attr]) + + g1_exp = CellLineage(g.subgraph([1, 2])) + g1_exp.graph["name"] = "blob" + g1_exp.graph["lineage_ID"] = 1 + g2_exp = CellLineage(g.subgraph([3, 4])) + g2_exp.graph["name"] = "blub" + g2_exp.graph["lineage_ID"] = 2 + + assert len(obtained) == 2 + assert is_equal(obtained[0], g1_exp) + assert is_equal(obtained[1], g2_exp) + + +def test_split_graph_into_lineages_different_lin_ID_key(): + g1_attr = {"name": "blob", "TRACK_ID": 1} + g2_attr = {"name": "blub", "TRACK_ID": 2} + + g = nx.DiGraph() + g.add_node(1, TRACK_ID=1) + g.add_node(2, TRACK_ID=1) + g.add_edge(1, 2) + g.add_node(3, TRACK_ID=2) + g.add_node(4, TRACK_ID=2) + g.add_edge(3, 4) + obtained = _split_graph_into_lineages(g, [g1_attr, g2_attr], lineage_ID_key="TRACK_ID") + + g1_exp = CellLineage(g.subgraph([1, 2])) + g1_exp.graph["name"] = "blob" + g1_exp.graph["TRACK_ID"] = 1 + g2_exp = CellLineage(g.subgraph([3, 4])) + g2_exp.graph["name"] = "blub" + g2_exp.graph["TRACK_ID"] = 2 + + assert len(obtained) == 2 + assert is_equal(obtained[0], g1_exp) + assert is_equal(obtained[1], g2_exp) + + +def test_split_graph_into_lineages_no_lin_props(): + g = nx.DiGraph() + g.add_edges_from([(1, 2), (3, 4)]) + + obtained = _split_graph_into_lineages(g) + + g1_exp = CellLineage(g.subgraph([1, 2])) + g1_exp.graph["lineage_ID"] = 0 + g2_exp = CellLineage(g.subgraph([3, 4])) + g2_exp.graph["lineage_ID"] = 1 + + assert len(obtained) == 2 + assert is_equal(obtained[0], g1_exp) + assert is_equal(obtained[1], g2_exp) + + +def test_split_graph_into_lineages_different_ID(): + g1_attr = {"name": "blob", "lineage_ID": 1} + g2_attr = {"name": "blub", "lineage_ID": 2} + + g = nx.DiGraph() + g.add_node(1, lineage_ID=0) + g.add_node(2, lineage_ID=1) + g.add_edge(1, 2) + g.add_node(3, lineage_ID=2) + g.add_node(4, lineage_ID=2) + g.add_edge(3, 4) + + with pytest.raises(ValueError): + _split_graph_into_lineages(g, [g1_attr, g2_attr]) diff --git a/tests/io/trackmate/test_loader.py b/tests/io/trackmate/test_loader.py index 7b109c1..183fd0b 100644 --- a/tests/io/trackmate/test_loader.py +++ b/tests/io/trackmate/test_loader.py @@ -1,76 +1,18 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- """Unit test for TrackMate XML file loader.""" -from copy import deepcopy import io +from copy import deepcopy from typing import Any -from lxml import etree as ET import networkx as nx -import networkx.algorithms.isomorphism as iso import pytest +from lxml import etree as ET -from pycellin.classes import CellLineage, Property, PropsMetadata import pycellin.io.trackmate.loader as tml - - -def is_equal(obt, exp): - """Check if two graphs are perfectly identical. - - It checks that the graphs are isomorphic, and that their graph, - nodes and edges attributes are all identical. - - Args: - obt (nx.DiGraph): The obtained graph, built from XML_reader.py. - exp (nx.DiGraph): The expected graph, built from here. - - Returns: - bool: True if the graphs are identical, False otherwise. - """ - edges_attr = list(set([k for (n1, n2, d) in exp.edges.data() for k in d])) - edges_default = len(edges_attr) * [0] - em = iso.categorical_edge_match(edges_attr, edges_default) - nodes_attr = list(set([k for (n, d) in exp.nodes.data() for k in d])) - nodes_default = len(nodes_attr) * [0] - nm = iso.categorical_node_match(nodes_attr, nodes_default) - - if not obt.nodes.data() and not exp.nodes.data(): - same_nodes = True - elif len(obt.nodes.data()) != len(exp.nodes.data()): - same_nodes = False - else: - for data1, data2 in zip(sorted(obt.nodes.data()), sorted(exp.nodes.data())): - n1, attr1 = data1 - n2, attr2 = data2 - if sorted(attr1) == sorted(attr2) and n1 == n2: - same_nodes = True - else: - same_nodes = False - - if not obt.edges.data() and not exp.edges.data(): - same_edges = True - elif len(obt.edges.data()) != len(exp.edges.data()): - same_edges = False - else: - for data1, data2 in zip(sorted(obt.edges.data()), sorted(exp.edges.data())): - n11, n12, attr1 = data1 - n21, n22, attr2 = data2 - if sorted(attr1) == sorted(attr2) and sorted((n11, n12)) == sorted((n21, n22)): - same_edges = True - else: - same_edges = False - - if ( - nx.is_isomorphic(obt, exp, edge_match=em, node_match=nm) - and obt.graph == exp.graph - and same_nodes - and same_edges - ): - return True - else: - return False +from pycellin.classes import CellLineage, Property, PropsMetadata +from pycellin.utils import is_equal # Fixtures ##################################################################### @@ -679,7 +621,9 @@ def test_add_all_nodes_only_ID_attribute(): def test_add_all_nodes_no_node_attributes(): - xml_data = ' ' + xml_data = ( + ' ' + ) it = ET.iterparse(io.BytesIO(xml_data.encode("utf-8")), events=["start", "end"]) _, element = next(it) @@ -1130,209 +1074,6 @@ def test_get_filtered_tracks_ID_no_tracks(): assert not obtained_ID -# _add_tracks_info ############################################################ - - -def test_add_tracks_info(): - g1_attr = {"name": "blob", "TRACK_ID": 0} - g2_attr = {"name": "blub", "TRACK_ID": 1} - - g1_obt = nx.DiGraph() - g1_obt.add_node(1, TRACK_ID=0) - g2_obt = nx.DiGraph() - g2_obt.add_node(2, TRACK_ID=1) - tml._add_tracks_info([g1_obt, g2_obt], [g1_attr, g2_attr]) - - g1_exp = nx.DiGraph() - g1_exp.graph["name"] = "blob" - g1_exp.graph["TRACK_ID"] = 0 - g1_exp.add_node(1, TRACK_ID=0) - g2_exp = nx.DiGraph() - g2_exp.graph["name"] = "blub" - g2_exp.graph["TRACK_ID"] = 1 - g2_exp.add_node(2, TRACK_ID=1) - - assert is_equal(g1_obt, g1_exp) - assert is_equal(g2_obt, g2_exp) - - -def test_add_tracks_info_no_track_ID_on_all_nodes(): - g1_attr = {"name": "blob", "TRACK_ID": 0} - g2_attr = {"name": "blub", "TRACK_ID": 1} - - g1_obt = nx.DiGraph() - g1_obt.add_node(1) - g1_obt.add_node(3) - g2_obt = nx.DiGraph() - g2_obt.add_node(2, TRACK_ID=1) - tml._add_tracks_info([g1_obt, g2_obt], [g1_attr, g2_attr]) - - g1_exp = nx.DiGraph() - g1_exp.add_node(1) - g1_exp.add_node(3) - g2_exp = nx.DiGraph() - g2_exp.graph["name"] = "blub" - g2_exp.graph["TRACK_ID"] = 1 - g2_exp.add_node(2, TRACK_ID=1) - - assert is_equal(g1_obt, g1_exp) - assert is_equal(g2_obt, g2_exp) - - -def test_add_tracks_info_no_track_ID_on_one_node(): - g1_attr = {"name": "blob", "TRACK_ID": 0} - g2_attr = {"name": "blub", "TRACK_ID": 1} - - g1_obt = nx.DiGraph() - g1_obt.add_node(1) - g1_obt.add_node(3) - g1_obt.add_node(4, TRACK_ID=0) - - g2_obt = nx.DiGraph() - g2_obt.add_node(2, TRACK_ID=1) - tml._add_tracks_info([g1_obt, g2_obt], [g1_attr, g2_attr]) - - g1_exp = nx.DiGraph() - g1_exp.graph["name"] = "blob" - g1_exp.graph["TRACK_ID"] = 0 - g1_exp.add_node(1) - g1_exp.add_node(3) - g1_exp.add_node(4, TRACK_ID=0) - g2_exp = nx.DiGraph() - g2_exp.graph["name"] = "blub" - g2_exp.graph["TRACK_ID"] = 1 - g2_exp.add_node(2, TRACK_ID=1) - - assert is_equal(g1_obt, g1_exp) - assert is_equal(g2_obt, g2_exp) - - -def test_add_tracks_info_different_ID_for_one_track(): - g1_attr = {"name": "blob", "TRACK_ID": 0} - g2_attr = {"name": "blub", "TRACK_ID": 1} - - g1_obt = nx.DiGraph() - g1_obt.add_node(1, TRACK_ID=0) - g1_obt.add_node(3, TRACK_ID=2) - g1_obt.add_node(4, TRACK_ID=0) - - g2_obt = nx.DiGraph() - g2_obt.add_node(2, TRACK_ID=1) - with pytest.raises(ValueError): - tml._add_tracks_info([g1_obt, g2_obt], [g1_attr, g2_attr]) - - -def test_add_tracks_info_no_nodes(): - g1_attr = {"name": "blob", "TRACK_ID": 0} - g2_attr = {"name": "blub", "TRACK_ID": 1} - - g1_obt = nx.DiGraph() - g2_obt = nx.DiGraph() - g2_obt.add_node(2, TRACK_ID=1) - tml._add_tracks_info([g1_obt, g2_obt], [g1_attr, g2_attr]) - - g1_exp = nx.DiGraph() - g2_exp = nx.DiGraph() - g2_exp.graph["name"] = "blub" - g2_exp.graph["TRACK_ID"] = 1 - g2_exp.add_node(2, TRACK_ID=1) - - assert is_equal(g1_obt, g1_exp) - assert is_equal(g2_obt, g2_exp) - - -# _split_graph_into_lineages ################################################## - - -def test_split_graph_into_lineages(): - g1_attr = {"name": "blob", "TRACK_ID": 1} - g2_attr = {"name": "blub", "TRACK_ID": 2} - - g = nx.DiGraph() - g.add_node(1, TRACK_ID=1) - g.add_node(2, TRACK_ID=1) - g.add_edge(1, 2) - g.add_node(3, TRACK_ID=2) - g.add_node(4, TRACK_ID=2) - g.add_edge(3, 4) - obtained = tml._split_graph_into_lineages(g, [g1_attr, g2_attr]) - - g1_exp = CellLineage(g.subgraph([1, 2])) - g1_exp.graph["name"] = "blob" - g1_exp.graph["TRACK_ID"] = 1 - g2_exp = CellLineage(g.subgraph([3, 4])) - g2_exp.graph["name"] = "blub" - g2_exp.graph["TRACK_ID"] = 2 - - assert len(obtained) == 2 - assert is_equal(obtained[0], g1_exp) - assert is_equal(obtained[1], g2_exp) - - -def test_split_graph_into_lineages_different_ID(): - g1_attr = {"name": "blob", "TRACK_ID": 1} - g2_attr = {"name": "blub", "TRACK_ID": 2} - - g = nx.DiGraph() - g.add_node(1, TRACK_ID=0) - g.add_node(2, TRACK_ID=1) - g.add_edge(1, 2) - g.add_node(3, TRACK_ID=2) - g.add_node(4, TRACK_ID=2) - g.add_edge(3, 4) - - with pytest.raises(ValueError): - tml._split_graph_into_lineages(g, [g1_attr, g2_attr]) - - -# _update_node_prop_key #################################################### - - -def test_update_node_prop_key(): - lineage = CellLineage() - old_key_values = ["value1", "value2", "value3"] - lineage.add_node(1, old_key=old_key_values[0]) - lineage.add_node(2, old_key=old_key_values[1]) - lineage.add_node(3, old_key=old_key_values[2]) - - tml._update_node_prop_key(lineage, "old_key", "new_key") - - for i, node in enumerate(lineage.nodes): - assert "new_key" in lineage.nodes[node] - assert "old_key" not in lineage.nodes[node] - assert lineage.nodes[node]["new_key"] == old_key_values[i] - - -# _update_TRACK_ID ############################################################ - - -def test_update_TRACK_ID(): - lineage = CellLineage() - lineage.add_node(1) - lineage.graph["TRACK_ID"] = 10 - tml._update_TRACK_ID(lineage) - assert "lineage_ID" in lineage.graph - assert lineage.graph["lineage_ID"] == 10 - assert "lineage_ID" not in lineage.nodes[1] - - -def test_update_TRACK_ID_no_TRACK_ID(): - lineage = CellLineage() - lineage.add_node(1) - tml._update_TRACK_ID(lineage) - assert "lineage_ID" in lineage.graph - assert lineage.graph["lineage_ID"] == -1 - - -def test_update_TRACK_ID_several_subgraphs(): - lineage = CellLineage() - lineage.add_node(1) - lineage.add_node(2) - - with pytest.raises(AssertionError): - tml._update_TRACK_ID(lineage) - - # _update_location_related_props ###########################################