Repository: tobac-project/tobac

Issue:
Untracked cells shouldn't have `cell_time`
Currently, we give untracked cells (which are, by default, labeled `-1`) a `time_cell` value. In at least `RC_v1.5.0`, these values end up being equal to the length of the total track. We should instead make these `NaT` or similar. 

Comments:

Patch:
diff --git a/doc/feature_detection/notebooks/feature_detection_filtering.ipynb b/doc/feature_detection/notebooks/feature_detection_filtering.ipynb
index fc5fed9..4f3d5e1 100644
--- a/doc/feature_detection/notebooks/feature_detection_filtering.ipynb
+++ b/doc/feature_detection/notebooks/feature_detection_filtering.ipynb
@@ -199,7 +199,7 @@
     "        # This is the parameter for erosion that gets passed to the scikit-image library. \n",
     "        footprint = np.ones((erosion, erosion))\n",
     "        # This is what tobac sees after erosion. \n",
-    "        filtered_mask = skimage.morphology.binary_erosion(tobac_mask, selem).astype(np.int64)\n",
+    "        filtered_mask = skimage.morphology.binary_erosion(tobac_mask, footprint).astype(np.int64)\n",
     "    else:\n",
     "        filtered_mask = tobac_mask\n",
     "\n",
@@ -219,9 +219,9 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python [conda env:tobac_stable]",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
-   "name": "conda-env-tobac_stable-py"
+   "name": "python3"
   },
   "language_info": {
    "codemirror_mode": {
@@ -233,9 +233,8 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.5"
+   "version": "3.11.3"
   },
-  "orig_nbformat": 4,
   "vscode": {
    "interpreter": {
     "hash": "25a19fbe0a9132dfb9279d48d161753c6352f8f9478c2e74383d340069b907c3"
diff --git a/tobac/tracking.py b/tobac/tracking.py
index 7f567c4..0b7e2c1 100644
--- a/tobac/tracking.py
+++ b/tobac/tracking.py
@@ -440,7 +440,9 @@ def linking_trackpy(
     #     add time coordinate relative to cell initiation:
     #    logging.debug('start adding cell time to trajectories')
     trajectories_filtered_filled = trajectories_filtered_unfilled
-    trajectories_final = add_cell_time(trajectories_filtered_filled)
+    trajectories_final = add_cell_time(
+        trajectories_filtered_filled, cell_number_unassigned=cell_number_unassigned
+    )
     # Add metadata
     trajectories_final.attrs["cell_number_unassigned"] = cell_number_unassigned
 
@@ -532,13 +534,15 @@ def fill_gaps(
     return t_out
 
 
-def add_cell_time(t):
+def add_cell_time(t: pd.DataFrame, cell_number_unassigned: int):
     """add cell time as time since the initiation of each cell
 
     Parameters
     ----------
     t : pandas.DataFrame
         trajectories with added coordinates
+    cell_number_unassigned: int
+        unassigned cell value
 
     Returns
     -------
@@ -551,6 +555,7 @@ def add_cell_time(t):
 
     t["time_cell"] = t["time"] - t.groupby("cell")["time"].transform("min")
     t["time_cell"] = pd.to_timedelta(t["time_cell"])
+    t.loc[t["cell"] == cell_number_unassigned, "time_cell"] = pd.Timedelta("nat")
     return t
 
 


Test patch:
diff --git a/tobac/tests/test_tracking.py b/tobac/tests/test_tracking.py
index 1a60e2e..15a400b 100644
--- a/tobac/tests/test_tracking.py
+++ b/tobac/tests/test_tracking.py
@@ -362,3 +362,40 @@ def test_argument_logic():
         output = tobac.linking_trackpy(
             cell_1, None, 1, 1, d_min=None, d_max=None, v_max=None
         )
+
+
+def test_untracked_nat():
+    """
+    Tests to make sure that the untracked cells don't have timedelta assigned.
+    """
+    features = tobac.testing.generate_single_feature(
+        1,
+        1,
+        min_h1=0,
+        max_h1=101,
+        min_h2=0,
+        max_h2=101,
+        frame_start=0,
+        num_frames=2,
+        spd_h1=50,
+        spd_h2=50,
+    )
+
+    output = tobac.linking_trackpy(
+        features,
+        None,
+        1,
+        1,
+        d_max=40,
+        method_linking="random",
+        cell_number_unassigned=-1,
+        time_cell_min=2,
+    )
+
+    assert np.all(output["cell"].values == np.array([-1, -1]))
+    # NaT values cannot be compared, so instead we check for null values
+    # and check for the data type
+    assert np.all(pd.isnull(output["time_cell"]))
+    # the exact data type depends on architecture, so
+    # instead just check by name
+    assert output["time_cell"].dtype.name == "timedelta64[ns]"