amfora.core.analysis#

Per-sherd and batch analysis. analyze_single_sherd is the main entry point; full_analysis is the batch wrapper that produces a CSV / DataFrame from a folder of images. The other functions are component helpers and are exposed mostly so you can pull them out of the pipeline when you need finer control.

Source: src/amfora/core/analysis.py.

Top-level pipeline#

amfora.core.analysis.analyze_single_sherd(image, scan_dpi=1200, analyze_inclusions=True, analyze_voids=True, analyze_core_periphery=True, use_blob=True, use_contour=True, enhance_contrast=True, clahe_clip=2.0, clahe_grid=(8, 8), channels=('B', 'G', 'R'), combine_mode='union', vote_min=2, void_intensity_max=60.0, paste_pop_k=None, paste_pop_floor=8.0, watershed_enabled=True, multigrain_split_enabled=True, pre_masked=False)[source]#

Comprehensive analysis of a single ceramic sherd image.

Parameters:
  • image (numpy.ndarray) – Image array of a scanned sherd

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200)

  • analyze_inclusions (bool, optional) – Whether to analyze inclusions (default: True)

  • analyze_voids (bool, optional) – Whether to analyze voids (default: True)

  • analyze_core_periphery (bool, optional) – Whether to perform core-periphery color analysis for firing atmosphere interpretation. This is computationally intensive. (default: True)

  • use_blob (bool, optional) – Whether to use blob detection method (default: True)

  • use_contour (bool, optional) – Whether to use contour detection method (default: True)

  • enhance_contrast (bool, optional) – If True (default), apply CLAHE before running blob/contour detection. When channels == ('L',) CLAHE is applied once to the masked image’s L* channel via the BGR round-trip; when multiple channels are requested CLAHE is instead applied to each channel inside the detectors so every channel benefits from the contrast enhancement. Set to False to disable entirely.

  • clahe_clip (float, optional) – CLAHE clip limit when enhance_contrast=True (default: 2.0).

  • clahe_grid (tuple of int, optional) – CLAHE tile grid size when enhance_contrast=True (default: (8, 8)).

  • channels (tuple of str, optional) – Image channels passed to both detectors. Default ('B', 'G', 'R') runs detection independently on each of OpenCV’s native BGR channels (not RGB) and combines the results, so inclusions that only contrast strongly in one channel (e.g. iron-rich grains in R, organic dark cores in B) are still picked up. L* is excluded from the default because it is a perceptually-weighted blend of B, G, and R, so including it gives features visible in L* an extra redundant vote in the combination step. Set channels=('L',) to recover the pre-multi-channel behavior.

  • combine_mode ({'union', 'vote'}, optional) – How to combine per-channel detections when len(channels) > 1. Default 'union' pools detections across channels and removes spatial duplicates without requiring cross-channel agreement. This catches monochromatic features — e.g. an iron-bearing sand grain can contrast strongly in B (where the warm-toned grain reads dark against a cream matrix) while showing near- zero contrast in R (where both grain and matrix read bright) — that the old 'vote' default with vote_min=2 dropped: roughly half of legitimate single-channel detections were lost to the agreement requirement on sand-tempered fabrics. Noise rejection is instead handled by the paste-anchored pop gate (paste_pop_k / paste_pop_floor), which directly measures whether a candidate’s interior is statistically distinct from the sherd’s paste on the raw (pre-CLAHE) pixels and is a stronger discriminator than per-channel voting. Both detectors accept this parameter; the contour detector’s noise rejection is less voting-dependent (shape filters do more of the work), so the 'union' default trades a small contour-recall hit (~5%) for a large blob- recall gain (~27%) on sand-tempered fabrics. Use 'vote' only if you have a specific reason to require cross-channel agreement (e.g. very noisy scans).

  • vote_min (int, optional) – Minimum number of channels that must agree for a feature to be kept when combine_mode='vote' (default: 2 of 3 BGR channels). Ignored under the default combine_mode='union'.

  • void_intensity_max (float in 0..255, optional) – Brightness gate applied to void detections in both detectors. A candidate void’s interior must read below this mean pixel intensity on its channel (default: 60). This is the primary inclusion-vs-void discriminator on real scans, because the DPI-scaled blur smooths shape concavities that would otherwise distinguish a pore (hole, near-black inside) from a dark mineral grain (just darker paste). Lower (e.g. 45) for stricter void detection; raise (e.g. 90) for low-contrast scans where genuine pores don’t quite reach black.

  • paste_pop_k (float or None, optional) –

    Minimum “pop” required for a candidate inclusion to be kept, expressed as a multiple of the paste’s per-channel Median Absolute Deviation. Default None lets each detector use its own calibrated default — sherd_blobs defaults to 2.0 (looser, since SimpleBlobDetector’s own shape filters already cull most noise) and contour_detection defaults to 2.5 (tighter, since contour candidates pass fewer prior gates). Setting an explicit value overrides BOTH detectors with the same K. “Pop” here is informal shorthand for how much the feature stands out against the sherd’s paste — concretely, on the raw (pre-CLAHE) BGR channels of the masked input image:

    paste_ref = per-channel median of non-zero pixels
    paste_MAD = per-channel median(|pixel - paste_ref|)
    keep iff  |median(interior) - paste_ref|  >=  max(K * MAD, floor)
              on at least one BGR channel
    

    The MAD scaling makes the same K work across paste types — smooth cream paste (MAD ~5) gets a tight absolute threshold, mottled grog-tempered paste (MAD ~18) gets a loose one. K=2.0 is roughly a 3σ detection threshold, K=2.5 ~3.7σ (σ ≈ 1.4826 · MAD for normal data). Anchoring on the global paste reference instead of the local ring used by earlier versions is what fixed the dense-cluster failure mode where bright paste pockets between dark grains were detected as light inclusions and dark cluster grains were rejected for having dark neighbors. Set to 0 to disable; raise (e.g. 3.0+) for tighter precision; lower (e.g. 1.5) when chasing very subtle features.

  • paste_pop_floor (float in 0..255, optional) – Absolute brightness floor under the MAD-scaled threshold (default: 8.0). Effective threshold per channel is max(paste_pop_k * MAD, paste_pop_floor). Inactive on every real flatbed scan (MAD ≥ ~7 across our calibration set), but protects against pathological near-zero-MAD inputs (synthetic test images, heavily slipped pieces) where a tiny MAD would otherwise collapse the gate.

  • watershed_enabled (bool, optional) – Enable distance-transform watershed recovery of dark cluster contours the size cap would otherwise drop (default: True). Contour detection only. Disable for legacy behavior or to save ~10-30 % runtime on cluster-dense scans.

  • multigrain_split_enabled (bool, optional) – Enable a second watershed pass that breaks already-accepted lumpy contours into individual grain sub-contours (default: True). Contour detection only. Splitting triggers only when a contour exceeds both an area threshold (default 0.005 cm²) and a solidity ceiling (default 0.75) — single big convex grains (large grog fragments, quartz pebbles) pass through untouched.

  • pre_masked (bool, optional) – If True, skip sherd_mask and treat the entire input image as the sherd (default: False). Use for backgroundless / tight-cropped images where every pixel is sherd and GrabCut has nothing to segment against.

Returns:

  • dict – Dictionary containing comprehensive analysis results.

    Contour inclusion orientation includes circular, multivariate-ready metrics (v1.0.3+) computed on the sherd-corrected axial angles: contour_inclusion_orientation_strength (0 = random .. 1 = perfectly aligned), _concentration (von Mises), _uniformity, _bimodality, _dominant_deg, and _alignment — a signed mean(cos 2*theta) where +1 = grains parallel to the sherd surface, 0 = random, -1 = perpendicular. Unlike the retained legacy _mean / _std columns (plain statistics on circular degrees), these are valid for PCA/LDA and clustering. See analyze_orientation_for_pca.

  • Limitations

  • ———–

  • **Optical contrast limit on same-coloured temper-in-matrix systems.**

  • Both detectors find features by their intensity contrast against the

  • surrounding paste on the scanned image. When the optical signature

  • of the temper grains overlaps the optical signature of the matrix —

  • e.g. a quartz/feldspar/iron-mineral sand temper in an iron-rich

  • terra-cotta paste, where the warmer-toned mineral fraction of the

  • sand reads similarly to the surrounding red paste — those grains

  • become chromatically camouflaged and the detectors can only resolve

  • the subset that still differs in lightness. This is a property of

  • the scan, not of the algorithm (in the AMFOrA_Test_Bars set the)

  • R08G (grey clay body) and R08TC (terra-cotta clay body) bars share

  • an identical sand temper, but R08G yields ~200 inclusions/cm²

  • while R08TC yields ~88/cm² (a 2.3× gap) because the warmer grains

  • in the sand disappear visually against the warmer paste. Note

  • that the scan colour rendering can also overstate this effect

  • sand that appears as a “salt and pepper” mix in the lab can read

  • as predominantly warm-toned under flatbed-scanner illumination,

  • which is what drives the chromatic overlap with terra-cotta

  • matrices.

  • Practical implications

  • - Inclusion counts and densities are NOT directly comparable across – sherds with substantially different matrix colours, even when the temper is identical. Reported counts represent an optically- visible lower bound on the true grain population, not a complete census.

  • - Same-coloured temper-in-matrix systems known to be affected – include: red-bodied wares with self-temper or iron-rich sand, calcareous (white-firing) clays with calcareous (limestone) temper, and reduced black wares with carbonaceous inclusions.

  • - For cross-fabric comparison, group sherds by matrix colour – first (e.g. compare R##G to R##G, R##TC to R##TC), or use petrographic thin-sectioning + polarised-light microscopy when a true grain census is required.

  • No parameter adjustment can recover the missing grains — the

  • information is not in the image — so the limitation should be

  • reported alongside any cross-fabric comparison of inclusion

  • metrics.

amfora.core.analysis.full_analysis(folder_path, scan_dpi=1200, analyze_inclusions=True, analyze_voids=True, analyze_core_periphery=True, use_blob=True, use_contour=True, interleave_columns=False, file_formats=None, save_csv=True, output_filename=None, enhance_contrast=True, clahe_clip=2.0, clahe_grid=(8, 8), channels=('B', 'G', 'R'), combine_mode='union', vote_min=2, void_intensity_max=60.0, paste_pop_k=None, paste_pop_floor=8.0, watershed_enabled=True, multigrain_split_enabled=True, pre_masked=False)[source]#

Comprehensive analysis of all ceramic sherds in a directory with both blob and contour detection.

This function processes all images in a directory using both detection methods and provides complete size, orientation, color, and morphological analysis for archaeological research.

Parameters:
  • folder_path (str) – Path to folder containing ceramic images

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200)

  • analyze_inclusions (bool, optional) – Whether to analyze inclusions (default: True)

  • analyze_voids (bool, optional) – Whether to analyze voids (default: True)

  • analyze_core_periphery (bool, optional) – Whether to perform core-periphery color analysis for firing atmosphere interpretation. This is computationally intensive. (default: True)

  • use_blob (bool, optional) – Whether to use blob detection method (default: True)

  • use_contour (bool, optional) – Whether to use contour detection method (default: True)

  • interleave_columns (bool, optional) – Whether to reorder columns so blob/contour variants of the same metric are placed side-by-side. (default: False)

  • file_formats (list, optional) – List of file extensions to process (default: [‘jpg’, ‘jpeg’, ‘png’, ‘bmp’, ‘tiff’, ‘tif’])

  • save_csv (bool, optional) – Whether to automatically save results as CSV (default: True)

  • output_filename (str, optional) – Custom filename for CSV output (default: auto-generated based on folder name)

  • enhance_contrast (bool, optional) – If True (default), apply CLAHE before running blob/contour detection. With the default multi-channel channels setting, CLAHE is applied to each channel independently inside the detectors; with channels=('L',) it falls back to the legacy single-pass L* CLAHE on the masked image. Set to False to disable entirely.

  • clahe_clip (float, optional) – CLAHE clip limit when enhance_contrast=True (default: 2.0).

  • clahe_grid (tuple of int, optional) – CLAHE tile grid size when enhance_contrast=True (default: (8, 8)).

  • channels (tuple of str, optional) – Image channels passed to both detectors. Default ('B', 'G', 'R') runs detection on each BGR channel and combines the results so inclusions that only contrast in one channel are still picked up. Set channels=('L',) to recover the pre-multi-channel L*-only behavior. See analyze_single_sherd for the full description.

  • combine_mode ({'union', 'vote'}, optional) – How to combine per-channel detections (default: 'union'). Pools detections without requiring cross-channel agreement, catching monochromatic features (e.g. an iron-bearing sand grain visible only in B against a warm matrix) that voting would drop. Noise rejection is handled by the paste-anchored pop gate (paste_pop_k / paste_pop_floor) instead. See analyze_single_sherd for the full rationale.

  • vote_min (int, optional) – Minimum number of channels that must agree under combine_mode='vote' (default: 2 of 3 BGR channels). Ignored under the default combine_mode='union'.

  • void_intensity_max (float in 0..255, optional) – Brightness gate for void detection in both detectors (default: 60). See analyze_single_sherd for the full description; lower this for stricter voids, raise for low-contrast scans.

  • paste_pop_k (float or None, optional) – Minimum paste-pop required for a candidate inclusion to be kept, expressed as a multiple of the paste’s per-channel Median Absolute Deviation. Default None lets each detector use its own calibrated default (sherd_blobs = 2.0, looser; contour_detection = 2.5, tighter). Setting an explicit value overrides BOTH detectors. See analyze_single_sherd for the formal definition and full rationale.

  • paste_pop_floor (float in 0..255, optional) – Absolute brightness floor under the MAD-scaled threshold (default: 8.0). Effective threshold per channel is max(paste_pop_k * MAD, paste_pop_floor).

  • watershed_enabled (bool, optional) – Enable distance-transform watershed recovery of dark cluster contours the size cap would otherwise drop (default: True). Contour detection only.

  • multigrain_split_enabled (bool, optional) – Enable a second watershed pass that breaks already-accepted lumpy contours into individual grain sub-contours (default: True). Contour detection only.

  • pre_masked (bool, optional) – If True, skip sherd_mask and treat every input image as already isolated to its sherd (default: False). Use for backgroundless / tight-cropped directories where GrabCut has nothing to segment against.

Returns:

Comprehensive DataFrame containing: - Blob detection results (blob_inclusion_*, blob_void_*) - Contour detection results (contour_inclusion_*, contour_void_*) - Orientation analysis (inclusion_orientation_* — including circular

strength / concentration / uniformity / bimodality / alignment metrics valid for multivariate analysis — plus sherd_orientation)

  • Color analysis (inclusion_color_*, sherd_color_*)

  • Density and percentage calculations

  • Processing status and metadata

Return type:

pandas.DataFrame

Notes

Output includes dual detection methods:

Blob Detection - Better for round, circular features: - Good for: quartz grains, rounded temper, spherical voids - Metrics: blob_inclusion_count, blob_inclusion_total_area_cm2, etc.

Contour Detection - Better for irregular, angular features: - Good for: angular rock fragments, irregular voids, elongated inclusions - Metrics: contour_inclusion_count, contour_inclusion_total_area_cm2, etc.

All area measurements are in cm², densities in features per cm².

Limitations#

Inclusion counts and densities are NOT directly comparable across sherds whose matrix colours overlap their temper colours (e.g. an iron-bearing sand temper in a terra-cotta paste vs the same sand in a grey paste). The detectors can only resolve grains that contrast against the matrix; chromatically camouflaged grains are invisible to optical scanning regardless of detection parameters. See analyze_single_sherd for the full discussion and the R08G/R08TC calibration data showing the ~2.3× density gap on bars sharing identical sand temper.

Per-stage helpers#

amfora.core.analysis.size_count_summary_single(blobs_light, blobs_dark, scan_dpi=1200)[source]#

Analyze size distributions for a single image’s detected blobs.

Parameters:
  • blobs_light (list) – List of light blob keypoints (inclusions)

  • blobs_dark (list) – List of dark blob keypoints (voids)

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200)

Returns:

Dictionary containing comprehensive size statistics for inclusions and voids

Return type:

dict

amfora.core.analysis.size_count_summary(folder_path, fileformat='jpeg', scan_dpi=1200, use_blob=True, use_contour=True, interleave_columns=False, pre_masked=False)[source]#

Analysis of inclusions and voids using blob and/or contour detection.

Parameters:
  • folder_path (str) – Path to folder containing ceramic images

  • fileformat (str, optional) – Image file format to process (default: ‘jpeg’)

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200) Valid range: 150-2400 DPI

  • use_blob (bool, optional) – Whether to use blob detection (default: True)

  • use_contour (bool, optional) – Whether to use contour detection (default: True)

  • interleave_columns (bool, optional) – Whether to reorder columns so blob/contour variants of the same metric are placed side-by-side. (default: False)

  • pre_masked (bool, optional) – If True, skip sherd_mask and treat every input as already isolated to its sherd (default: False).

Returns:

Summary statistics for each ceramic’s inclusions and voids. All area measurements are in cm². Column names are prefixed with blob_ or contour_ to indicate detection method.

Return type:

pandas.DataFrame

amfora.core.analysis.void_counter(image, scan_dpi=1200)[source]#

Calculate the number and area of void spaces within a ceramic sherd.

Parameters:
  • image (numpy.ndarray) – Image of a scanned sherd

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200) Valid range: 150-2400 DPI

Returns:

(list of void areas in cm², number of voids found)

Return type:

tuple

amfora.core.analysis.contour_counter(image, scan_dpi=1200)[source]#

Calculate the number and area of contours within a ceramic sherd.

Parameters:
  • image (numpy.ndarray) – Image of a scanned sherd

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200) Valid range: 150-2400 DPI

Returns:

(list of contour areas in cm², number of contours found)

Return type:

tuple

amfora.core.analysis.sacredsquare(og_img, blobs)[source]#

Extract squares representing inclusions using blob detection results.

Parameters:
  • og_img (numpy.ndarray) – Original image from which blobs were detected

  • blobs (list) – Blob KeyPoint objects from blob detection

Returns:

(sorted list of [(left_vertex, right_vertex), size],

image with squares drawn over blobs)

Return type:

tuple

amfora.core.analysis.inclusion_colors(image, inclusion_list)[source]#

Extract color information for each inclusion using k-means clustering.

Parameters:
  • image (numpy.ndarray) – Masked scanned image for color analysis (BGR format)

  • inclusion_list (list) – List of inclusions and their locations from sacredsquare

Returns:

List of CIELAB colors for dominant 3 colors of each inclusion [[L*, a*, b*], [L*, a*, b*], [L*, a*, b*]] ordered by frequency L* = lightness (0-100), a* = green-red, b* = blue-yellow

Return type:

list

amfora.core.analysis.inclusion_colors_from_contours(image, contours)[source]#

Extract color information for contour-detected inclusions using contour masks.

Unlike inclusion_colors() which uses rectangular bounding boxes, this function masks each inclusion to its exact contour boundary, avoiding paste/matrix color contamination.

Parameters:
  • image (numpy.ndarray) – Masked scanned image (BGR format)

  • contours (list) – List of contour arrays from contour_detection[‘inclusions’]

Returns:

List of CIELAB colors for dominant 3 colors of each inclusion [[L*, a*, b*], [L*, a*, b*], [L*, a*, b*]] ordered by frequency

Return type:

list

amfora.core.analysis.inclusion_orientation(image, scan_dpi, contour_result=None)[source]#

Estimate orientations of inclusions and voids.

Parameters:
  • image (numpy.ndarray) – Masked image of a scanned sherd (background zeroed).

  • scan_dpi (int) – Scan resolution in dots per inch.

  • contour_result (dict, optional) – Output dict from contour_detection(). When provided the already-filtered inclusion and void contours are used directly, avoiding a redundant re-detection pass. When None the function falls back to independent threshold-based detection.

Returns:

(inclusion_angles, void_angles) — lists of integer angles in degrees derived from the minimum-area bounding rectangle of each detected feature.

Return type:

tuple

Notes

Orientation will depend on how sherds were scanned — trends will be in modal angles, not true measures. Use inclusion_orientation2 to correct for the sherd’s own principal axis.

amfora.core.analysis.inclusion_orientation2(image, scan_dpi, contour_result=None, sherd_contour=None)[source]#

Enhanced orientation analysis that corrects angles relative to the sherd’s own principal axis.

Parameters:
  • image (numpy.ndarray) – Masked image of a scanned sherd (background zeroed).

  • scan_dpi (int) – Scan resolution in dots per inch.

  • contour_result (dict, optional) – Output dict from contour_detection(). When provided the already-filtered inclusion and void contours are used directly, avoiding a redundant re-detection pass. When None the function falls back to independent threshold-based detection.

  • sherd_contour (numpy.ndarray or None, optional) – The best_contour returned by sherd_mask(). When provided the sherd’s principal axis is derived from this exact contour via cv2.minAreaRect — the same geometry that determined the mask and crop. This is the authoritative sherd orientation because the mask bounding box is what orients the entire sherd in the pipeline. When None the function derives the sherd orientation by thresholding the masked image (fallback — less reliable on dark-matrix sherds).

Returns:

(inclusion_angles, void_angles, sherd_angle) — angle lists in degrees corrected for the sherd’s principal axis orientation, plus the sherd angle itself.

Return type:

tuple

amfora.core.analysis.sherd_color_analysis(image, mask=None, crop=None, pre_masked=False)[source]#

Analyze color properties of a single ceramic sherd image using CIELAB.

Parameters:
  • image (numpy.ndarray) – Input image array (BGR format). Pass the original (un-cropped) image; the function will slice it using crop when provided.

  • mask (numpy.ndarray, optional) – Mask to apply (already cropped to the sherd region when crop is given). If None, a mask is generated automatically via sherd_mask (or full_image_mask when pre_masked=True).

  • crop (tuple or None, optional) – (y1, y2, x1, x2) crop rectangle as returned by sherd_mask. When provided together with mask, the image is sliced to this region so its dimensions match the (already-cropped) mask.

  • pre_masked (bool, optional) – If True and mask is None, skip sherd_mask and treat the entire input as the sherd (default: False). Ignored when mask is given.

Returns:

Dictionary containing CIELAB color values: - mean_l: L* lightness (0-100) - mean_a: a* green-red axis (-128 to +127) - mean_b: b* blue-yellow axis (-128 to +127)

Return type:

dict

amfora.core.analysis.sherd_color_summary(folder_path, scan_dpi=1200, use_blob=True, use_contour=True, analyze_core_periphery=True, interleave_columns=False, pre_masked=False)[source]#

Provide summary of color aspects of sherds in CIELAB colorspace.

Parameters:
  • folder_path (str) – Path to folder containing ceramic images

  • scan_dpi (int, optional) – Scan resolution in dots per inch (default: 1200)

  • use_blob (bool, optional) – Whether to analyze inclusion colors using blob detection (default: True)

  • use_contour (bool, optional) – Whether to analyze inclusion colors using contour detection (default: True)

  • analyze_core_periphery (bool, optional) – Whether to perform core-periphery color analysis for firing atmosphere interpretation. This is computationally intensive. (default: True)

  • interleave_columns (bool, optional) – Whether to reorder columns so blob/contour variants of the same metric are placed side-by-side. (default: False)

  • pre_masked (bool, optional) – If True, skip sherd_mask and treat every input as already isolated to its sherd (default: False).

Returns:

Summary statistics for each ceramic’s color and inclusion colors in CIELAB. L* = lightness (0-100), a* = green-red, b* = blue-yellow. Columns are prefixed with blob_ or contour_ to indicate method.

Return type:

pandas.DataFrame

amfora.core.analysis.extract_core_periphery_colors(masked_image, mask, scan_dpi=1200)[source]#

Extract ceramic paste colors from core vs margin regions using distance transform.

Uses distance transform to define the structural core (innermost 20% by distance from edges) and analyzes color differences between core and margin regions.

Archaeological Significance#

The a* channel (red-green axis) is the primary indicator of iron oxidation state in ceramics. Oxidized iron (Fe2O3, hematite) produces red/brown colors (high a*), while reduced iron and preserved carbon remain chromatically neutral (gray/black). L* alone cannot distinguish a dark oxidized ceramic from a reduced one.

Each zone is independently classified as oxidized, reduced, incomplete_oxidation, or carbonaceous using the full CIELAB color, then a whole-sherd firing_interpretation is derived from the combination.

param masked_image:

Masked ceramic sherd image (BGR format)

type masked_image:

numpy.ndarray

param mask:

Binary mask defining ceramic boundaries

type mask:

numpy.ndarray

param scan_dpi:

Scan resolution (default: 1200)

type scan_dpi:

int, optional

returns:
  • ‘core_lab’: [L*, a*, b*] for ceramic core

  • ‘inner_margin_lab’: [L*, a*, b*] for inner margin

  • ‘outer_margin_lab’: [L*, a*, b*] for outer margin

  • ‘core_atmosphere’: per-zone classification

  • ‘inner_margin_atmosphere’: per-zone classification

  • ‘outer_margin_atmosphere’: per-zone classification

  • ‘color_gradient’: Max Delta-E between regions

  • ‘firing_interpretation’: Archaeological assessment

  • ‘margin_symmetry’: symmetric/symmetric_transitional/asymmetric

  • ‘core_pixels’, ‘inner_margin_pixels’, ‘outer_margin_pixels’: Counts

rtype:

dict

amfora.core.analysis.analyze_inclusion_angularity(contours, scan_dpi=1200)[source]#

Analyze geometric angularity and roundness of inclusion contours.

This function uses polygon approximation and roundness metrics to classify inclusions into the six standard sedimentological roundness categories established by Muller (1964) and Powers (1953), as applied to ceramic petrography by Stienstra (1986).

Parameters:
  • contours (list) – List of contour objects from cv2.findContours()

  • scan_dpi (int, optional) – Scan resolution for size-aware filtering (default: 1200)

Returns:

Dictionary containing: - ‘angularity_scores’: list of angularity scores (0-1, higher = more angular) - ‘vertex_counts’: list of vertex counts for each inclusion - ‘roundness_ratios’: list of roundness ratios (0-1, higher = more round) - ‘roundness_classifications’: list of Muller/Powers roundness classes

(‘very_angular’, ‘angular’, ‘sub_angular’, ‘sub_rounded’, ‘rounded’, ‘well_rounded’)

  • ’approx_polygons’: list of approximated polygon contours

  • ’summary_stats’: dict with aggregate statistics

  • ’pca_metrics’: dict with metrics formatted for PCA analysis

Return type:

dict

Notes

Roundness classification follows the Powers (1953) / Muller (1964) scale adapted for automated circularity measurement: - Very angular: circularity < 0.17 - Angular: 0.17 <= circularity < 0.25 - Sub-angular: 0.25 <= circularity < 0.35 - Sub-rounded: 0.35 <= circularity < 0.49 - Rounded: 0.49 <= circularity < 0.70 - Well-rounded: circularity >= 0.70

amfora.core.analysis.analyze_orientation_for_pca(orientation_angles)[source]#

Convert circular orientation data to PCA-compatible metrics.

Handles the statistical challenges of circular data by computing: 1. Vector components (sine/cosine) - preserves directional information 2. Circular statistical measures (mean direction, concentration) 3. Fabric strength indicators (preferred orientation vs randomness)

Parameters:

orientation_angles (list or array-like) – List of angles in degrees (0-360° or -180° to +180°)

Returns:

Dictionary containing PCA-ready orientation metrics: - orientation_strength: How strongly oriented the fabric is (0=random, 1=perfectly aligned) - mean_orientation_x: X-component of mean orientation vector - mean_orientation_y: Y-component of mean orientation vector - orientation_concentration: Circular concentration parameter (higher = more aligned) - orientation_uniformity: Measure of how evenly distributed angles are - dominant_orientation_deg: Main orientation direction in degrees - orientation_bimodality: Whether fabric shows two preferred orientations

Return type:

dict

Notes

This approach solves the “circular data problem” for PCA by: 1. Converting angles to unit vectors, avoiding 0°/360° discontinuity 2. Computing vector statistics that are PCA-compatible 3. Providing archaeological interpretations (fabric strength, preferred orientations)

For ceramic analysis: - High orientation_strength = strong fabric, deliberate manufacturing technique - Low orientation_strength = random fabric, hand-building or poor clay preparation - Bimodality = cross-hatched or woven fabric structure

amfora.core.analysis.analyze_manufacturing_technique(orientation_metrics, size_metrics, geometric_metrics)[source]#

Identify likely ceramic manufacturing technique based on inclusion patterns.

Based on archaeological research (Berg 2008, Roux & Courty 2005, EXARC 2021): - Coiling: wavy/spiral patterns, radial orientations, moderate alignment - Wheel throwing: strong horizontal alignment, high uniformity, low bimodality - Slab construction: parallel to walls, moderate-high alignment, clustered joints - Pinching: random orientations, low alignment, clustering at stress points

Parameters:
  • orientation_metrics (dict) – Results from analyze_orientation_for_pca()

  • size_metrics (dict) – Size distribution metrics

  • geometric_metrics (dict) – Geometric analysis results

Returns:

Manufacturing technique analysis with confidence scores

Return type:

dict