Metadata¶
Metadata allows you to store arbitrary key-value data for every sample in your dataset. It can be viewed and filtered in the GUI or managed through the Python API. Use metadata to attach context such as:
- capture conditions (e.g. weather, lighting, camera settings),
- GPS coordinates or location information,
- camera or sensor identifiers,
- production line or machine IDs,
- data source or collection campaign,
- recording date, season, or time of day,
- customer, project, or site identifiers,
- or any custom attribute you want to track.
For embeddings, captions, annotations, predictions, or tags, the corresponding built-in concepts are better suited.
View and Filter Metadata in the GUI¶
In the main grid view, numeric metadata fields appear as range sliders in the left sidebar, alongside the built-in dimension filters. To filter by metadata values, drag the slider handles to narrow the visible samples to the desired range.
In the sample detail view, all metadata key-value pairs are listed in the side panel.
Note
Metadata in the GUI is read-only. To edit metadata, use the Python API described below.
Metadata in Python¶
Metadata on individual samples¶
Access and modify metadata on a Sample object using the metadata property, which behaves like a
dictionary:
# Assuming `dataset` is a lightly_studio dataset object
dataset = ...
for sample in dataset:
# Read metadata. Returns None for missing keys.
print(sample.metadata["temperature"])
# Set a single value
sample.metadata["temperature"] = 25.3
sample.metadata["location"] = "city"
sample.metadata["is_processed"] = True
sample.metadata["keywords"] = ["outdoor", "daytime"]
sample.metadata["extra"] = {"camera": "Canon", "lens": "50mm"}
# Update multiple key-value pairs at once
sample.metadata.update({"confidence": 0.95, "priority": 3})
Supported value types are string, integer, float, boolean, list, and dict.
Once a key is set with a given type, it is expected that values for other samples for that key use the same type.
Filter samples by metadata¶
Use ImageSampleField.metadata["<path>"] in a dataset query to select images by metadata values:
from lightly_studio.core.dataset_query import AND, ImageSampleField
query = dataset.match(
AND(
ImageSampleField.metadata["location"] == "city",
ImageSampleField.metadata["temperature"] >= 25,
)
)
samples = query.to_list()
For video datasets, use VideoSampleField.metadata instead of ImageSampleField.metadata.
Filters support string and boolean equality, numeric comparisons, and nested paths such as
extra.camera or keywords[0]. Missing values and values of a different JSON type do not
match, including with !=. See Metadata queries for
the supported operators, path syntax, and comparison rules.
Add metadata from external sources¶
You can bulk add metadata from any external source by providing it as a dictionary or CSV file. Use Dataset.update_metadata to write metadata for multiple samples in a single call. The two examples below show how to do this given a python dictionary (e.g. loaded from JSON) or a CSV file.
from typing import Any
import lightly_studio as ls
dataset = ls.ImageDataset.load()
# Provided by you. Keys must exactly match sample.file_name.
filename_to_metadata: dict[str, dict[str, Any]] = {
"img_001.jpg": {"weather": "sunny", "temperature": 28},
"img_002.jpg": {"weather": "rainy", "temperature": 15},
}
filename_to_sample_id = {sample.file_name: sample.sample_id for sample in dataset}
sample_metadata = []
for filename, metadata in filename_to_metadata.items():
sample_id = filename_to_sample_id.get(filename)
if sample_id is None:
raise ValueError(
f"File name {filename!r} not found in dataset. "
f"Keys must match sample.file_name exactly."
)
sample_metadata.append((sample_id, metadata))
dataset.update_metadata(sample_metadata)
import csv
from typing import Any
import lightly_studio as ls
dataset = ls.ImageDataset.load()
# CSV has columns: file_name,weather,temperature
# file_name must match sample.file_name exactly.
filename_to_metadata: dict[str, dict[str, Any]] = {}
with open("metadata.csv") as f:
for row in csv.DictReader(f):
filename_to_metadata[row["file_name"]] = {
"weather": row["weather"],
"temperature": float(row["temperature"]),
}
filename_to_sample_id = {sample.file_name: sample.sample_id for sample in dataset}
sample_metadata = []
for filename, metadata in filename_to_metadata.items():
sample_id = filename_to_sample_id.get(filename)
if sample_id is None:
raise ValueError(
f"File name {filename!r} not found in dataset. "
f"Keys must match sample.file_name exactly."
)
sample_metadata.append((sample_id, metadata))
dataset.update_metadata(sample_metadata)
Add metadata calculated from images¶
You can also iterate the dataset, open each image with PIL, compute derived statistics, and write them back in a single bulk call. The example below computes the mean luminance and the per-channel brightness ratio for every image.
import lightly_studio as ls
import tqdm
from PIL import Image, ImageStat
dataset = ls.ImageDataset.load()
sample_metadata = []
for sample in tqdm.tqdm(dataset):
with Image.open(sample.file_path_abs) as img:
rgb = img.convert("RGB")
channel_sums = ImageStat.Stat(rgb).sum # [sum_R, sum_G, sum_B]
luminance = ImageStat.Stat(rgb.convert("L")).mean[0]
total = sum(channel_sums) or 1 # avoid divide-by-zero on fully black images
sample_metadata.append(
(
sample.sample_id,
{
"luminance": luminance,
"red_ratio": channel_sums[0] / total,
"green_ratio": channel_sums[1] / total,
"blue_ratio": channel_sums[2] / total,
},
)
)
dataset.update_metadata(sample_metadata)
dataset.update_metadata merges values: new keys are created on demand, and existing keys are
overwritten by the incoming values. Metadata on other keys is left untouched.