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

colour-science / colour-checker-detection / 17929889887

22 Sep 2025 10:11PM UTC coverage: 88.323% (-4.5%) from 92.814%
17929889887

Pull #64

github

web-flow
[pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/astral-sh/ruff-pre-commit: v0.12.4 → v0.13.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.4...v0.13.1)
- [github.com/adamchainz/blacken-docs: 1.19.1 → 1.20.0](https://github.com/adamchainz/blacken-docs/compare/1.19.1...1.20.0)
Pull Request #64: [pre-commit.ci] pre-commit autoupdate

590 of 668 relevant lines covered (88.32%)

0.88 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

108
    data = []
×
109

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

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

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

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

122
        for i in range(data_boxes.shape[0]):
×
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

131
            if np.any(data[-1][-1]):
×
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

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

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

209
    time_start = perf_counter()
×
210

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

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

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

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

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

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

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

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