Remote sensing analysis · macOS
Hachure is a macOS workstation for quantitative remote sensing, built to be quick to reach for and quick to answer. Landsat 4 through 9, Sentinel-1 and 2, MODIS, VIIRS, ASTER, SPOT 1 through 7, Pléiades and Pléiades Neo, WorldView-2/3 and Legion, GeoEye-1, SkySat and the PlanetScope Doves are first-class citizens - their bands, wavelengths, ground sample distances and calibration equations are already known, so an index written once resolves on any of them. Aerial photography, LiDAR surfaces, hyperspectral cubes and towed conductivity surveys open in the same window.
Plotted above: hachures traced down the gradient of a 1 m LiDAR surface, Whanganui Inlet, north-west Nelson. Stroke weight follows slope, after Lehmann (1799). The same ground appears photographically further down.
Finding dataWhere the time goes
Most of the time spent on a scene is spent before it opens: working out which archive holds it, drawing the area again in someone's web viewer, downloading four candidates to find the one the cloud missed. Hachure searches five public archives from the window you are already working in - Earth Search on AWS, Microsoft Planetary Computer, CNES GEODES, the Alaska Satellite Facility and LINZ - and shows you enough to choose before anything is fetched.
If a raster is open, its extent is one click: the panel offers Extent of <that raster> and seeds the search area from it, correctly projected, with no coordinates typed and no box redrawn by eye. That is the common case - you have a scene and you want the one before it, or the same ground from a different sensor - and it is the one that usually costs the most fiddling elsewhere. Otherwise drag a box on the built-in world map, which works offline.
The date range is a draggable span drawn over the archive's own temporal extent, so a window that cannot contain anything is visibly one: SPOT 2's imagery begins in 1990, and a search from 1986 shows you the four years it is wasting rather than returning nothing and leaving you to guess why. Cloud cover is a slider where the collection reports it, and quietly absent where it does not, because a filter that silently does nothing is worse than no filter. Results sort by least cloud first.
Where an archive publishes a browse image, it is drawn on the map, inside the scene's own footprint, over your area of interest - so you can see at a glance whether the cloud is over the ground you care about or off in a corner. That is the question a percentage cannot answer: a scene at 18 % cloud is useless if all of it sits on your paddock. Thumbnails load only for rows that appear and are cached per scene, so scrolling a long result list does not fetch fifty images.
Placement is taken seriously rather than approximated. A browse image is positioned by its scene's bounding box, so any transparent margin inside the picture is ground it claims and does not have - on a real radar quicklook that was 27.5 % of the canvas above the swath, which drew the data a fifth of a box too far south. Trimming to the opaque content puts the four points where the swath meets its own envelope within 0.15 – 0.73 % of where the archive's geometry says they are. The trim is on alpha only, never brightness, because a dark margin and a dark coastline are the same pixels.
LINZ publishes a static catalogue rather than a search service, so it is selected by Topo50 sheet. Click the sheets you want and Find Surveys asks the bucket which aerial surveys hold tiles for them - over a single sheet that answer is a time series of the same ground across decades, narrowed by the years they were flown. Nothing is fetched to find out: one listing request per survey answers whether it covers your ground at all.
Choosing a survey draws its tiles on the map, and the map and the list are one selection - click a tile on either. Hovering names the sheet the way LINZ printed it and the tile by its filename. The download states its size before it starts.
Bands on different native grids are assembled as separate stacks and never resampled together. A file already on disk is not fetched again. A dropped transfer is retried and resumed where the archive allows it; the retry fetches only what is still missing, the partially written file is discarded - a truncated raster that looks openable is worse than an absent one - and the failed attempt stays on record as its own row. Landsat is free on Planetary Computer and requester-pays on Earth Search, and the panel says which before you commit.
Once a scene is open, the overview pane answers “where is this?” against real topography. The source is picked by the ground under the scene, so New Zealand gets the LINZ 1:50,000 series and Switzerland gets swisstopo, while ground no national service covers falls back to Apple's map - dark, muted, points of interest off. That fallback was measured rather than assumed: over Aoraki it carries shaded relief, glaciers and landcover; over farmland it is very nearly blank, which is why it is the fallback rather than the preference. The scene footprint and your cursor are drawn on it, and any manual pan turns following off.
Composites& spectral indices
NDVI is (B08−B04)/(B08+B04) on Sentinel-2 and (B5−B4)/(B5+B4) on Landsat 8. You do not tell Hachure which is which - the catalogue carries every band's centre wavelength, FWHM, ground sample distance and spectral role, so the preset resolves itself against whatever stack is open. The same is true of NDWI, NDMI, NBR, NDSI, NDRE and tasseled-cap brightness, greenness and wetness.
The Composite panel draws every band that states a wavelength where it actually sits, as a passband rather than a point, with a draggable puck per display channel. Centring one channel in an absorption feature and the other two on its shoulders is a drag along the axis - the workflow three band-number popups cannot express. The presets are drawn as the spectra they select, so you can see what a choice will do before you make it.
Bands with no stated wavelength still appear in the list below, fully assignable; they simply have no position, which is a fact about the file rather than a limitation of the panel.
A wavelength axis as an assignment control is established prior art; what is unusual here is making it the primary control, in the place bands are assigned, with bands drawn as passbands rather than points.
Click a pixel and its full spectrum is plotted - value across every band, against wavelength where the bands declare one and band index where they do not. The y-axis is pinned rather than rescaled per pixel, so scrubbing across a boundary shows the spectrum changing instead of the axis jumping. On a hyperspectral cube that is the fastest way to tell two surfaces apart; on a ten-band stack it is the fastest way to catch a band assignment that is wrong.
Beyond the presets, a custom expression takes an arbitrary formula in b1…bN with arithmetic, comparison, logical and bitwise operators, conditionals, clamp, min and max, logarithms, safe division and QA bit extraction. One typed expression tree drives both the CPU evaluation that writes a Float32 GeoTIFF and the Metal shader compiled at runtime for the live view, and the two are tested against each other for parity - so the picture on screen and the file on disk are the same arithmetic.
Worth knowing An index is only as good as the spectral roles behind it. NDVI uses whichever bands are named NIR and Red; on a stack whose roles are wrong the arithmetic still succeeds and returns a plausible surface of the wrong two bands. The Composite panel is where you check, and the spectral profile is how you notice.
DisplayTone & fidelity
Stretch, gamma, curve and colour map are display exposure. Processing jobs, band maths and the GeoTIFF export read source samples regardless of what the screen is showing, so you can drive the display as hard as you like without wondering whether it has contaminated a result. Nothing is corrected on open: the default is the band's own full range. When you do want the curve in the file, one named command puts it there and labels what it made.
Five transfer modes - full range, percentile, histogram equalisation, levels and a tone curve - and the curve is drawn over the distribution it is shaping rather than beside it, because the judgement being made is “steepen where the pixels are”. The histogram underneath shows the values going in and holds still as you drag: one redrawn from the output always looks well spread, which tells you nothing. A curve can be derived from an equalisation and then edited, and unlike Auto it is fixed, so the same mapping can be re-applied to another scene.
Each raster layer carries its distribution as a sparkline in its sidebar row, which is how you spot the one layer in a stack of twelve whose histogram sits somewhere different - before it turns into a mosaic seam or a bad difference. It is the question the Levels plot answers for one layer, asked of the whole stack at a glance.
Three exits, and the difference between them is what happens to the curve. Export as GeoTIFF writes the source's own samples and records the curve beside them - described, not applied. The rendered exports (PNG, HEIC, OpenEXR) apply it but produce a picture: a display composite with no georeferencing, 8-bit or Float16. Processing ▸ Radiometry ▸ Apply Display Tone Curves is the one in between, and the one an expert asking for a stretched product usually wants - the curve applied, the raster kept. Every display channel goes through its own transfer, whatever is in force, and the result is a georeferenced GeoTIFF at full bit depth: either the source's own data type spread over that type's whole range, which is what another GIS or a print expects to be handed, or Float32 holding the transfer's 0 to 1 output with no requantisation at all.
The curve is evaluated per sample, not sampled from the display's lookup table. The shader reads 4096 entries, which is sixteen times coarser than uint16 data, so going through it would quantise the written file down to whatever the screen happened to need. The file that comes out is finer than the display was, and its metadata says so. Where a contributing band declares nodata and the output is an integer type, zero is reserved for it and the tone occupies one level less - an integer band cannot hold NaN, and absent ground reading as legitimate black is the one confusion worth a level.
What the output says about itself A tone curve clips at both ends and is not invertible, so the result is not radiometry and cannot be treated as any. The file states that in its own metadata rather than leaving it to be remembered: a per-band description ending (not radiometry), and a tag reading “these samples are display intensities… the measurements are in the source named above” - along with the source path, the transfer used on each channel and a note of any clipping. Nothing about the display path changes: this is a transforming operation, chosen once, by name.
A sixteen-bit scene holds far more range than a standard-dynamic-range screen can present, which is why so much of the craft of looking at one is choosing what to sacrifice. On a display with extended range there is less to sacrifice: the composite is rendered to rgba16Float in extended-linear Display P3 and handed to the compositor at that depth, so bright cloud tops and shadowed valley floor can hold detail in the same view rather than trading against each other in the stretch. Nothing along the way is quantised to eight bits.
It is opt-in, and it does not pretend. SDR is the default; headroom is read from the screen the window is actually on and re-read when it moves to another; and if that screen has no usable headroom the effective mode stays SDR even with HDR preferred, rather than rendering as though it were there. The footer says which regime you are in - a plain SDR or HDR badge - and the tooltip gives the number: rendering in HDR, output range 0 to 3.4× SDR white, or rendering in SDR, display EDR headroom 1.0× (no EDR). That matters when two people compare the same scene on different machines, and it is the sort of thing that is invisible until it has already misled somebody.
Colour is managed rather than assumed. The layer declares its colour space, so ColorSync converts to the profile of whatever display is attached instead of firing raw numbers at the panel - a wide-gamut composite looks the same on a calibrated studio display as on a laptop, and a colormap ramp is the ramp you chose rather than the one the screen happened to make of it. Exports stay SDR unless you pick HDR explicitly, and the export panel says so; OpenEXR is there for when the float range needs to leave the app intact.
Twelve- and sixteen-bit data stays at its own depth from disk to screen, and the display never invents a sample when it magnifies. A readout, a histogram bin and a percentile clip all refer to values the file actually contains. Decimation is allowed to sample, because the alternative is showing one pixel in nine; a raster with no overview pyramids opens anyway, streaming from full resolution, and a banner offers to build a sidecar in the background - mode resampling for thematic rasters, average for continuous ones.
Click corners, free draw, rectangle or ellipse. Each finished shape becomes its own layer inside a polygon group, carrying its ground area in whatever unit suits its size - hectares for a paddock, square metres for a gravel bar - and a distinct tint, so overlapping regions stay legible. Mark one Use as Processing Region and it turns graticule cyan; every region-aware job then clips to it, and the Processing menu says which region it will use. An imported shapefile or GeoJSON boundary works the same way with no conversion.
The region is immediately the scope for measurement, not only for jobs. Band statistics switches between whole image and selected region and reports, per band, the valid and excluded pixel counts alongside minimum, maximum, mean and standard deviation - with percentiles that state their own resolution: exact for integer data in a modest range, an honest bin width for float. Zonal statistics does the same per polygon, in one pass however many zones there are, and the table exports as CSV.
RadiometryPhysical values
Metadata ingest reads Landsat MTL, Sentinel-2 SAFE, Maxar IMD with RPB, Airbus DIMAP v2 and NITF, and records what calibration each delivery publishes without applying any of it. Run the conversion when you want it and the result carries the quantity, the units, the exact equation and which coefficient came from where - so a reflectance product can be audited a year later.
Calibration covers scale and offset in both conventions, solar-elevation reflectance, Planck brightness temperature, Sentinel quantification, Maxar gain and bandwidth, DIMAP divisor gain and lookup tables. Only the operations you tick are applied, results are Float32 and unclipped, and a fill value or an out-of-range input becomes NaN rather than a plausible number.
Atmospheric correction is per-band dark-object subtraction with the scene percentile and sample bound in your hands. It is labelled DOS rather than dressed up as 6S: it assumes somewhere in the scene is genuinely black in every band, which over a bright arid scene or a small subset is often false.
Topographic correction offers SCS+C, C-correction, Minnaert with the constant fitted per band, and the statistical-empirical rotation. Slope and aspect come from a Horn 3 × 3 operator on the DEM you choose; a correction fitted where the DEM does not reach is not applied there, and the report says how much of the scene was covered.
Sun zenith and azimuth are auto-filled from acquisition metadata where the product states them, and the Info inspector draws them rather than only listing them: the glyph is the sky dome seen from above, with the sun where the metadata puts it, so a low winter sun and a near-nadir summer one are told apart at a glance. That matters because illumination geometry decides whether a topographic correction is worth running at all.
Layers that cannot share a grid are not silently piled together. They group into projects by coordinate reference system, each labelled with its CRS, so a NZTM survey and a UTM scene sit in separate groups until you reproject one - which makes “why will these not overlay?” a question you can answer by looking. A whole session, layers and all, saves as a project file and reopens as it was.
ChangeDrag the pane
The base document is A and any added or derived raster is B. Fixed side by side, a draggable swipe, a linear blend, and timed flicker - all display-only, all isolating A and B from the rest of the stack. Up to four named view groups link camera, zoom and crosshair across separate windows, in geographic coordinates where both datasets have a CRS.
Change detection offers differencing, log-ratio for radar, index difference including dNBR burn severity, and IR-MAD's no-change probability as the threshold. Every method compares co-located pixels, so the panel states the same-grid requirement up front rather than failing halfway through a read - and it asks you to record what normalisation was done, because a change product that does not say gets believed.
Six ways to normalise: histogram specification, linear optimal transport matching mean and full covariance across bands, global regression, LIRRN, and IR-MAD. Accuracy assessment follows the good-practice area estimators rather than a single headline percentage, and reports quantity and allocation disagreement in place of Kappa.
TerrainSame ground as the hero
Relief shading, the slope raster and the topographic correction all use Horn's 3 × 3 kernel, which means a slope class and a hillshade cannot tell you different things about the same cell. Shading stays display-only: every control has a live thumbnail, and the full-resolution pass runs only when you press Apply, arriving as its own layer. Slope writes a file - degrees, percent or rise over run, with the downslope bearing beside it if you want it.
The light is placed on a dial rather than through two numeric sliders - bearing around the ring, height from the centre - because that is the shape of the quantity, and finding the azimuth that reveals a lineament is a matter of moving the sun until it does. Multi-directional light, cast shadows, skylight and a hypsometric, slope or aspect tint over any colormap are all there.
The shading is deliberately not exportable as a raster, because it is a picture and the slope raster is the measurement. Both come from the same operator, so the two cannot disagree about a cell.
Click a line across an elevation surface and the terrain profile charts as you go - length, elevation range, ascent and descent, and maximum slope. Where the line crosses ground the elevation model does not cover, it reports the count of those cells rather than drawing a line through them. It sits in a pane above the footer rather than floating over the map, so the ground being measured stays visible while it is being measured, and the table glyph beside the readout takes the samples out as a file.
A slope is a height over a ground distance and most DEMs state only the second. The ground distance comes from the geotransform, converted per axis on a geographic grid because a degree of longitude shortens with latitude; the vertical unit comes from the band or a compound CRS, and where nothing declares one the panel says the heights were taken as metres. A DEM in feet read as metres reports every slope 3.28 times too steep and fails in no way at all - which is why the panel says it rather than assuming you knew.
DEM of Difference reports volume lost and gained with the uncertainty that makes the figure defensible: a level of detection measured from ground you mark as stable, a probability of real change per cell, and a spatial-coherence pass that keeps a coherent patch of marginal cells and drops isolated spikes. Co-registration is offered as a check and stays off, because a rigid vertical correction removes a constant offset and uniform soil loss is a constant offset. InSAR displacement answers the neighbouring question - millimetres of motion along one line of sight at 30–80 m posting, with the coherence that says which cells are a measurement at all.
FusionDrag the pane
SFIM detail injection, Brovey, GSA - Gram-Schmidt Adaptive, which fits its intensity component by least squares instead of assuming a flat band mean - and MTF-GLP-HPM, which degrades the guide through a Gaussian matched to the sensor's modulation transfer before injecting it. Method, guide band, coarse resampling kernel and detail amount are all yours to set.
MTF-matched and GSA hold a property you can test: aggregate the fused result back to the coarse grid and it should reproduce the input. A plain component substitution does not hold it nearly as well. So where a figure has to be defensible per pixel - a calibrated reflectance, an index quoted as a number - compute it on the source bands and use the fused product for geometry. Only layers finer on the ground than the target are offered as a guide; it streams one band at a time, so peak memory does not scale with band count.
ClassificationAnd what follows it
Unsupervised: K-Means with k-means++ and maximin seeding, ISODATA with adaptive split and merge, and a Gaussian/Mahalanobis mixture - all deterministic, all multicore, with a Metal backend for the assignment step, and images too large for memory fitted on a decimated sample and assigned block by block. Supervised: random forest, maximum likelihood, minimum distance and spectral angle from training polygons you label.
Two classes that overlap in feature space cannot be told apart by any classifier, so separability is drawn as a heat map before you commit: the problem pair is a dark cell you notice, rather than one number among thirty in a table. The panel is also explicit that out-of-bag accuracy is not an accuracy assessment - it shares the training polygons' spatial autocorrelation - and the live preview fits on the visible extent, which makes it a tool for choosing k by looking rather than the model the full scene gets.
Reclassify turns a continuous band into named classes by a table of breaks, with the band's own distribution drawn behind them - because where a threshold falls is the whole of what the output says, and a number typed with nothing behind it is chosen blind. A value outside every class is left unclassified rather than pushed into the nearest one: clamping would let a top break typed as 3.6 instead of 36 report a whole property as the steepest class, complete and confident, with no gap anywhere to notice.
Land Resource Inventory intersects up to five factor layers - rock type, soil, slope, erosion, vegetation - into inventory units, sieves them to a minimum mappable area, and looks each combination up in an editable rule table for a Land Use Capability class. The class comes from the most limiting factor rather than an average; a factor nobody assessed leaves its units unclassified rather than optimistic; and a severity of zero, meaning somebody looked and found nothing, stays separate from nobody having looked. Every unit's row carries where each of its five statements came from.
MosaicJoining scenes
The panel plans the output grid, reports overlaps and disconnected pieces, and pre-flights CRS, resolution ratio, overlap sufficiency and radiometry. Two seam solvers - least-cost path routed through waypoints you pin, and graph cut, which can isolate an island. Four join modes: hard, feathered on a linear or cosine ramp, distance-weighted, and seamless multi-band Laplacian.
The preview composites on a decimated grid so the seam can be dragged and re-routed at interactive speed, and the cost surface behind it is a superset of the usual difference term: absolute difference plus gradient, constrained by a dilated mask. Radiometric balancing solves a per-scene gain and offset across every overlap simultaneously rather than chaining pairwise fits, which is what stops error accumulating along a strip.
Hard blending leaves values untouched and is the default; feathered, weighted and multi-band blending all rewrite pixels near the seam, and balancing rewrites whole scenes - so the panel says which of those you have asked for. The case it is built for is the large one: seventeen 0.3 m aerial tiles onto a single 32,000 × 60,000 four-band grid, 7.68 billion samples planned, composited and written in 62 seconds.
ProximalBelow the surface
Point a logger export at Hachure and it recognises the instrument, works out the coordinate format, names each coil with the effective depth its separation gives, and produces three georeferenced products in one window: the measured points, an interpolated surface per channel, and an inverted conductivity-against-depth stack.
The panel is a stack of stages rather than a wizard: every filter is a row that switches off, each with its own numbers on show - motion filter with a speed ceiling in metres a second and a stationary threshold in metres, headland turns flagged - the pipeline re-runs on any change, and excluded records stay drawn in dark so you can see where a filter bites before you commit. Stages run in order: parse and project, acquisition geometry, motion filter, range filter, ferrous interference, along-track despike, rolling mean, GNSS latency, sensor offset, instrument drift, temperature standardisation to 25 °C, calibration against reference profiles.
It also shows its working. The instrument is identified from the file rather than asked for. Each channel is named with the effective depth its coil separation gives, so VCP0.71 reads as 0.54 m rather than as a number you have to convert. Coil height above ground - which every forward model needs and most exports omit - is filled from the published typical for the recognised instrument and labelled as that, not presented as measured. And where the coordinate format has to be inferred, the panel states the inference and the evidence for it: values exceed ±90°, integer parts are four digits wide, values carry a hemisphere letter.
The inverted profile is a volume, so it is drawn as one - ray-marched, draped on the terrain rather than laid out flat, with the coil depths labelled down both axes and two section blades you drag in to cut the block open. Probe it and the readout gives easting, northing and depth, and says below the depth of investigation when that is where you are pointing. The exaggeration factor stays on screen rather than hiding in a menu - both figures, the depth stretch and the terrain's own - because people misjudge layer thickness on an exaggerated view and an unlabelled one is actively misleading. The chrome is achromatic throughout: furniture borrowing a colour from the transfer function would read as data. And the block stops where the data does, clipped at the depth of investigation, with a line saying the half-space below the deepest boundary is not drawn because it is an assumption of homogeneity rather than a resolved layer.
Sounding Posterior exists because a misfit is not a resolution: it says the model reproduces the readings, not that a quite different model would not have done equally well - and for EMI one very often would. Sampling the posterior under a single sounding gives the width of each layer's credible interval, and past a factor of ten the data do not resolve that layer at all. Substrate Units then clusters the whole inverted column rather than a depth slice, segmenting first so the units come out spatially coherent, and ranking them against the handbook's coding turns them into an inventory factor layer.
ProcessingJobs, not filters
Nothing blocks the viewer, nothing mutates the source, results arrive as new layers with their own provenance. Any whole-image job checks a memory ceiling before reading a pixel, so an over-large job fails in a second instead of paging for twenty minutes - and offers to run anyway, with the required and available figures on screen.
Nothing is greyed out in silence. Slope reads “is not an elevation model” against the layer it was asked about; Rank Substrate Units reads “run Substrate Units… first, then select its output”; a capability that is declared but unbuilt reads “not built yet”. The header states the processing region every region-aware job will actually use, which is the union of the polygons you selected and any vector layer flagged as a region - not a guess.
Long jobs report progress and cancel cooperatively; the Jobs inspector lists what has run and how long it took. Results are written to temporary files and opened as layers, which is what makes the menu feel immediate - so closing a window lists any derived layer you have not saved, with a format for each, and offers Save, Don't Save or Cancel. The formats are all scientific containers: GeoTIFF, Cloud-Optimised GeoTIFF, KEA and ERDAS Imagine.
A question box over a journal of this session's questions and answers. Ask what wavelength Sentinel-2's B04 sits at, which bands make a colour-infrared composite of the scene that is open, what NDRE is computed from on this sensor, or where the pan-sharpening command lives and what its methods differ on. It runs on device, and the journal lives only as long as the window - nothing is written to disk, because these are working notes about somebody's imagery and a log that outlived the session would be a store of them nobody asked for.
What makes it usable rather than merely fluent is that it is not allowed to answer from recollection. Two lookups back it: the sensor catalogue, for exact band wavelengths, widths, ground sample distances, spectral roles and index formulas; and a written guide to the interface, for where a control is and what it does. The model is instructed to quote what it was handed rather than paraphrase it, and when the catalogue does not hold something it says so and lists what it does hold - because a wavelength wrong by 8 nm looks exactly like one that is right, and a plausible menu path is worse than no answer at all. Every answer is shown with the facts it was built from underneath it: not decoration, but the difference between a number you can use and one you have to go and check.
It is handed a value describing the open scene's metadata - sensor, acquisition, bands, CRS. There is nowhere in that type to put a pixel, so “the assistant does not read your data” is a property of the code rather than a promise about it. Where Apple Intelligence is unavailable on the machine, the panel says which reason applies instead of silently doing nothing.
ThroughputTime to a result
The figures below are whole jobs on real scenes - open, read, compute, compress, write - not kernels timed in isolation, and nothing prepared beforehand and held outside the clock. The rate is megabytes of image data a second: source samples at their own width, which is what the algorithm actually reads, rather than bytes off disk, which moves with the codec. All of it was measured on one machine: a MacBook Pro with an M5 Pro, 18 cores and 64 GB of unified memory, release build. Treat them as a sense of scale rather than a specification.
Unsupervised classification
198MB/s
ISODATA over 553 megapixels and five bands on the Metal backend, end to end in 14.0 s: fit, assign every pixel, write the class raster and its attribute table. K-Means 219 MB/s, Gaussian mixture 187 MB/s.
Seventeen-tile aerial mosaic
124MB/s
Seventeen 8000 × 12000 four-band tiles at 0.3 m onto one 32,000 × 60,000 grid - 7.68 billion samples planned, composited and written in 62.1 s.
Sharpening, guided
796MB/s
4096 × 4096 × 3 Float32 in 0.253 s, reaching 10.5 of 18 cores. Richardson–Lucy at twelve iterations on the same raster: 278 MB/s.
Relief re-shade
124MB/s
34.6 megapixels of Float32 elevation in 1.11 s. The horizon field is a GPU march computed once per surface; moving the sun after that re-shades at this rate.
Is a minute fast? There is no directly comparable published figure, so this is a sense of scale rather than a benchmark. The nearest recent work - Nie, Cui & Wan, Remote Sensing 2023 - mosaics 48 Landsat-8 scenes, 55.28 GB across two UTM zones, on a Spark cluster of five blade servers: six-core Xeons, 32 GB each, 24 executor cores in total, 16 GB of Spark worker memory and 2 GB per executor. Their speed comparisons are against their own MPI and Spark baselines; ENVI appears in that paper as the quality reference, mosaicked for RMSE and SSIM, not as a speed baseline.
Their throughput is volume divided by “the running time of the mosaicking algorithm”, and the paper does not say whether blocking, HDFS ingest and the UTM to EPSG:4326 reprojection sit inside that time - the reprojection is described as done before the experiment, so probably not, which makes any rate taken from it a partial-pipeline number. The figure above is the other kind, and it is the write that bounds it: the mosaic reaches only 1.27 of eighteen cores, because compositing finishes far ahead of a writer that is single-threaded by design. Take from the comparison what it can carry - the hardware published work has needed for this job - and not a ratio.
Whole-image jobs stream in bounded blocks, so a raster larger than RAM is a longer job rather than a failed one. Before any of that starts, the job checks a memory ceiling you set - so an over-large run fails in a second with the required and available figures on screen, instead of paging for twenty minutes and then failing anyway. Reads take one GDAL handle per worker, because GDAL datasets are not thread-safe, and tiled output is what makes out-of-order block writes safe.
Every material job writes its own provenance into the result: the operation, the equation, the coefficients and which file the measurement belongs to. A topographic correction reports how much of the scene the DEM actually covered; a co-registration reports RMSE and peak ratio before and after; a DEM of Difference reports its level of detection. Results arrive as new layers and never overwrite a source.
Inspector ▸ Info ▸ System is the instrument on the instrument. It reports the GPU and its Metal version, unified and GPU memory, the processing ceiling you set, and - the useful part while you are working - the live streaming state: tile size, resident tiles, tile-texture memory, which pyramid level is being drawn and how many source pixels that level is putting into each tile pixel. Beside it sit the display facts that change what you are seeing: screen resolution, backing scale, drawable size, refresh rate, render mode and EDR headroom.
That turns two vague complaints into answerable questions. Why is this scene soft? - because the pyramid level in use is decimating eight source pixels into each tile pixel, and zooming further will fetch the finer level. Why did that job refuse? - because the processing ceiling is set to half of 64 GB and the job wanted more, which the panel shows against the figure it was measured with. It is also the first place to look when a raster streams slowly: resident tiles climbing while tile-texture memory sits at the cache ceiling is a different problem from decode never finishing.
Band assignment, stretch, index maths and relief shading are evaluated on the GPU at full resolution, so changing what you are looking at is not a job - there is no render to wait through, no downsampled proxy to work on, and no reason to commit to a band combination before you have tried three. That is the point of the GPU here: not a number on a chart, but that examining a 30,000-pixel scene never becomes the slow part of the day.
Underneath, GDAL provides format breadth and PROJ the coordinate maths, both vendored from source, pinned by version and checksum, built for Apple silicon with a curated driver set and their resources embedded so there is nothing to install alongside. One target touches GDAL's C API and no raw handle leaves it; every call site checks the error state and throws a typed error. The whole package builds in Swift 6 with complete concurrency checking.
CreditsStanding on
The audit below is generated from the manifest of archives actually merged at build time, not maintained by hand, and it is re-run whenever a version pin changes. The result below.
GPL code linked
0
No GPL anywhere in the binary.
LGPL linked statically
0
The one LGPL component in the process is Apple's system libiconv, loaded from the OS like any other Mac app.
Proprietary SDKs
0
ECW, MrSID, FileGDB and Kakadu are excluded by policy, not by accident.
Third-party Swift packages
0
Apple frameworks, GDAL and PROJ. That constraint is why this list is as short as it is.
The KEA format deserves its name said out loud. It is an HDF5-backed raster format from Landcare Research New Zealand and Aberystwyth University - Sam Gillingham and Peter Bunting - and it is one of the containers Hachure offers when you save a derived layer, because it carries raster attribute tables and arbitrary band metadata that a GeoTIFF handles awkwardly or not at all.
Data, and what its licence asks for Toitū Te Whenua Land Information New Zealand - aerial imagery, elevation, and the Topo50 1:50,000 map-sheet index and topographic tiles, all licensed CC BY 4.0. Attribution is required by that licence and is not optional; it appears in the app beneath the map that draws it, and here. Natural Earth (1:110 m and 1:10 m physical and cultural vectors, via nvkelso/natural-earth-vector) is public domain and backs the world map in the import picker. Apple MapKit provides the overview basemap where no national topographic service covers the ground.
Imagery used to build this page comes from the European Space Agency and the European Commission (Copernicus Sentinel-2, 2025), the U.S. Geological Survey and NASA (Landsat 4 and Landsat 8), Airbus Defence and Space (SPOT 6), and LINZ (aerial photography and LiDAR). The multi-coil electromagnetic-induction survey is an open example dataset in the channel convention EMagPy uses (McLachlan, Blanchy & Binley, 2020), which the proximal reader follows.
Apple frameworks do the rest of the work: Metal and MetalFX for rendering, MPSGraph for GPU Fourier transforms, Accelerate and vDSP for the CPU filters, Swift Charts for the spectral profile, Core Location and MapKit for the overview, Image I/O for rendered export, CryptoKit for source-file digests, and Foundation Models for the on-device assistant. No third-party Swift package is linked - a deliberate constraint rather than an accident, and the reason this list is as short as it is.
ReferencesWhat the methods come from
Where a panel offers a method, the method has a paper behind it and the code says which. These are the principal sources; the repository's own notes carry the rest, including the assessments of methods that were read and deliberately not built.
Imagery on this page is rendered from the project's own test data: Sentinel-2 L2A and Landsat 4 / 8 scenes over north-west Nelson and Golden Bay, New Zealand aerial photography and LiDAR published by Land Information New Zealand under CC BY 4.0, a SPOT 6 multispectral product, and an open multi-coil EMI survey. Each figure states its sensor, bands, ground sample distance and date.