1const e=[{kind:"image",src:"/examples/sensors-04-depth-map.png",alt:"The project window: measured tiles on the left, smoothed picture on the right",caption:"What the project shows when you run it. A hand held 0.38 m in front of the sensor, a wall an even metre behind it. Left: the 64 measurements, holes left as holes. Right: the same 64 numbers smoothed into a picture, with the crosshair reading underneath."},{kind:"hardware",fallbackMd:"Hardware used: [ToF VL53L8CH USB](https://depz.ai/product/tof-sensor-vl53l8ch-usb) or [ToF VL53L8CX USB](https://depz.ai/product/tof-sensor-vl53l8cx-usb)."},{kind:"ul",items:["**Sensor:** {sensor} time-of-flight matrix","**What you get:** 64 distances at once â and the three facts that decide whether those distances mean anything at all."]},{kind:"p",text:"Everything here runs unchanged on both boards of this family: the VL53L8CH and the VL53L8CX carry the same 8Ã8 ranging matrix and speak to the SDK through the same class, so the code, the flags and the measured numbers below apply to either."},{kind:"h2",id:"why",text:"Why"},{kind:"p",text:"The ultrasonic sensor answered with one number. This one answers with 64: an 8Ã8 grid of tiny laser rangefinders behind a shared lens, each watching its own narrow slice of the scene, all firing fifteen times a second."},{kind:"p",text:"Reading them is not the hard part â the SDK hands over an 8Ã8 array. The hard part is that several of that array's properties are not what a first glance suggests, and every one of them below was found on the bench rather than assumed."},{kind:"h2",id:"the-status-byte-is-not-optional-reading",text:"The status byte is not optional reading"},{kind:"p",text:'A cell with nothing in front of it does not report "nothing". It reports a number, and the number is garbage.'},{kind:"p",text:"Pointed at a wall with a doorway off to one side, the cells looking through the doorway came back with `0.000`, `-0.010`, `-0.016` â and, at other moments, with a perfectly plausible `2.157`. Nothing about the value itself gives it away. A zero is not obviously wrong; two metres is not wrong at all if there happens to be a wall two metres down the corridor."},{kind:"p",text:"What gives it away is the status byte the frame carries for every cell:"},{kind:"table",headers:["status","meaning","usable"],rows:[["5","valid","yes"],["9","valid, but the return pulse was wide â two surfaces merged","yes"],["10","valid, target not seen in the previous frame","yes, but it flickers"],["4","the reading disagreed with the previous one","no"],["6","first frame â see below","no"],["255","no target","no"]]},{kind:"image",src:"/examples/sensors-04-flat-wall.png",alt:"The same frame with the validity filter on and off",caption:"One frame, drawn twice. Left: the three cells aimed at the doorway have nothing to reflect off and are left empty. Right: the same frame with the filter off â those cells turn out to hold `0.000`, `0.002` and `-0.007`, and the colour scale paints them **nearer than the actual hand**. That is
1what an unfiltered depth map looks like."},{kind:"p",text:"The project paints only 5 and 9. Status 10 is a real range too, but it is the first sighting of something that was not there a frame ago, and on a live map that is exactly the flicker you do not want painted. `--raw` turns the filter off, which is the fastest way to see why it exists."},{kind:"code",ref:"valid-status"},{kind:"code",ref:"read-map"},{kind:"h2",id:"two-views-because-neither-one-is-honest-alone",text:"Two views, because neither one is honest alone"},{kind:"p",text:"The window shows the same frame twice, and the difference between the halves is the point."},{kind:"p",text:"**The tiles are the data.** 64 cells, each with its number, cells without a usable reading left empty. The colour scale is fixed at 0.10â2.50 m and never moves, so a colour means the same distance in this frame and the next â a hand coming closer does not repaint the wall behind it."},{kind:"p",text:"**The picture is the room.** The same 64 numbers, holes filled in from their neighbours, smoothed up to 600 pixels, and â the part that actually makes shapes appear â the colour stretched over whatever this frame contains. A room a metre away spans about 20 cm of depth; on a scale that runs to 2.5 m all of it is one shade of yellow. Stretched over those 20 cm, the same data uses the whole ramp and a hand stands out from the wall behind it."},{kind:"p",text:"Both halves earn their place, and the picture is the one to distrust. Its colours mean something different every frame, and the cells it fills in were never measured â the tiles beside it are what says which is which."},{kind:"p",text:"The smoothing itself is deliberately the dull kind. Cubic interpolation looks better and was tried first, but on a frame with a hand in it 17 % of the pixels came out either nearer than the nearest measurement or farther than the farthest: the overshoot at every edge draws a halo that is not in the data. Linear leaves visible facets and invents nothing."},{kind:"code",ref:"smoothing"},{kind:"p",text:"What smoothing cannot do is add resolution â see the last section for how little there is. The picture shows **where** things are, never what they are."},{kind:"h2",id:"the-first-frame-is-empty-and-that-is-correct",text:"The first frame is empty, and that is correct"},{kind:"p",text:"Start ranging and the first frame arrives with all 64 cells stamped status 6. Nothing is broken. The sensor checks every reading against the previous one, and on the opening frame there is no previous one."},{kind:"p",text:"It costs one frame out of fifteen per second, so live modes simply paint it as an empty grid for a sixteenth of a second. `--flat` drops it and says so â counted as a sample, that one frame would make every single zone look like it had dropped a reading."},{kind:"code",ref:"first-frame"},{kind:"h2",id:"the-grid-arrives-turned-a-quarter-turn",text:"The grid arrives turned a quarter turn"},{kind:"p",text:"Zone 0 is a corner of the sensor die, not the top-left of the scene. On this board the raw index runs down the **right** edge of the field first."},{kind:"p",text:"This was measured, not read off a datasheet. The bench scene was a wall a metre away with a doorway opening to the right, and the wall's top-left corner leaning slightly towards the sensor:"},{kind:"ul",items:["the cells with no return â the doorway â arrived in raw **row 0**;","the nearest cell of all â the leaning corner â arrived at raw **(7, 0)**."]},{kind:"p",text:"Two facts, and between them the eight possible ways to lay out the grid collapse to one: rotate the raw array a quarter turn clockwise and the map reads the way a person standing behind the sensor sees the room, row 0 at the top, column 0 on the left. That is the whole of `GRID_QUARTER_TURNS` in the code."},{kind:"code",ref:"orientation"},{kind:"p",text:"Then it was confirmed the other way round, by putting a hand somewhere known and seeing where it came out. A hand held to the left of the axis filled **columns 0-3, every row** â a vertical stripe down the left of the map, which is what a hand and the forearm behind it look like when the field is only 25 cm wide at that distance. Up and down was checked in the live window, where the red patch moves the same way the hand does."},{kind:"p",text:"That forearm is worth knowing about before designing anything on top of the {sensor}: at half a metre the whole field is 41 cm across, so an arm reaching in does not point at a zone, it fills half the map."},{kind:"h2",id:"is-a-flat-wall-flat",text:"Is a flat wall flat?"},{kind:"p",text:"The zones fan out. The lens covers 45° by 45°, split into 8 columns and 8 rows of 5.62° each, so the corner zone looks 27° off the sensor's axis."},{kind:"code",ref:"zone-angle"},{kind:"p",text:"That matters more than it sounds. If each cell reported the distance along its own slanted line of sight, a wall that really is flat would arrive as a bowl: the corner cell has to look 1/cos 27° = **12 % farther** to reach the same wall. At a metre that is twelve centimetres â impossible to miss, and impossible to ignore when the following projects start comparing cells against each other."},{kind:"p",text:"`--flat` measures it. Zones are grouped into rings by how far off-axis they sit, and each ring is averaged â a sensor not perfectly square to the wall lifts one side of a ring by as much as it drops the other, so the tilt cancels and the slant, which lifts the whole ring at once, does not."},{kind:"code",ref:"rings"},{kind:"p",text:"Wall at 1.000 m by tape, 60 frames:"},{kind:"table",headers:["ring","off-axis","measured / centre","1/cos, if it were along the ray"],rows:[["1","4.0°","1.000","1.002"],["2","9.9°","0.999","1.015"],["3","16.1°","0.999","1.042"],["4","22.3°","0.998","1.082"]]},{kind:"p",text:"The outer ring should have been 8 % farther. It is 0.2 % nearer. **The {sensor} projects every reading onto its own axis before handing it over** â a flat wall arrives flat, and cells may be compared with each other directly. Nothing in this series needs a cosine correction."},{kind:"h2",id:"checked-against-a-tape",text:"Checked against a tape"},{kind:"p",text:"Same run, wall at 1.000 m measured from the front face of the board:"},{kind:"table",headers:["",""],rows:[["centre 2Ã2","**1.009 m** â 9 mm long"],["noise per zone over 60 frames","4.3 mm typical, 15.1 mm worst"],["zones reporting in all 60 frames","53 of 64"]]},{kind:"p",text:"The eleven unreliable zones are the ones aimed at the doorway, flickering between a far wall and no target at all â exactly what the status byte is for."},{kind:"p",text:"Nine millimetres is close enough that it could be the tape rather than the sensor: the ranging zero is the cover glass, not the board edge, and a metre measured to a board held by hand is not a metre measured to a lens."},{kind:"h2",id:"why-there-are-blobs-and-not-contours",text:"Why there are blobs and not contours"},{kind:"p",text:"The first thing anyone asks after seeing the smoothed picture is
1why the shapes are so vague. It is not the smoothing. It is the size of one cell."},{kind:"p",text:"The field is 45° wide whatever the distance, so it opens out as a fixed fraction of the range, and so does every cell in it:"},{kind:"table",headers:["distance","width of the whole field","width of one cell"],rows:[["0.25 m","0.21 m","2.6 cm"],["0.50 m","0.41 m","5.2 cm"],["1.00 m","0.83 m","**10.4 cm**"],["2.00 m","1.66 m","20.7 cm"],["4.00 m","3.31 m","41.4 cm"]]},{kind:"p",text:"At a metre, one cell is a 10 cm square. Anything narrower than that â a chair leg, a wrist, the edge of a table â cannot have an outline here, because there is no measurement narrower than 10 cm to draw it with."},{kind:"p",text:'And a cell that straddles an edge does not report the edge. It reports one number for its whole cone, somewhere between the near thing and the far thing; the sensor flags those cells status 9, "wide pulse". So even at full resolution the boundary of an object is smeared across an entire cell before any drawing starts.'},{kind:"p",text:"None of this is fixable downstream. Smoothing 64 measurements produces a smoother 64 measurements. Modules whose depth pictures show a recognisable chair are not drawing better â they have a hundred points across where this one has eight."},{kind:"p",text:"What this sensor is good at is the other question: not what shape is there, but **how far away it is**, 64 times at once, to a few millimetres. The next projects in this series are built on that, not on outlines."},{kind:"h2",id:"run-it",text:"Run it"},{kind:"code",ref:"run"},{kind:"p",text:"The window is the default here, where the ultrasonic projects default to text. Sixty four numbers refreshed fifteen times a second are a picture, and a picture read as a table of digits is not read at all. Over ssh there is no window to open, so the project notices and prints the text map instead."},{kind:"p",text:"The first second of every run is silent: the sensor boots with no firmware of its own, and ~84 KB of it is pushed over USB before ranging can start."},{kind:"h2",id:"the-complete-code",text:"The complete code"},{kind:"p",text:"Everything above is taken from one file, `tof_depth_map.py` â here it is in full:"},{kind:"code",ref:"full"},{kind:"p",text:"The complete, runnable code of this example project is on GitHub: [tof-depth-map on GitHub](https://github.com/depz-ai/depz-sensor-examples/tree/main/examples/04_tof_depth_map)."}];export{e as BLOCKS};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.