• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

colour-science / colour-checker-detection / 18254503072

05 Oct 2025 05:29AM UTC coverage: 91.506% (-1.3%) from 92.814%
18254503072

push

github

KelSolaar
Implement support for *Templated* detection.

Co-authored By: Tim Walter <tim.michelbach@hotmail.com>

595 of 650 new or added lines in 15 files covered. (91.54%)

30 existing lines in 1 file now uncovered.

1142 of 1248 relevant lines covered (91.51%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.06
/colour_checker_detection/scripts/inference.py
1
#!/usr/bin/env python
2
"""
3
Colour Checker Detection - Inference
4
====================================
5

6
Define the scripts for colour checker detection using inference based on
7
*Ultralytics YOLOv8* machine learning model.
8

9
Warnings
10
--------
11
This script is provided under the terms of the
12
*GNU Affero General Public License v3.0* as it uses the *Ultralytics YOLOv8*
13
API which is incompatible with the *BSD-3-Clause*.
14
"""
15

16
from __future__ import annotations
1✔
17

18
import logging
1✔
19
import os
1✔
20
import typing
1✔
21

22
if typing.TYPE_CHECKING:
23
    from pathlib import Path
24

25
from time import perf_counter
1✔
26

27
import click
1✔
28
import cv2
1✔
29
import numpy as np
1✔
30
from colour import read_image
1✔
31

32
if typing.TYPE_CHECKING:
33
    from colour.hints import Any, List, Literal, NDArray, Tuple
34

35
from colour.io import convert_bit_depth
1✔
36

37
__author__ = "Colour Developers"
1✔
38
__copyright__ = "Copyright 2024 Colour Developers"
1✔
39
__license__ = (
1✔
40
    "GNU Affero General Public License v3.0 - "
41
    "https://www.gnu.org/licenses/agpl-3.0.en.html"
42
)
43
__maintainer__ = "Colour Developers"
1✔
44
__email__ = "colour-developers@colour-science.org"
1✔
45
__status__ = "Production"
1✔
46

47

48
__all__ = [
1✔
49
    "ROOT_REPOSITORY",
50
    "URL_BASE",
51
    "URL_MODEL_FILE_DEFAULT",
52
    "inference",
53
    "segmentation",
54
]
55

56

57
LOGGER = logging.getLogger(__name__)
1✔
58

59
ROOT_REPOSITORY: str = os.environ.get(
1✔
60
    "COLOUR_SCIENCE__COLOUR_CHECKER_DETECTION__REPOSITORY",
61
    os.path.join(
62
        os.path.expanduser("~"),
63
        ".colour-science",
64
        "colour-checker-detection",
65
    ),
66
)
67
"""Root of the local repository to download the hosted models to."""
1✔
68

69
URL_BASE: str = "https://huggingface.co/colour-science/colour-checker-detection-models"
1✔
70
"""URL of the remote repository to download the models from."""
1✔
71

72
URL_MODEL_FILE_DEFAULT: str = (
1✔
73
    f"{URL_BASE}/resolve/main/models/colour-checker-detection-l-seg.pt"
74
)
75
"""URL for the default segmentation model."""
1✔
76

77

78
def inference(
1✔
79
    source: str | Path | NDArray,
80
    model: YOLO,  # noqa: F821 # pyright: ignore
81
    show: bool = False,
82
    **kwargs: Any,
83
) -> List[Tuple[NDArray, NDArray, NDArray]]:
84
    """
85
    Run the inference on the provided source.
86

87
    Parameters
88
    ----------
89
    source
90
        Source of the image to make predictions on. Accepts all source types
91
        accepted by the *YOLOv8* model.
92
    model
93
        The model to use for the inference.
94
    show
95
        Whether to show the inference results on the image.
96

97
    Other Parameters
98
    ----------------
99
    \\**kwargs : dict, optional
100
        Keywords arguments for the *YOLOv8* segmentation method.
101

102
    Returns
103
    -------
104
    :class:`list`
105
        Inference results.
106
    """
107

UNCOV
108
    data = []
×
109

UNCOV
110
    for result in model(source, show=show, **kwargs):
×
UNCOV
111
        show and cv2.waitKey(0) == ord("n")
×
112

UNCOV
113
        if result.boxes is None:
×
114
            continue
×
115

UNCOV
116
        if result.masks is None:
×
117
            continue
×
118

UNCOV
119
        data_boxes = result.boxes.data
×
UNCOV
120
        data_masks = result.masks.data
×
121

UNCOV
122
        for i in range(data_boxes.shape[0]):
×
UNCOV
123
            data.append(
×
124
                (
125
                    data_boxes[i, 4].cpu().numpy(),
126
                    data_boxes[i, 5].cpu().numpy(),
127
                    data_masks[i].data.cpu().numpy(),
128
                )
129
            )
130

UNCOV
131
            if np.any(data[-1][-1]):
×
UNCOV
132
                LOGGER.debug(
×
133
                    'Found a "%s" class object with "%s" confidence.',
134
                    data[-1][1],
135
                    data[-1][0],
136
                )
137
            else:
138
                LOGGER.warning("No objects were detected!")
×
139

UNCOV
140
    return data
×
141

142

143
@click.command()
1✔
144
@click.option(
1✔
145
    "--input",
146
    required=True,
147
    type=click.Path(exists=True),
148
    help="Input file to run the segmentation model on.",
149
)
150
@click.option(
1✔
151
    "--output",
152
    help="Output file to write the segmentation results to.",
153
)
154
@click.option(
1✔
155
    "--model",
156
    "model",
157
    type=click.Path(exists=True),
158
    help='Segmentation model file to load. Default to the "colour-science" model '
159
    'hosted on "HuggingFace". It will be downloaded if not cached already.',
160
)
161
@click.option(
1✔
162
    "--show/--no-show",
163
    default=False,
164
    help="Whether to show the segmentation results.",
165
)
166
@click.option(
1✔
167
    "--logging-level",
168
    "logging_level",
169
    default="INFO",
170
    type=click.Choice(
171
        ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], case_sensitive=False
172
    ),
173
    help="Set the logging level.",
174
)
175
def segmentation(
1✔
176
    input: str,  # noqa: A002
177
    output: str | None = None,
178
    model: str | None = None,
179
    show: bool = False,
180
    logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
181
) -> NDArray:
182
    """
183
    Run the segmentation model on the given input file and save the results to
184
    given output file.
185

186
    Parameters
187
    ----------
188
    input
189
        Input file to run the segmentation model on.
190
    output
191
        Output file to write the segmentation results to.
192
    model
193
        Segmentation model file to load. Default to the *colour-science8 model
194
        hosted on *HuggingFace*. It will be downloaded if not cached already.
195
    show
196
        Whether to show the segmentation results.
197
    logging_level
198
        Set the logging level.
199

200
    Returns
201
    -------
202
    :class:`numpy.ndarray`
203
        Inference results.
204
    """
205

UNCOV
206
    from ultralytics import YOLO  # noqa: PLC0415
×
UNCOV
207
    from ultralytics.utils.downloads import download  # noqa: PLC0415
×
208

UNCOV
209
    time_start = perf_counter()
×
210

UNCOV
211
    logging.getLogger().setLevel(getattr(logging, logging_level.upper()))
×
212

UNCOV
213
    if model is None:
×
UNCOV
214
        model = os.path.join(ROOT_REPOSITORY, os.path.basename(URL_MODEL_FILE_DEFAULT))
×
UNCOV
215
        LOGGER.debug('Using "%s" default model.', model)
×
UNCOV
216
        if not os.path.exists(model):
×
UNCOV
217
            LOGGER.info('Downloading "%s" model...', URL_MODEL_FILE_DEFAULT)
×
UNCOV
218
            download(URL_MODEL_FILE_DEFAULT, ROOT_REPOSITORY)  # pyright: ignore
×
219

UNCOV
220
    if input.endswith((".npy", ".npz")):
×
221
        LOGGER.debug('Reading "%s" serialised array...', input)
×
222
        source = np.load(input)
×
223
    else:
UNCOV
224
        LOGGER.debug('Reading "%s" image...', input)
×
UNCOV
225
        source = convert_bit_depth(
×
226
            read_image(input)[..., :3],
227
            np.uint8.__name__,  # pyright: ignore
228
        )
229

230
    # NOTE: YOLOv8 expects "BGR" arrays.
UNCOV
231
    results = np.array(inference(source[..., ::-1], YOLO(model), show), dtype=object)
×
232

UNCOV
233
    if output is None:
×
234
        output = f"{input}.npz"
×
235

UNCOV
236
    np.savez(output, results=results)
×
237

UNCOV
238
    LOGGER.debug('Total segmentation time: "%s" seconds.', perf_counter() - time_start)
×
239

UNCOV
240
    return results
×
241

242

243
if __name__ == "__main__":
244
    logging.basicConfig()
245

246
    segmentation()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc