Skip to the content.

read_image_patch_colors

Reference and Usage Guide

Version: 1.4
Author: Knut Larsson
Purpose: Generate ArgyllCMS compatible .ti1 and .ti2 files from a grid of color patches in an image.

Table of Contents

Overview

This program extracts color values from a rectangular grid of color patches in an image, computes colorimetric values (RGB percentages, XYZ, Lab), applies row/column labeling rules, and writes three output files:

It supports sampling in mean or median mode, configurable patch geometry, numeric or alphabetic labeling patterns, and RGB/XYZ/Lab output combinations.

The script is designed for use in color-management workflows where printed color targets must be scanned or photographed and converted into Argyll measurement files.

Example use cases:

  1. Using the image of a reference target, create a .ti1 file so thatn one may use ArgyllCMS printtarg command and generate a target using the colors of the image, which then can be used to create a printer profile.

  2. Using the image of a reference target, create a .ti2 file so that one may print the target, scan it, and then use ArgyllCMS scanin command to create a printer profile.

Examples Included

See chapter Examples for detailed examples on how to read patches from different size patch grid images. Most examples are based on Datacolor SpyderPrint Targets, which are:

Target images used for creating .ti1, .ti2 and .csv files are under folder Example Targets Read.

One example of generating an ArgyllCMS printtarg target for SpyderPrint High Quality Target (225-patches) is also included.

Command-line Arguments

Argument Description
--image / -i (Required) Path to the input image containing the colour-patch grid. Image must be a display-referred, D65-based RGB image encoded using a 2.2 gamma transfer curve (typical scanned/photographed targets).
--image_color_space Input image device colour space. Determines RGB→XYZ conversion matrix. Options:
srgb (default)
adobergb
--patch_first_xy (Required) "X,Y" coordinates of the centre of the first patch (top-left). Accepts integers or floats.
--patch_last_xy (Required) "X,Y" coordinates of the centre of the last patch (bottom-right). Accepts integers or floats.
--patch_width_height_ratio (Required) Width ÷ Height ratio (W/H) of a single patch.
Examples: 1.0 (square), 1.378, etc.
--num_cols (Required) Number of columns in the grid.
--num_rows (Required) Number of rows in the grid.
--sample_fraction Fraction of patch area to sample (float). Default: 0.20 (20%).
Constraints: 0 < f ≤ 0.6.
The sampling area is a centered square clamped to at least 3×3 px and at most 60% of patch size.
--row_labels (Required) Label sequence for rows. Must match num_rows exactly.
Allowed patterns:
Numeric ranges: 1-15, 03-12, 0001-0120 (zero-padding preserved)
Alphabetic ranges: A-Z, A-AC, BQ-CF, up to ZZ
Note: Rows and columns must use different types (numeric ↔ alphabetic).
--col_labels (Required) Label sequence for columns (same rules as row_labels).
--patch_label_order (Required) Determines patch label composition:
col_then_row → e.g. A12
row_then_col → e.g. 12A
--output_color_space (Required) Comma-separated list specifying which colour spaces to output (in order). Allowed tokens:
RGB
XYZ
LAB
Rules:
• TI1 can include only RGB and/or XYZ
• TI2/CSV include all listed tokens
Examples:
RGB,XYZ → TI1: RGB,XYZ; TI2: RGB,XYZ.
XYZ,LAB,RGB → TI1: XYZ,RGB; TI2: XYZ,LAB,RGB.
--sample_mode Patch sampling aggregation method:
mean – arithmetic mean (not robust)
median – robust, but biases asymmetric patches
mad (default) – robust mean via MAD-based sigma clipping
--output_order Determines initial traversal order before rotation/mirroring, if applied:
row_major (default): iterate row by row (top→bottom, left→right)
column_major: iterate column by column (left→right, top→bottom)
Applied first, before rotate_grid and mirror_output.
--mirror_output Reverse traversal after output_order and rotate_grid:
• If row_major: reverse column labels for each row (vertical flip).
• If column_major: reverse row labels for each column (horizontal flip).
--rotate_grid Rotate the entire patch grid clockwise before mirroring (if mirror_output applied). Allowed values:
0 (default)
90 – rotate 90° CW
180 – rotate 180°
270 – rotate 270° CW
Applied after output_order, before mirror_output.
--output Base filename for generated .ti1, .ti2, and .csv output files.
If omitted, filenames are based on the input image name.
--debug Enable diagnostic printing for the first few patches. Shows sampling geometry, pixel statistics, and intermediate RGB calculations.

Image Input

Accepted image types: any PIL-compatible raster file Color depth: handled as 16-bit RGB internally Color space:

Parameter: --image / -i Provides the path to the patch-grid image.

Grid Geometry

Geometry is computed from:

The script calculates patch center coordinates and boundaries.

Sampling

Parameter: --sample_fraction Range: 0 < f ≤ 0.6 Defines fraction of the smaller patch dimension used for sampling.

Sampling modes (parameter --sample_mode):

Sampling region is square, centered in each patch.

Labeling Rules

Row and column labels come from:

Accepted patterns:

Label order:

Label counts must match grid dimensions exactly.

Color Processing

Sampled RGB → RGB percentage (0–100): RGB_percent = (R_16bit / 65535) * 100

Conversions use python-colormath according to selected color space (“srgb” / “adobergb”).

The patch with the highest XYZ_Y is stored as APPROX_WHITE_POINT.

XYZ output: • CIE 1931 2° • Chromatically adapted to D50 • White scaled to Y=100

Lab output: • CIE Lab (1976) • D50 reference

Output Files

Three files:

1. TI1 (.ti1)

Contains: header, APPROX_WHITE_POINT, COLOR_REP (iRGB), expected-values flag, fields, and patch data.

2. TI2 (.ti2)

Contains: header, APPROX_WHITE_POINT, strip/patch index patterns, STEPS_IN_PASS, PASSES_IN_STRIPS2, index order, and full color data including Lab.

SAMPLE_LOC uses labels like “A1”, “D14”, etc.

3. CSV (.csv)

Space-separated table with SAMPLE_ID, SAMPLE_LOC, and selected color values.

Patch Value Ranges

RGB: 0–100 XYZ: typically 0–100 Lab_L: 0–100 Lab_a/b: approx. –128 to +128

All values have six decimal places.

Valid Color Blocks

--output_color_space must include at least one of: RGB, XYZ, LAB

Example: --output_color_space RGB,XYZ,LAB

Order controls column order in TI1/TI2/CSV.

Dependencies

Notes on Accuracy

Patch Sampling Method

  1. Compute patch center via interpolation.
  2. Define sampling region size (min 3px, max 60% patch).
  3. Extract pixels.
  4. Average RGB in 16-bit domain.
  5. Convert using colormath (RGB→XYZ D50 → Lab D50).

Output File Format (TI2)

CTI2

DESCRIPTOR "Argyll Calibration Target chart information 2"
ORIGINATOR "read_image_patch_colors.py"
CREATED "<timestamp>"
ACCURATE_EXPECTED_VALUES "true"
APPROX_WHITE_POINT "<Xw Yw Zw>"
COLOR_REP "iRGB"
STEPS_IN_PASS "<ROWS>"
PASSES_IN_STRIPS2 "<COLS>"
STRIP_INDEX_PATTERN "<row_pattern>"
PATCH_INDEX_PATTERN "<column_pattern>"
INDEX_ORDER "STRIP_THEN_PATCH"

NUMBER_OF_FIELDS <AAA>
BEGIN_DATA_FORMAT
SAMPLE_ID SAMPLE_LOC [colour fields...]
END_DATA_FORMAT

NUMBER_OF_SETS <BBB>
BEGIN_DATA
  <patch_index> "<label>" <RGB/XYZ/Lab values...>
  ...
END_DATA

Fields appear in the order defined by output_color_space.

Examples:

Error Conditions

Script stops if:

Workflow Summary

  1. Validate dependencies
  2. Parse CLI args
  3. Load image
  4. Compute grid geometry
  5. Generate labels
  6. Sample patches
  7. Convert RGB→XYZ→Lab
  8. Compute white/black points
  9. Generate metadata
  10. Write TI1/TI2/CSV

Examples

9×12 SpyderPrint target with numeric rows, alphabetic columns

python3 read_image_patch_colors.py \
  --image "EZ 729 Colors Plus Grays 1 of 9 (108-patches).tif" \
  --patch_first_xy 111,64 \
  --patch_last_xy 1039,746 \
  --patch_width_height_ratio 1.89286 \
  --num_cols 9 \
  --num_rows 12 \
  --row_labels 1-12 \
  --col_labels A-I \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

10×12 SpyderPrint target with numeric rows, alphabetic columns

python3 read_image_patch_colors.py \
  --image "EZ 729 Colors Plus Grays 8 of 9 (120-patches).tif" \
  --patch_first_xy 87,67 \
  --patch_last_xy 1023,749 \
  --patch_width_height_ratio 1.7142857 \
  --num_cols 10 \
  --num_rows 12 \
  --row_labels 1-12 \
  --col_labels A-J \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

15×15 SpyderPrint target with numeric rows, alphabetic columns

python3 read_image_patch_colors.py \
  --image "High Quality Target (225-patches).tif" \
  --patch_first_xy 61,50 \
  --patch_last_xy 848,612 \
  --patch_width_height_ratio 1.37837838 \
  --num_cols 15 \
  --num_rows 15 \
  --row_labels 1-15 \
  --col_labels A-O \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

17×14 SpyderPrint target with numeric rows, alphabetic columns

python3 read_image_patch_colors.py \
  --image "Expert Target Plus Grays 4 of 4 (238-patches).tif" \
  --patch_first_xy 56,51 \
  --patch_last_xy 847,592 \
  --patch_width_height_ratio 1.09803921568 \
  --num_cols 17 \
  --num_rows 14 \
  --row_labels 1-14 \
  --col_labels A-Q \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

18×14 SpyderPrint target with numeric rows, alphabetic columns

Note!
This script requires specification of first and last patch of a full grid. Thus, last 9 patches of last row must be manually removed from files as they do not exist in image.

python3 read_image_patch_colors.py \
  --image "Expert Target Page 1 of 3 (243-patches).tif" \
  --patch_first_xy 48,45 \
  --patch_last_xy 872,604 \
  --patch_width_height_ratio 1.075 \
  --num_cols 18 \
  --num_rows 14 \
  --row_labels 1-14 \
  --col_labels A-R \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

27×27 SpyderPrint target with numeric rows, alphabetic columns

Note!
This image uses a special character on last column label, which must be manually changed in the ti2 file.

python3 read_image_patch_colors.py \
  --image "Expert Target (large)(729-patches).tif" \
  --patch_first_xy 56,38 \
  --patch_last_xy 1234,921 \
  --patch_width_height_ratio 1.4 \
  --num_cols 27 \
  --num_rows 27 \
  --row_labels 1-27 \
  --col_labels A-AA \
  --patch_label_order col_then_row \
  --output_color_space RGB,XYZ

36×26 LaserSoft target with numeric rows, alphabetic columns

python3 read_image_patch_colors.py \
  --image "LaserSoft_Advanced_Target-display.tif" \
  --patch_first_xy 168,168 \
  --patch_last_xy 1335,935 \
  --patch_width_height_ratio 1.0 \
  --num_cols 36 \
  --num_rows 24 \
  --row_labels A-X \
  --col_labels 1-36 \
  --patch_label_order row_then_col \
  --output_color_space RGB,XYZ