Reference
Contents
Index
ImageTally.CSV_HEADERImageTally.CSV_HEADER_SCALEDImageTally.LEGACY_SESSION_FORMAT_VERSIONImageTally.MICRO_SIGNImageTally.SESSION_FORMAT_VERSIONImageTally.UNIT_ALIASESImageTally.VALID_MARKERSImageTally.VALID_UNITSImageTally.CountPointImageTally.CountSessionImageTally.TagImageTally.add_point!ImageTally.add_tag!ImageTally.check_image_file_sizeImageTally.clamp_to_imageImageTally.clear_scale!ImageTally.count_by_tagImageTally.csv_escapeImageTally.csv_rowImageTally.csv_row_scaledImageTally.default_tagsImageTally.delete_point!ImageTally.export_csvImageTally.find_nearest_pointImageTally.get_tagImageTally.has_tagImageTally.launch_counterImageTally.load_sessionImageTally.move_point!ImageTally.new_sessionImageTally.normalize_unitImageTally.parse_scale_pointImageTally.pixel_to_relativeImageTally.pixels_to_realImageTally.real_to_pixelsImageTally.relative_to_pixelImageTally.remove_tag!ImageTally.require_keyImageTally.require_scaleImageTally.save_sessionImageTally.scale_fieldsImageTally.scale_pixel_distanceImageTally.session_summaryImageTally.set_active_tag!ImageTally.set_marker_size!ImageTally.set_scale!ImageTally.stored_next_idImageTally.total_count
ImageTally.CSV_HEADER — Constant
Header written for a session with no scale calibration. Unchanged since ImageTally v0.1.0.
ImageTally.CSV_HEADER_SCALED — Constant
Header written for a calibrated session: three columns inserted before timestamp, leaving the preceding columns in their existing positions.
The unit is a column rather than a suffix on the coordinate names (x_mm) because a unit is free-form — normalize_unit stores an unrecognised unit as given, and "grid squares" would make a hostile column name. As data the unit also survives any parse, and pooling rows exported from differently calibrated images groups by unit instead of silently mixing dimensions.
ImageTally.LEGACY_SESSION_FORMAT_VERSION — Constant
Format version assumed when a session file has no format_version key. Every ImageTally v0.1.0 session file falls in this category.
ImageTally.MICRO_SIGN — Constant
The MICRO SIGN (U+00B5) and the GREEK SMALL LETTER MU (U+03BC) render identically but are different codepoints. Unicode NFKC normalisation maps the former onto the latter, so U+03BC is the canonical form ImageTally stores.
Written as escapes on purpose: the two characters are indistinguishable on screen, so literals here would be unreviewable and one stray copy-paste away from being wrong.
ImageTally.SESSION_FORMAT_VERSION — Constant
Session file format version written by this version of ImageTally.
Version history:
1— implicit; written by ImageTally v0.1.0, which had noformat_versionkey at all. Absence of the key means version 1.2— adds theformat_versionandimage_file_sizekeys.3— adds the optional[scale]table and the top-levelnext_idkey. Both are read with a default, so version 1 and 2 files load unchanged.
load_session accepts any version up to this one and rejects anything higher, so a file written by a newer ImageTally fails loudly instead of loading with fields silently dropped.
ImageTally.UNIT_ALIASES — Constant
Aliases accepted for the canonical units in VALID_UNITS, looked up in lower case. The canonical units appear as their own aliases so that case variants ("MM", "Micron") normalise too.
ImageTally.VALID_MARKERS — Constant
Valid GLMakie marker symbols accepted by ImageTally.
ImageTally.VALID_UNITS — Constant
Canonical length units recognised by ImageTally's scale calibration.
The micro prefix is U+03BC (GREEK SMALL LETTER MU), not the visually identical U+00B5 (MICRO SIGN). Unicode NFKC normalisation maps U+00B5 onto U+03BC, so U+03BC is the canonical form; normalize_unit rewrites the other one.
Units outside this set are accepted with a warning — see normalize_unit — so unusual reference objects (a grid square, a body length) are not blocked.
ImageTally.CountPoint — Type
CountPointRepresents a single counted object in the image.
Fields
id::Int: Unique identifierx::Float64: Relative x position (0.0 to 1.0)y::Float64: Relative y position (0.0 to 1.0)tag::String: Name of the associated Tag- `timestamp::DateTime
ImageTally.CountSession — Type
CountSession(; image_path, image_width, image_height, active_tag, kwargs...)Holds all state for a single counting session.
Construct with keywords. image_path, image_width, image_height, and active_tag are required; the rest default. Requiring those four means there is no zero-argument constructor, so a session cannot be built without the information needed to make it internally consistent. The positional constructor is also available, but prefer the keyword form — it survives new fields being added.
Field values are not validated here. Validation lives at the entry points (new_session, load_session, and the mutating helpers), which is where the context to produce a good error message exists.
Required keywords
image_path::String: Path to the image fileimage_width::Int: Original image width in pixelsimage_height::Int: Original image height in pixelsactive_tag::String: Currently selected tag name
Optional keywords
tags::Vector{Tag} = default_tags(): Available counting categoriespoints::Vector{CountPoint} = CountPoint[]: All counted pointsnext_id::Int = 1: Counter for generating unique point IDsmarker_size::Float64 = DEFAULT_MARKER_SIZE: Display size of markers
Scale calibration keywords
A session with has_scale = false — the default — carries no calibration and behaves exactly as a session did before calibration existed.
has_scale::Bool = false: Whether a scale calibration is set. This is the authoritative flag; the other scale fields are meaningless when it isfalse.scale_real_distance::Float64 = 0.0: Real-world distance between the two scale points, inscale_unit.scale_unit::String = "": Unit ofscale_real_distance, canonicalised bynormalize_unit. Known units are ("nm", "μm", "mm", "cm", "m", "in").scale_point_1::Tuple{Float64,Float64} = (0.0, 0.0): First scale endpoint, in relative coordinates (0.0 to 1.0).scale_point_2::Tuple{Float64,Float64} = (0.0, 0.0): Second scale endpoint, in relative coordinates (0.0 to 1.0).
The endpoints are stored relative, exactly like a CountPoint, so they survive any change in how the image is displayed. The pixel distance between them is not stored — it is derived by scale_pixel_distance, so there is one source of truth.
set_scale! and clear_scale! are the only supported ways to change any scale_* field or has_scale. Assigning to them directly bypasses validation and can leave the session inconsistent — for example a has_scale = true session whose endpoints are coincident, which makes every conversion undefined.
Examples
session = CountSession(;
image_path = "moths.jpg",
image_width = 3456,
image_height = 5184,
active_tag = "object",
)ImageTally.Tag — Type
TagRepresents a user-defined counting category with visual properties.
Fields
name::String: Display name (e.g. "male", "female", "egg"). Must not be empty.color::Symbol: Marker color (e.g.:red,:blue,:green). GLMakie accepts any named color; commonly used values include:red,:blue,:green,:orange,:purple,:cyan,:magenta,:yellow,:black,:white,:gray.marker::Symbol: Marker shape (e.g.:circle,:utriangle). Known supported markers are (:circle, :utriangle, :dtriangle, :rect, :diamond, :xcross, :cross, :pentagon). A warning is issued for unrecognised symbols.
Throws
ArgumentErrorifnameis empty.
ImageTally.add_point! — Method
add_point!(session, x_px, y_px) -> CountPointAdd a new point at the given pixel coordinates using the active tag. Coordinates are converted to relative and clamped to valid range. Both integer and floating-point coordinates are accepted.
Examples
add_point!(session, 1728.0, 2592.0)
add_point!(session, 1728, 2592)ImageTally.add_tag! — Method
add_tag!(session, tag) -> TagAdd a new tag to the session. Throws ArgumentError if a tag with the same name already exists or if the maximum number of tags is reached.
Examples
add_tag!(session, Tag("juvenile", :green, :diamond))ImageTally.check_image_file_size — Method
check_image_file_size(session, expected_size)Warn if the image file has changed size since the session was saved.
Silent when expected_size is nothing (a v0.1.0 file, which recorded no size) or when the image file is not present — load_session has never required the image to exist, and headless analysis where the session travels without its image is supported. Never throws: a size mismatch is suspicious, not invalid.
ImageTally.clamp_to_image — Method
clamp_to_image(x_rel, y_rel) -> Tuple{Float64, Float64}Clamp relative coordinates to valid image range (0.0 to 1.0). Handles clicks slightly outside the image boundary.
ImageTally.clear_scale! — Method
clear_scale!(session) -> NothingRemove the session's scale calibration, resetting every scale field to its default. Points, tags, and every other field are left untouched. Calling this on a session with no scale is a no-op, not an error.
Examples
clear_scale!(session)
session.has_scale # falseImageTally.count_by_tag — Method
count_by_tag(session) -> Dict{String, Int}Return a dictionary with the count of points for each tag.
Examples
count_by_tag(session) # Dict("male" => 5, "female" => 3)ImageTally.csv_escape — Method
csv_escape(value) -> StringReturn value as an RFC 4180 CSV field: wrapped in double quotes if it contains a comma, a double quote, a carriage return, or a newline, with any embedded double quote doubled. A value containing none of those is returned unchanged.
Every string-valued column is free-form — a tag is whatever the user typed, and so is an unrecognised unit — so a tag named eggs, parasitized would otherwise shift every following column by one for that row, silently.
Returning ordinary values bare is what keeps output for existing sessions byte-identical to what ImageTally v0.1.0 wrote.
ImageTally.csv_row — Method
csv_row(session, point) -> StringBuild the uncalibrated CSV row for point, matching CSV_HEADER.
ImageTally.csv_row_scaled — Method
csv_row_scaled(session, point) -> StringBuild the calibrated CSV row for point, matching CSV_HEADER_SCALED.
Only called for a session with a calibration: pixels_to_real throws without one, so export_csv selects between this and csv_row up front rather than converting speculatively.
ImageTally.default_tags — Method
default_tags() -> Vector{Tag}Return a default set of two tags to use when no tags are specified.
ImageTally.delete_point! — Method
delete_point!(session, id) -> BoolDelete the point with the given id. Returns true if found and deleted, false if no point with that id exists.
Examples
delete_point!(session, 1)ImageTally.export_csv — Method
export_csv(session, path) -> NothingExport the counted points to a CSV file at path. Each row represents one counted point with its relative coordinates, pixel coordinates, tag, and timestamp.
The CSV includes both relative (0.0-1.0) and absolute pixel coordinates so the data is useful regardless of how the image is displayed.
A session with no scale calibration exports seven columns:
id,tag,x_relative,y_relative,x_pixel,y_pixel,timestampA calibrated session exports ten, with x_real, y_real, and unit inserted before timestamp:
id,tag,x_relative,y_relative,x_pixel,y_pixel,x_real,y_real,unit,timestampunit carries the session's canonical unit — the normalised form, so a session calibrated in um exports μm — repeated on every row.
A scale converts distances, not positions. x_real is the distance from the left edge of the image and y_real the distance from its top, so the origin is the corner of the photograph — wherever the camera happened to be framed, which is arbitrary and not comparable between images. y_real increases downward, following the image convention rather than the mathematical one.
Absolute values are therefore not meaningful; differences are. Valid uses: the distance between two points, the extent of a bounding box, an area for a density calculation. Not valid: treating the values as coordinates in any external reference frame.
The calibration endpoints are not exported. They are metadata about the measurement, not count data, and they are recorded in the session TOML.
String fields are escaped per RFC 4180 — see csv_escape — so a tag or unit containing a comma, a quote, or a newline round-trips through any standard CSV reader.
Throws
ArgumentErrorifpathdoes not have a.csvextension.
Examples
export_csv(session, "my_count.csv")ImageTally.find_nearest_point — Method
find_nearest_point(session, x_px, y_px; threshold=50.0) -> Union{CountPoint, Nothing}Find the nearest point to the given pixel coordinates within the threshold distance. Returns nothing if no point is within the threshold.
Examples
point = find_nearest_point(session, 1728.0, 2592.0)ImageTally.get_tag — Method
get_tag(session, tag_name) -> Union{Tag, Nothing}Return the Tag with the given name, or nothing if it doesn't exist.
Examples
tag = get_tag(session, "male")ImageTally.has_tag — Method
has_tag(session, tag_name) -> BoolReturn true if a tag with the given name exists in the session.
Examples
has_tag(session, "male") # true or falseImageTally.launch_counter — Method
launch_counter(args...; kwargs...)Launch the ImageTally graphical counting interface. Requires GLMakie to be loaded first:
using GLMakie
using ImageTally
launch_counter("path/to/image.jpg")ImageTally.load_session — Method
load_session(path) -> CountSessionLoad a CountSession from a TOML file previously saved with save_session.
Session files written by ImageTally v0.1.0 load unchanged: they carry no format_version key, which is read as version 1. A file whose format_version is newer than this ImageTally understands is rejected rather than loaded with fields silently dropped.
The image file itself is not required to exist. If it does, and the session recorded its size, a mismatch produces a warning — see check_image_file_size.
A [scale] table is optional: a file without one loads with has_scale = false and the default scale fields, which is every version 1 and version 2 file. When the table is present, all four of its keys are required and its unit is canonicalised — see scale_fields.
next_id is likewise optional, and falls back to the derived max(id) + 1 for older files — see stored_next_id.
Throws
ArgumentErrorif the file does not exist or is not a.tomlfile.ArgumentErrorifformat_versionis newer than this version of ImageTally.ArgumentErrorif a required key is missing.ArgumentErrorif a[scale]table is present but incomplete or malformed.
Examples
session = load_session("my_count.toml")ImageTally.move_point! — Method
move_point!(session, id, x_px, y_px) -> BoolMove the point with the given id to new pixel coordinates. Returns true if found and moved, false if no point with that id exists. Both integer and floating-point coordinates are accepted.
Examples
move_point!(session, 1, 1800.0, 2600.0)
move_point!(session, 1, 1800, 2600)ImageTally.new_session — Method
new_session(image_path, width, height; tags=default_tags()) -> CountSessionCreate a new counting session for the given image.
Examples
session = new_session("moths.jpg", 3456, 5184)
session = new_session("moths.jpg", 3456, 5184; tags=[Tag("male", :blue, :circle), Tag("female", :red, :utriangle)])Throws
ArgumentErrorifimage_pathis empty.ArgumentErrorifwidthorheightis not positive.ArgumentErroriftagsis empty or exceedsMAX_TAGS.
ImageTally.normalize_unit — Method
normalize_unit(unit) -> StringReturn the canonical form of a length unit.
Surrounding whitespace is stripped, the MICRO SIGN (U+00B5) is rewritten to the GREEK SMALL LETTER MU (U+03BC), and common aliases (um, micron, millimetre, inches, …) are mapped to their entry in VALID_UNITS, case-insensitively. A unit already in VALID_UNITS is returned unchanged.
Anything else is returned as given, with a warning. This is deliberate: a scale bar is not the only reference object in practice, and a session calibrated against a grid square or a body length is legitimate. The warning exists so an unintended unit does not pass silently, not to block one.
Accepts any AbstractString — unit strings often arrive as a SubString from split or from parsed input.
Examples
normalize_unit("mm") # "mm"
normalize_unit(" um ") # "μm"
normalize_unit("Microns") # "μm"
normalize_unit("Inches") # "in"Throws
ArgumentErrorif the unit is empty or only whitespace.
ImageTally.parse_scale_point — Method
parse_scale_point(value, key, path) -> Tuple{Float64, Float64}Convert a [scale] endpoint read from TOML into a relative-coordinate tuple. Throws an ArgumentError — never a BoundsError or MethodError — when the value is not a 2-element array of numbers, so a hand-edited [scale] table fails the same way every other malformed session key does.
ImageTally.pixel_to_relative — Method
pixel_to_relative(x_px, y_px, width, height) -> Tuple{Float64, Float64}Convert pixel coordinates to relative coordinates (0.0 to 1.0).
Examples
pixel_to_relative(1728, 2592, 3456, 5184) # (0.5, 0.5)ImageTally.pixels_to_real — Method
pixels_to_real(session, px_distance) -> Float64Convert a distance in pixels to the session's real-world unit.
Examples
# 100 px calibrated as 2.0 mm
pixels_to_real(session, 50.0) # 1.0Throws
ArgumentErrorif the session has no scale calibration.
ImageTally.real_to_pixels — Method
real_to_pixels(session, real_distance) -> Float64Convert a distance in the session's real-world unit to pixels. The exact inverse of pixels_to_real.
Examples
# 100 px calibrated as 2.0 mm
real_to_pixels(session, 1.0) # 50.0Throws
ArgumentErrorif the session has no scale calibration.
ImageTally.relative_to_pixel — Method
relative_to_pixel(x_rel, y_rel, width, height) -> Tuple{Int, Int}Convert relative coordinates (0.0 to 1.0) to pixel coordinates.
Examples
relative_to_pixel(0.5, 0.5, 3456, 5184) # (1728, 2592)ImageTally.remove_tag! — Method
remove_tag!(session, tag_name) -> BoolRemove the tag with the given name. Returns false if the tag doesn't exist. Throws ArgumentError if points exist with this tag — delete or retag those points first. Automatically switches active tag if the removed tag was active.
Examples
remove_tag!(session, "juvenile")ImageTally.require_key — Method
require_key(data, key, path; table = "")Return data[key], or throw an ArgumentError naming the missing key and the file it was expected in. Used instead of a bare index so a truncated or hand-edited session file produces an actionable message rather than a raw KeyError.
table qualifies the key name in the message for keys that live in a sub-table, so a missing [scale] key is reported as "scale.unit" rather than the ambiguous "unit".
ImageTally.require_scale — Method
require_scale(session, what)Throw an ArgumentError naming what if session has no scale calibration.
ImageTally.save_session — Method
save_session(session, path) -> NothingSave a CountSession to a TOML file at path. The session can be reloaded with load_session. The image file is not saved — only its path, dimensions, and size in bytes are recorded.
The size in bytes of the image file is recorded as image_file_size so load_session can warn if the image has changed since the session was saved. If the image file does not exist at save time, the key is omitted rather than the save failing — a session is still worth saving without its image.
next_id is written out rather than re-derived on load. Deriving it from the highest point id means deleting the highest-numbered points and reloading silently rewinds the counter, so new points reuse ids that a previously exported CSV already refers to.
A [scale] table is written only when the session has a calibration, so a session without one is structurally unchanged from a version-2 file apart from the version number and next_id:
[scale]
real_distance = 1.0
unit = "mm"
point_1 = [0.123, 0.456]
point_2 = [0.789, 0.456]Keys are written sorted, so a session file is reproducible and diffable.
Examples
save_session(session, "my_count.toml")ImageTally.scale_fields — Method
scale_fields(data, path) -> NamedTupleRead the optional [scale] table into the CountSession scale keywords.
Returns the uncalibrated defaults when the table is absent, which is every version 1 and version 2 session file. When it is present all four keys are required and validated: a partial or malformed table is a corrupt file, not a session with a half-set scale.
The unit is normalised on read, so a file written by an older ImageTally or edited by hand carries a canonical unit in memory regardless of what is on disk.
ImageTally.scale_pixel_distance — Method
scale_pixel_distance(session) -> Float64Return the Euclidean distance in pixels between the two scale points, or 0.0 when the session has no scale.
The distance is derived from the stored relative endpoints and the image dimensions rather than stored, so it cannot drift out of agreement with them.
Examples
session = new_session("moths.jpg", 1000, 1000)
set_scale!(session, 100.0, 100.0, 200.0, 100.0, 2.0, "mm")
scale_pixel_distance(session) # 100.0ImageTally.session_summary — Method
session_summary(session) -> StringReturn a human-readable summary of the session as a string.
Examples
println(session_summary(session))ImageTally.set_active_tag! — Method
set_active_tag!(session, tag_name) -> NothingSet the active tag by name. Throws ArgumentError if tag doesn't exist.
Examples
set_active_tag!(session, "female")ImageTally.set_marker_size! — Method
set_marker_size!(session, size) -> NothingSet the global marker display size. Must be positive. Both integer and floating-point values are accepted. A warning is issued for sizes above 200, which are unusually large and may obscure the image.
Examples
set_marker_size!(session, 20.0)
set_marker_size!(session, 20)Throws
ArgumentErrorifsizeis not positive.
ImageTally.set_scale! — Method
set_scale!(session, x1_px, y1_px, x2_px, y2_px, real_distance, unit) -> NothingCalibrate the session by declaring that the segment between two pixel coordinates spans real_distance in unit.
The endpoints are given in pixel coordinates, matching add_point!, and are converted to relative coordinates and clamped to the image before being stored. Calling this on an already-calibrated session replaces the calibration.
The unit is canonicalised by normalize_unit, which warns — but does not throw — for a unit outside VALID_UNITS.
Examples
# A 100 px scale bar that is 2 mm long
set_scale!(session, 100.0, 900.0, 200.0, 900.0, 2.0, "mm")
set_scale!(session, 100, 900, 200, 900, 2, "mm")Throws
ArgumentErrorifreal_distanceis not positive.ArgumentErrorifunitis empty.ArgumentErrorif the two points are coincident after clamping — a zero pixel distance makes every conversion undefined. Two distinct points outside the image can clamp to the same location, so the check happens after clamping.
ImageTally.stored_next_id — Method
stored_next_id(data, points, path) -> IntReturn the id counter to load the session with.
Version 3 files store next_id; older files do not, and fall back to the value derived from the points — max(id) + 1 — which is what every version of load_session has used. A stored value below the derived one would hand out ids that already exist, so the derived value wins and the discrepancy is reported.
ImageTally.total_count — Method
total_count(session) -> IntReturn the total number of counted points across all tags.
Examples
total_count(session) # 8