Skip to content

Commit

Permalink
Tp/add mkdocs (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanpepinartefact authored Nov 23, 2023
1 parent 5708a9a commit 5b64484
Show file tree
Hide file tree
Showing 20 changed files with 458 additions and 114 deletions.
1 change: 0 additions & 1 deletion docs/code.md

This file was deleted.

148 changes: 148 additions & 0 deletions docs/custom_cost_selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Designing custom cost and selection functions

## Custom cost function

In our codebase, a cost function is utilized to quantify the dissimilarity between two objects, specifically instances of [TrackedObjects](reference/tracked_object.md). The cost function plays a pivotal role in the matching process within the [Matcher class](reference/matcher.md), where it computes a cost matrix. Each element in this matrix represents the cost of assigning a candidate to a switcher. For a deeper understanding of cost functions, please refer to the [related documentation](reference/cost_functions.md).

When initializing the [ReidProcessor](reference/reid_processor.md), you have the option to provide a custom cost function. The requirements for designing one are as follows:

- The cost function must accept 2 [TrackedObjects](reference/tracked_object.md) instances: a candidate (a new object that appears and can potentially be matched), and a switcher (an object that has been lost and can potentially be re-matched).
- All the [metadata](reference/tracked_object_metadata.md) of each [TrackedObject](reference/tracked_object.md) can be utilized to compute a cost.
- If additional metadata is required, you should modify the [metadata](reference/tracked_object_metadata.md) class accordingly. Please refer to the [developer quickstart documentation](quickstart_dev.md) if needed.

Here is an example of an Intersection over Union (IoU) distance function that you can use:

```python
def bounding_box_iou_distance(candidate: TrackedObject, switcher: TrackedObject) -> float:
"""
Calculates the Intersection over Union (IoU) between the bounding boxes of two TrackedObjects.
This measure is used as a measure of similarity between the two objects, with a higher IoU
indicating a higher likelihood of the objects being the same.
Args:
candidate (TrackedObject): The first TrackedObject.
switcher (TrackedObject): The second TrackedObject.
Returns:
float: The IoU between the bounding boxes of the two TrackedObjects.
"""
# Get the bounding boxes from the Metadata of each TrackedObject
bbox1 = candidate.metadata.bbox
bbox2 = switcher.metadata.bbox

# Calculate the intersection of the bounding boxes
x1 = max(bbox1[0], bbox2[0])
y1 = max(bbox1[1], bbox2[1])
x2 = min(bbox1[2], bbox2[2])
y2 = min(bbox1[3], bbox2[3])

# If the bounding boxes do not overlap, return 0
if x2 < x1 or y2 < y1:
return 0.0

# Calculate the area of the intersection
intersection_area = (x2 - x1) * (y2 - y1)

# Calculate the area of each bounding box
bbox1_area = (bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])
bbox2_area = (bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])

# Calculate the IoU
iou = intersection_area / float(bbox1_area + bbox2_area - intersection_area)

return 1 - iou

```

Next, pass this function during the initialization of your [ReidProcessor](reference/reid_processor.md):

```python
reid_processor = ReidProcessor(cost_function_threshold=0.3,
cost_function = bounding_box_iou_distance,
filter_confidence_threshold=...,
filter_time_threshold=...,
max_attempt_to_match=...,
max_frames_to_rematch=...,
save_to_txt=True,
file_path="your_file.txt")
```

In this case, candidates and switchers with bounding boxes will be matched if their IoU is below 0.7. Among possible matches, the two bounding boxes with the lowest cost (i.e., larger IoU) will be matched. You can use all the available metadata. For instance, here is an example of a cost function based on the difference in confidence:

```python
def confidence_difference(candidate: TrackedObject, switcher: TrackedObject) -> float:
"""
Calculates the absolute difference between the confidence values of two TrackedObjects.
This measure is used as a measure of dissimilarity between the two objects, with a smaller difference
indicating a higher likelihood of the objects being the same.
Args:
candidate (TrackedObject): The first TrackedObject.
switcher (TrackedObject): The second TrackedObject.
Returns:
float: The absolute difference between the confidence values of the two TrackedObjects.
"""
# Get the confidence values from the Metadata of each TrackedObject
confidence1 = candidate.metadata.confidence
confidence2 = switcher.metadata.confidence

# Calculate the absolute difference between the confidence values
difference = abs(confidence1 - confidence2)

return difference

```

Then, pass this function during the initialization of your [ReidProcessor](reference/reid_processor.md):

```python
reid_processor = ReidProcessor(cost_function_threshold=0.1,
cost_function = confidence_difference,
filter_confidence_threshold=...,
filter_time_threshold=...,
max_attempt_to_match=...,
max_frames_to_rematch=...,
save_to_txt=True,
file_path="your_file.txt")
```

In this case, candidates and switchers will be matched if their confidence is similar, with a threshold acceptance of 0.1. Among possible matches, the two objects with the lowest cost (i.e., lower confidence difference) will be matched.

## Custom Selection function

In the codebase, a selection function is used to determine whether two objects, specifically [TrackedObjects](reference/tracked_object.md) instances, should be considered for matching. The selection function is a key part of the matching process in the [Matcher class](reference/matcher.md). For a deeper understanding of selection functions, please refer to the [related documentation](reference/selection_functions.md).

Here is an example of a selection function per zone that you can use:

```python

# Define the area of interest, [x_min, y_min, x_max, y_max]
AREA_OF_INTEREST = [0, 0, 500, 500]

def select_by_area(candidate: TrackedObject, switcher: TrackedObject) -> int:

# Check if both objects are inside the area of interest
if (candidate.bbox[0] > AREA_OF_INTEREST[0] and candidate.bbox[1] > AREA_OF_INTEREST[1] and
candidate.bbox[0] + candidate.bbox[2] < AREA_OF_INTEREST[2] and candidate.bbox[1] + candidate.bbox[3] < AREA_OF_INTEREST[3] and
switcher.bbox[0] > AREA_OF_INTEREST[0] and switcher.bbox[1] > AREA_OF_INTEREST[1] and
switcher.bbox[0] + switcher.bbox[2] < AREA_OF_INTEREST[2] and switcher.bbox[1] + switcher.bbox[3] < AREA_OF_INTEREST[3]):
return 1
else:
return 0

```

Then, pass this function during the initialization of your [ReidProcessor](reference/reid_processor.md):

```python
reid_processor = ReidProcessor(selection_function = select_by_area,
filter_confidence_threshold=...,
filter_time_threshold=...,
max_attempt_to_match=...,
max_frames_to_rematch=...,
save_to_txt=True,
file_path="your_file.txt")
```

In this case, candidates and switchers will be considerated for matching if they belong to the same zone. You can of course combine selection functions, for instance to selection only switchers and candidates that belong to the same area and belong to the same category.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Welcome to the documentation!
# Welcome to the documentation

For more information, make sure to check the [Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/getting-started/)
57 changes: 57 additions & 0 deletions docs/quickstart_dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Quickstart developers

## Installation

First, clone the repository to your local machine:

```bash
git clone https://github.com/artefactory-fr/track-reid.git
```

Then, navigate to the project directory:

```bash
cd track-reid
```

To install the necessary dependencies, we use Poetry. If you don't have Poetry installed, you can download it using the following command:

```bash
curl -sSL https://install.python-poetry.org | python3 -
```

Now, you can install the dependencies:

```bash
make install
```

This will create a virtual environment and install the necessary dependencies.
To activate the virtual environment in your terminal, you can use the following command:

```bash
poetry shell
```

You can also update the requirements using the following command:

```bash
make update-requirements
```

Then, you are ready to go !
For more detailed information, please refer to the `Makefile`.

## Tests

In this project, we have designed both integration tests and unit tests. These tests are located in the `tests` directory of the project.

Integration tests are designed to test the interaction between different parts of the system, ensuring that they work together as expected. Those tests can be found in the `tests/integration_tests` directory of the project.

Unit tests, on the other hand, are designed to test individual components of the system in isolation. We provided a bench of unit tests to test key functions of the project, those can be found in `tests/unit_tests`.

To run all tests, you can use the following command:

```bash
make run_tests
```
117 changes: 117 additions & 0 deletions docs/quickstart_user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Using the ReidProcessor

The `ReidProcessor` is the entry point of the `track-reid` library. It is used to process and reconcile tracking data, ensuring consistent and accurate tracking of objects over time. Here's a step-by-step guide on how to use it:

## Step 1: Understand the Usage

The reidentification process is applied to tracking results, which are derived from the application of a tracking algorithm on detection results for successive frames of a video. This reidentification process is applied iteratively on each tracking result, updating its internal states during the process.

The `ReidProcessor` needs to be updated with the tracking results for each frame of your
sequence or video. This is done by calling the `update` method that takes 2 arguments:

- `frame_id`: an integer specifying the current frame of the video
- `tracker_output`: a numpy array containing the tracking results for the current frame

## Step 2: Understand the Data Format Requirements

The `ReidProcessor` update function requires a numpy array of tracking results for the current frame as input. This data must meet specific criteria regarding data type and structure.

All input data must be numeric, either integers or floats.
Here's an example of the expected input data format based on the schema:

| bbox (0-3) | object_id (4) | category (5) | confidence (6) |
|-----------------|---------------|--------------|----------------|
| 50, 60, 120, 80 | 1 | 1 | 0.91 |
| 50, 60, 120, 80 | 2 | 0 | 0.54 |

Each row corresponds to a tracked object.

- The first four columns denote the **bounding box coordinates** in the format (x, y, width, height),
where x and y are the top left coordinates of the bounding box. These coordinates can be either normalized or in pixel units.
These values remain unchanged during the reidentification process.
- The fifth column is the **object ID** assigned by the tracker, which may be adjusted during the reidentification process.
- The sixth column indicates the **category** of the detected object, which may also be adjusted during the reidentification process.
- The seventh column is the confidence score of the detection, which is not modified by the reidentification process.

For additional information, you can utilize `ReidProcessor.print_input_data_requirements()`.

Here's a reformatted example of how the output data should appear, based on the schema:

| frame_id (0) | object_id (1) | category (2) | bbox (3-6) | confidence (7) | mean_confidence (8) | tracker_id (9) |
|--------------|---------------|--------------|-----------------|----------------|---------------------|----------------|
| 1 | 1 | 1 | 50, 60, 120, 80 | 0.91 | 0.85 | 1 |
| 2 | 2 | 0 | 50, 60, 120, 80 | 0.54 | 0.60 | 2 |

- The first column represents the **frame identifier**, indicating the frame for which the result is applicable.
- The second column is the **object ID** assigned by the reidentification process.
- The third column is the **category** of the detected object, which may be adjusted during the reidentification process.
- The next four columns represent the **bounding box coordinates**, which remain unchanged from the input data.
- The seventh column is the **confidence** of the object, which also remains unchanged from the input data.
- The eighth column indicates the **average confidence** of the detected object over its lifetime, from the beginning of the tracking to the current frame.
- The final column is the **object ID assigned by the tracking algorithm**, before the reidentification process.

You can use `ReidProcessor.print_output_data_format_information()` for more insight.

## Step 3: Understand Necessary Modules

To make ReidProcessor work, several modules are necessary:

- `TrackedObject`: This class represents a tracked object. It is used within the Matcher and ReidProcessor classes.
- `TrackedObjectMetadata`: This class is attached to a tracked object and represents informations and properties about the object.
- `TrackedObjectFilter`: This class is used to filter tracked objects based on certain criteria. It is used within the ReidProcessor class.
- `Matcher`: This class is used to match tracked objects based on a cost function and a selection function. It is initialized within the ReidProcessor class.

The cost and selection functions are key components of the ReidProcessor, as they will drive the matching process between lost objects and new objects during the video. Those two functions are fully customizable and can be passed as arguments of the ReidProcessor at initialization. They both take 2 `TrackedObjects` as inputs, and perform computation based on their metadatas.

- **cost function**: This function calculates the cost of matching two objects. It takes two TrackedObject instances as input and returns a numerical value representing the cost of matching these two objects. A lower cost indicates a higher likelihood of a match. The default cost function is `bounding_box_distance`.

- **selection_function**: This function determines whether two objects should be considered for matching. It takes two TrackedObject instances as input and returns a binary value (0 or 1). A return value of 1 indicates that the pair should be considered for matching, while a return value of 0 indicates that the pair should not be considered. The default selection function is `select_by_category`.

In summary, prior to the matching process, filtering on which objects should be considerated is applied thought the `TrackedObjectFilter`. All objects are represented by the `TrackedObject` class, with its attached metadata represented by `TrackedObjectMetadata`. The `ReidProcessor` then uses the `Matcher` class with a cost function and selection function to match objects.

## Step 4: Initialize ReidProcessor

If you do not want to provide custom cost and selection function, here is an example of ReidProcessor initialization:

```python
reid_processor = ReidProcessor(filter_confidence_threshold=0.1,
filter_time_threshold=5,
cost_function_threshold=5000,
max_attempt_to_match=5,
max_frames_to_rematch=500,
save_to_txt=True,
file_path="your_file.txt")
```

Here is a brief explanation of each argument in the ReidProcessor function, and how you can monitor the `Matcher` and the `TrackedObjectFilter` behaviours:

- `filter_confidence_threshold`: Float value that sets the **minimum average confidence level** for a tracked object to be considered valid. Tracked objects with average confidence levels below this threshold will be ignored.

- `filter_time_threshold`: Integer that sets the **minimum number of frames** a tracked object must be seen with the same id to be considered valid. Tracked objects seen less frames that this threshold will be ignored.

- `cost_function_threshold`: This is a float value that sets the **maximum cost for a match** between a detection and a track. If the cost of matching a detection to a track exceeds this threshold, the match will not be made. Set to None for no limitation.

- `max_attempt_to_match`: This is an integer that sets the **maximum number of attempts to match a tracked object never seen before** to a lost tracked object. If this tracked object never seen before can't be matched within this number of attempts, it will be considered a new stable tracked object.

- `max_frames_to_rematch`: This is an integer that sets the **maximum number of frames to try to rematch a tracked object that has been lost**. If a lost object can't be rematch within this number of frames, it will be considered as lost forever.

- `save_to_txt`: This is a boolean value that determines whether the tracking results should be saved to a text file. If set to True, the results will be saved to a text file.

- `file_path`: This is a string that specifies the path to the text file where the tracking results will be saved. This argument is only relevant if save_to_txt is set to True.

For more information on how to design custom cost and selection functions, refer to [this guide](custom_cost_selection.md).

## Step 5: Run reidentifiaction process

Lets say you have a `dataset` iterable object, composed for each iteartion of a frame id and its associated tracking results. You can call the `ReidProcessor` update class using the following:

```python
for frame_id, tracker_output in dataset:
corrected_results = reid_processor.update(frame_id = frame_id, tracker_output=tracker_output)
```

At the end of the for loop, information about the correction can be retrieved using the `ReidProcessor` properties. For instance, the list of tracked object can be accessed using:

```python
reid_processor.seen_objects()
```
15 changes: 15 additions & 0 deletions docs/reference/cost_functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Cost functions

In the codebase, a cost function is used to measure the dissimilarity between two objects, specifically [TrackedObjects](tracked_object.md) instances. The cost function is a crucial part of the matching process in the [Matcher class](matcher.md). It calculates a cost matrix, where each element represents the cost of assigning a candidate to a switcher.

The cost function affects the behavior of the matching process in the following ways:

1. **Determining Matches**: The cost function is used to determine the best matches between candidates and switchers. The lower the cost, the higher the likelihood that two objects are the same.

2. **Influencing Match Quality**: The choice of cost function can greatly influence the quality of the matches. For example, a cost function that calculates the Euclidean distance between the centers of bounding boxes might be more suitable for tracking objects in a video, while a cost function that calculates the absolute difference between confidence values might be more suitable for matching objects based on their detection confidence.

3. **Setting Match Thresholds**: The cost function also plays a role in setting thresholds for matches. In the [Matcher class](matcher.md), if the cost exceeds a certain threshold, the match is discarded.

You can provide a custom cost function to the reidentification process. For more information, please refer to [this documentation](../custom_cost_selection.md).

:::trackreid.cost_functions
Loading

0 comments on commit 5b64484

Please sign in to comment.