Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions docs/source/example_overview.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a230b00c14cc64e",
"metadata": {},
"outputs": [],
"metadata": {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you dete all outputs of notebooks before if you push it to the repo
Also, i think this file has nothing to do with your edits so you should take it out of your PR

"ExecuteTime": {
"end_time": "2026-01-29T14:11:59.229832Z",
"start_time": "2026-01-29T14:11:54.050642Z"
}
},
"source": [
"from pathlib import Path\n",
"\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from post_processing.dataclass.data_aplose import DataAplose"
]
],
"outputs": [],
"execution_count": 1
},
{
"cell_type": "markdown",
Expand All @@ -35,14 +40,34 @@
},
{
"cell_type": "code",
"execution_count": null,
"id": "c19ddde8bf965ee8",
"metadata": {},
"outputs": [],
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-29T14:13:45.480964Z",
"start_time": "2026-01-29T14:13:44.483047Z"
}
},
"source": [
"yaml_file = Path(r\"resource/APOCADO_yaml.yml\")\n",
"data = DataAplose.from_yaml(file=yaml_file)"
]
],
"outputs": [
{
"ename": "FileNotFoundError",
"evalue": "[Errno 2] No such file or directory: 'resource\\\\APOCADO_yaml.yml'",
"output_type": "error",
"traceback": [
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
"\u001B[31mFileNotFoundError\u001B[39m Traceback (most recent call last)",
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[2]\u001B[39m\u001B[32m, line 2\u001B[39m\n\u001B[32m 1\u001B[39m yaml_file = Path(\u001B[33mr\u001B[39m\u001B[33m\"\u001B[39m\u001B[33mresource/APOCADO_yaml.yml\u001B[39m\u001B[33m\"\u001B[39m)\n\u001B[32m----> \u001B[39m\u001B[32m2\u001B[39m data = \u001B[43mDataAplose\u001B[49m\u001B[43m.\u001B[49m\u001B[43mfrom_yaml\u001B[49m\u001B[43m(\u001B[49m\u001B[43mfile\u001B[49m\u001B[43m=\u001B[49m\u001B[43myaml_file\u001B[49m\u001B[43m)\u001B[49m\n",
"\u001B[36mFile \u001B[39m\u001B[32m~\\Documents\\Projets_Osmose\\Git\\post_processing_detections\\src\\post_processing\\dataclass\\data_aplose.py:483\u001B[39m, in \u001B[36mDataAplose.from_yaml\u001B[39m\u001B[34m(cls, file, concat)\u001B[39m\n\u001B[32m 460\u001B[39m \u001B[38;5;129m@classmethod\u001B[39m\n\u001B[32m 461\u001B[39m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34mfrom_yaml\u001B[39m(\n\u001B[32m 462\u001B[39m \u001B[38;5;28mcls\u001B[39m,\n\u001B[32m (...)\u001B[39m\u001B[32m 465\u001B[39m concat: \u001B[38;5;28mbool\u001B[39m = \u001B[38;5;28;01mTrue\u001B[39;00m,\n\u001B[32m 466\u001B[39m ) -> DataAplose | \u001B[38;5;28mlist\u001B[39m[DataAplose]:\n\u001B[32m 467\u001B[39m \u001B[38;5;250m \u001B[39m\u001B[33;03m\"\"\"Return a DataAplose object from a yaml file.\u001B[39;00m\n\u001B[32m 468\u001B[39m \n\u001B[32m 469\u001B[39m \u001B[33;03m Parameters\u001B[39;00m\n\u001B[32m (...)\u001B[39m\u001B[32m 481\u001B[39m \n\u001B[32m 482\u001B[39m \u001B[33;03m \"\"\"\u001B[39;00m\n\u001B[32m--> \u001B[39m\u001B[32m483\u001B[39m filters = \u001B[43mDetectionFilter\u001B[49m\u001B[43m.\u001B[49m\u001B[43mfrom_yaml\u001B[49m\u001B[43m(\u001B[49m\u001B[43mfile\u001B[49m\u001B[43m=\u001B[49m\u001B[43mfile\u001B[49m\u001B[43m)\u001B[49m\n\u001B[32m 484\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mcls\u001B[39m.from_filters(filters, concat=concat)\n",
"\u001B[36mFile \u001B[39m\u001B[32m~\\Documents\\Projets_Osmose\\Git\\post_processing_detections\\src\\post_processing\\dataclass\\detection_filter.py:71\u001B[39m, in \u001B[36mDetectionFilter.from_yaml\u001B[39m\u001B[34m(cls, file)\u001B[39m\n\u001B[32m 53\u001B[39m \u001B[38;5;129m@classmethod\u001B[39m\n\u001B[32m 54\u001B[39m \u001B[38;5;28;01mdef\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34mfrom_yaml\u001B[39m(\n\u001B[32m 55\u001B[39m \u001B[38;5;28mcls\u001B[39m,\n\u001B[32m 56\u001B[39m file: Path,\n\u001B[32m 57\u001B[39m ) -> DetectionFilter | \u001B[38;5;28mlist\u001B[39m[DetectionFilter]:\n\u001B[32m 58\u001B[39m \u001B[38;5;250m \u001B[39m\u001B[33;03m\"\"\"Return a DetectionFilter object from a YAML file.\u001B[39;00m\n\u001B[32m 59\u001B[39m \n\u001B[32m 60\u001B[39m \u001B[33;03m Parameters\u001B[39;00m\n\u001B[32m (...)\u001B[39m\u001B[32m 69\u001B[39m \n\u001B[32m 70\u001B[39m \u001B[33;03m \"\"\"\u001B[39;00m\n\u001B[32m---> \u001B[39m\u001B[32m71\u001B[39m \u001B[38;5;28;01mwith\u001B[39;00m \u001B[43mfile\u001B[49m\u001B[43m.\u001B[49m\u001B[43mopen\u001B[49m\u001B[43m(\u001B[49m\u001B[43mencoding\u001B[49m\u001B[43m=\u001B[49m\u001B[33;43m\"\u001B[39;49m\u001B[33;43mutf-8\u001B[39;49m\u001B[33;43m\"\u001B[39;49m\u001B[43m)\u001B[49m \u001B[38;5;28;01mas\u001B[39;00m yaml_file:\n\u001B[32m 72\u001B[39m parameters = yaml.safe_load(yaml_file)\n\u001B[32m 73\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mcls\u001B[39m.from_dict(parameters)\n",
"\u001B[36mFile \u001B[39m\u001B[32m~\\AppData\\Roaming\\uv\\python\\cpython-3.13.5-windows-x86_64-none\\Lib\\pathlib\\_local.py:537\u001B[39m, in \u001B[36mPath.open\u001B[39m\u001B[34m(self, mode, buffering, encoding, errors, newline)\u001B[39m\n\u001B[32m 535\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[33m\"\u001B[39m\u001B[33mb\u001B[39m\u001B[33m\"\u001B[39m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;129;01min\u001B[39;00m mode:\n\u001B[32m 536\u001B[39m encoding = io.text_encoding(encoding)\n\u001B[32m--> \u001B[39m\u001B[32m537\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mio\u001B[49m\u001B[43m.\u001B[49m\u001B[43mopen\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmode\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbuffering\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencoding\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43merrors\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mnewline\u001B[49m\u001B[43m)\u001B[49m\n",
"\u001B[31mFileNotFoundError\u001B[39m: [Errno 2] No such file or directory: 'resource\\\\APOCADO_yaml.yml'"
]
}
],
"execution_count": 2
},
{
"cell_type": "markdown",
Expand Down
4 changes: 2 additions & 2 deletions src/post_processing/dataclass/data_aplose.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)
from post_processing.utils.metrics_utils import detection_perf
from post_processing.utils.plot_utils import (
agreement,
plot_agreement,
heatmap,
histo,
overview,
Expand Down Expand Up @@ -442,7 +442,7 @@ def plot(

if mode == "agreement":
bin_size = kwargs.get("bin_size")
return agreement(df=df_filtered, bin_size=bin_size, ax=ax)
return plot_agreement(df=df_filtered, bin_size=bin_size, ax=ax)

if mode == "timeline":
color = kwargs.get("color")
Expand Down
54 changes: 39 additions & 15 deletions src/post_processing/utils/plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def histo(
offset = i * bar_width.total_seconds() / 86400

bar_kwargs = {
"width": bar_width.total_seconds() / 86400,
"width": (bar_width.total_seconds() / 86400),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why parenthesis here ?

"align": "edge",
"edgecolor": "black",
"color": color[i],
Expand Down Expand Up @@ -469,16 +469,11 @@ def wrap_text(text: str) -> str:
ax.set_xticklabels(new_labels, rotation=0)


def agreement(
def count_detections_within_timeframe(
df: DataFrame,
bin_size: Timedelta | BaseOffset,
ax: plt.Axes,
) -> None:
"""Compute and visualise agreement between two annotators.

This function compares annotation timestamps from two annotators over a time range.
It also fits and plots a linear regression line and displays the coefficient
of determination (R²) on the plot.
) -> DataFrame:
"""Counts the number of detections in df within bin_size timeframe.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""Count the number of detections in an APLOSE dataframe within bin_size time bin.```

Ruff D401: First line of docstring should be in imperative mood


Parameters
----------
Expand All @@ -489,8 +484,10 @@ def agreement(
bin_size : Timedelta | BaseOffset
The size of each time bin for aggregating annotation timestamps.

ax : matplotlib.axes.Axes
Matplotlib axes object where the scatterplot and regression line will be drawn.
Returns
-------
df_hist: Dataframe with columns = annotators and lines = number of detections
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

df_hist: DataFrame
    A  Dataframe with columns correposnding to the annotators
    and lines corresponding to the number of detections

within the timebin defined by bin_size

"""
labels, annotators = get_labels_and_annotators(df)
Expand All @@ -505,28 +502,55 @@ def agreement(
]

# scatter plot
n_annot_max = bin_size.total_seconds() / df["end_time"].iloc[0]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete this empty line

freq = (
bin_size if isinstance(bin_size, Timedelta) else str(bin_size.n) + bin_size.name
)

bins = date_range(
start=df["start_datetime"].min().floor(bin_size),
end=df["start_datetime"].max().ceil(bin_size),
end=df["end_datetime"].max().ceil(bin_size),
freq=freq,
)

df_hist = (
return (
DataFrame(
{
annotators[0]: histogram(datetimes[0], bins=bins)[0],
annotators[1]: histogram(datetimes[1], bins=bins)[0],
},
)
/ n_annot_max
)


def plot_agreement(
df: DataFrame,
bin_size: Timedelta | BaseOffset,
ax: plt.Axes,
) -> None:
"""Compute and visualise agreement between two annotators.

This function compares annotation timestamps from two annotators over a time range.
It also fits and plots a linear regression line and displays the coefficient
of determination (R²) on the plot.

Parameters
----------
df : DataFrame
APLOSE-formatted DataFrame.
It must contain The annotations of two annotators.

bin_size : Timedelta | BaseOffset
The size of each time bin for aggregating annotation timestamps.

ax : matplotlib.axes.Axes
Matplotlib axes object where the scatterplot and regression line will be drawn.



"""
labels, annotators = get_labels_and_annotators(df)
df_hist = count_detections_within_timeframe(df, bin_size)
scatterplot(data=df_hist, x=annotators[0], y=annotators[1], ax=ax)

coefficients = polyfit(df_hist[annotators[0]], df_hist[annotators[1]], 1)
Expand Down