Quantitative Metallography: Grain Size and Porosity from a Single Micrograph
Input image
The only model-facing image is:
data/alloy_M17_micrograph.png
It is a deterministic synthetic etched-alloy optical micrograph at native resolution with a visible scale bar and label in the light annotation strip at the bottom. The field contains realistic acquisition and preparation defects: radial vignetting / uneven illumination with a measurable corner-minus-center dark bias, a long dark polishing scratch, isolated surface debris specks, and a zone with locally weak grain boundaries and touching grains. Per-pixel sensor/etch noise causes pore, artifact, and boundary intensity distributions to overlap; no single raw luma threshold is a complete solution.
Use the native image, not a screenshot. No other images or ground-truth masks are provided to you.
Goal
Produce a defensible quantitative metallography package: calibrate from the scale bar, define the analysis ROI, correct illumination, segment true pores while rejecting scratch/debris, separate individual grains despite weak boundaries, assign every ROI pixel to exactly one grain territory, measure per-grain area, centroid and equivalent-circle diameter (ECD), compute porosity and a planimetric-style grain density estimate, and deliver auditable masks, spatial labels, overlays, statistics, and report.
Tooling
Fiji / ImageJ is available in the environment and is encouraged for interactive inspection, scale-bar measurement, background correction, and marker-controlled watershed, because this is a professional image-metrology workflow. Python image-analysis tools (scikit-image, scipy, numpy, matplotlib, Pillow, pandas) are also acceptable. Grading scores outputs, not tool choice — do not lose points for using Python if your masks and numbers are correct, and do not gain points for mentioning Fiji without delivering correct artifacts.
Required workflow
-
Calibration. Read the physical length from the scale-bar label in the image. Measure the scale-bar length in pixels on the native image. Compute um_per_px = physical_length_um / length_px. State method, measured pixel length, and resulting calibration_um_per_px. Use this calibration for all um and um2 conversions.
-
Analysis ROI. Restrict all quantitative analysis to the rectangular micrograph field above the light annotation strip at the bottom. The annotation strip and the scale bar inside it must be excluded from the porosity denominator and from grain/field area denominators. Determine the rectangular ROI visually and record it as analysis_roi_xywh_px = [x, y, width, height] in pixel coordinates with four integers. It should span the full image width and start at y=0, ending where the annotation strip begins. Record how you determined the boundary.
-
Illumination / vignetting correction. The micrograph has uneven illumination with darker corners and a measurable corner-versus-center bias. Correct it before any intensity-based thresholding. Describe the flat-field, background-estimation, or top-hat method and its parameters reproducibly (e.g., filter size, polynomial degree, rolling-ball radius). Explain why correction matters and quantify its effect using your own masks or region-wise statistics.
-
Pore segmentation and artifact rejection. Segment true metallurgical pores (dark rounded/irregular pores throughout the field) and exclude preparation contamination: a long polishing scratch and isolated surface debris specks. Save a final binary pore mask at original image dimensions. Explain the objective rule used to distinguish defects from pores (shape, size, linearity, location, manual curation) and any size or eccentricity filters. Pore, boundary, and artifact intensities overlap, so do not treat a single raw global threshold as a complete segmentation method.
-
Grain separation and spatial labeling. Segment individual grains over the full analysis ROI and assign every ROI pixel to exactly one grain territory, including pore and boundary pixels. The field includes locally weak boundaries and touching grains (notably in the upper-right). Use marker-controlled watershed or an equivalent marker-based separation to split touching grains while not over-splitting; a component count alone is not a sufficient result. Include edge grains clipped by the analysis ROI consistently and state the policy explicitly. Flag per-grain edge contact in the CSV. Produce a 16-bit label image where each positive integer maps to one grain.
-
Measurements. For each final grain defined by the label image, measure area in pixels and in um2, centroid in native image coordinates, and equivalent-circle diameter equivalent_diameter_um = sqrt(4 * area_um2 / pi). Produce one row per grain. Compute porosity as:
porosity_percent = 100 * (pore_pixels_inside_ROI) / (total_pixels_inside_ROI)
where total_pixels_inside_ROI includes pore pixels (pores remain in denominator). Do not include annotation-strip pixels or the scale bar in this denominator. Summarize distribution.
-
Planimetric-style estimate. Report grain_count = number of positive IDs in the label image = number of CSV data rows, analysis_area_um2 = ROI pixel area * calibration^2, and grain_density_per_mm2 = grain_count / (analysis_area_um2 / 1e6). Convert that density to an approximate ASTM E112 grain-size number using G = 1 + log2(grain_density_per_mm2 / 15.5). Label both as single-field planimetric-style estimates, not formal ASTM E112 compliance. Also report mean and median ECD in um.
Deliverables
Create artifacts/ and save exactly these seven files there:
-
measurements.csv: Exactly one row per segmented grain after final separation, including edge grains, and exactly one row per positive ID in grain_labels.png. Columns in exact order:
grain_id,centroid_x_px,centroid_y_px,area_px,area_um2,equivalent_diameter_um,touches_edge
grain_id unique integer 1..N contiguous, centroid_x_px/centroid_y_px numeric pixel coordinates computed from final label pixels in native image coordinates, area_px integer count of pixels with that ID in grain_labels.png, area_um2 numeric, equivalent_diameter_um numeric, touches_edge string true or false lowercase indicating whether that grain's label pixels touch the analysis ROI border. No extra columns. Each grain_id must equal its pixel value in grain_labels.png; area_px is the count of those pixels; centroid and edge flag derive from those pixels.
-
summary.json: JSON object with numeric fields:
calibration_um_per_px, analysis_roi_xywh_px (array of four integers), analysis_area_um2, grain_count, grain_density_per_mm2, astm_e112_grain_size_number_estimate, porosity_percent, mean_equivalent_diameter_um, median_equivalent_diameter_um
plus string fields:
edge_grain_policy (describe inclusion), porosity_denominator (must state that denominator includes pores and excludes annotation strip/scale bar).
Use finite JSON numbers, not strings or NaN/Inf; fields defined as integers must remain integers. Serialize units as ASCII um and um2 in keys only as given — do not use µm. grain_count must equal number of positive IDs in grain_labels.png and row count of measurements.csv.
-
pore_mask.png: 8-bit grayscale binary image at original image dimensions (same width/height as input micrograph, 1024x768). Pore pixels = 255, all other pixels = 0, including the entire bottom annotation strip and background. No intermediate gray values, anti-aliasing, or alpha channel. Every pixel where grain_labels.png belongs to a grain still contributes to the denominator; pore mask and label image are defined over the same ROI but stored separately.
-
grain_labels.png: Spatial grain territory map at original image dimensions (1024x768). Requirements: unsigned 16-bit grayscale PNG (mode I;16 or equivalent 16-bit), zero throughout the annotation strip (y>=704), and positive integer IDs 1..N contiguous in the full 1024x704 ROI. Every ROI pixel must belong to exactly one grain territory, including pore and boundary pixels; no zero/unassigned pixels are allowed inside ROI. IDs are arbitrary but must be contiguous 1..N and consistent with measurements.csv (each CSV grain_id equals its pixel value; area and centroid derived from those pixels). Grain count equals number of positive IDs.
-
segmentation_overlay.png: The original micrograph with grain boundaries and pore regions clearly distinguished in contrasting colors (e.g., cyan boundaries, red pores). Show the excluded polishing scratch / debris in a third color or explicitly annotate it. Preserve the source image content and the visible scale bar label/bar. The image must be human-auditable and consistent with both pore_mask.png and grain_labels.png.
-
grain_size_histogram.png: Publication-quality histogram of equivalent_diameter_um. Requirements: labeled x-axis "Equivalent diameter (um)" and y-axis "Count" or frequency, units in label, visible mean and median markers with numeric values or legend, and sample count (grain_count) annotated on figure or caption.
-
report.md: Concise engineering report covering:
- calibration method, measured scale-bar pixels, resulting
um_per_px;
- ROI selection:
xywh and how annotation-strip boundary found;
- illumination correction: method, parameters, visual effect, with quantified corner-vs-center improvement from your own data;
- pore cleanup and artifact exclusion: rule, thresholds, manual steps;
- grain separation and spatial labeling: preprocessing, marker generation, watershed or equivalent, handling of weak-boundary zone and touching grains, how every ROI pixel was assigned to exactly one grain including pores/boundaries;
- edge-grain policy;
- results: calibration, ROI, field area, grain count (with spatial label fidelity discussion), grain density and approximate ASTM E112
G (both labeled single-field planimetric-style estimates), porosity, mean/median ECD;
- limitations: single field, inclusion criteria, weak-boundary uncertainty, dependence on correction and labeling;
- QC comparison with evidence-based attribution: run a deliberately naive uncorrected global threshold (no illumination correction, no artifact rejection) and report its numeric porosity and grain count vs. your final values. There is no prescribed expected naive percentage. Separate observed contributions using evidence from your own masks or region-wise statistics: vignetting false positives (e.g., corner-versus-center dark fraction or flat-field corrected difference), grain-boundary/halo contribution, estimated scratch/debris contribution, and weak-boundary merging effect on grain count. Correct attribution may conclude a factor is negligible for your chosen threshold, but you must show the measurement that supports the claim.
Scientific contracts (must be preserved)
- Analysis ROI is the rectangular image field above the annotation strip, not a sub-crop or circular field.
- Pores remain in the porosity denominator;
porosity_percent = pore_ROI / total_ROI * 100.
- Annotation strip and scale bar are excluded from denominator and must be zero in
pore_mask.png and grain_labels.png.
- Edge grains are included in count, statistics, and CSV, and flagged via
touches_edge.
measurements.csv contains exactly one row per final grain and per positive ID in grain_labels.png, grain_count == row count == number of positive IDs.
- Each CSV
grain_id equals its pixel value in grain_labels.png; area_px is the count of those pixels; centroid and edge flag derive from those label pixels in native coordinates.
grain_labels.png is original image size, unsigned 16-bit, contiguous IDs 1..N, every ROI pixel assigned exactly one grain, including pore and boundary pixels; no zero/unassigned inside ROI.
pore_mask.png is original image size, binary 0/255 only.
- Serialized unit spellings are ASCII
um and um2.
- Calibration derived from image scale bar, not assumed.
- Planimetric-style estimate language required; do not claim formal ASTM E112 compliance from one synthetic field.
- Numeric naive/raw baseline comparison required with evidence-based attribution that separates vignetting, boundaries, scratch/debris, and weak-boundary merging; no specific naive percentage is prescribed.
Units, rounding, validation
- Use micrometres as length unit; serialize as
um / um2.
- Use decimal points and finite numbers.
- All numeric values in JSON, CSV, histogram annotations, and report must be derived from submitted final masks/labels and agree across files.
measurements.csv centroid_x_px/centroid_y_px/area_px/area_um2/equivalent_diameter_um must be internally consistent with grain_labels.png label counts and centroids.
- When finished, verify all seven artifacts exist in
artifacts/ and are readable, then stop.