@@ -292,91 +292,3 @@ def test_process_with_tiles_pai_handles_low_top_height(mock_pipeline_cls, tmp_pa
292292
293293 created_tifs = list (out_dir .glob ("tile_*_pai.tif" ))
294294 assert len (created_tifs ) >= 1 , "Expected a PAI output tile even when top height < 1 m."
295-
296-
297- @patch ("pyforestscan.process.downsample_poisson" )
298- @patch ("pyforestscan.process.pdal.Pipeline" )
299- def test_process_with_tiles_by_flightline_sampling_applied (mock_pipeline_cls , mock_downsample , tmp_path ):
300- """
301- With by_flightline=True, Poisson sampling should be applied per PointSourceId group.
302- """
303- dtype = [("X" , "f8" ), ("Y" , "f8" ), ("HeightAboveGround" , "f8" ), ("PointSourceId" , "i4" )]
304- pts = np .zeros (120 , dtype = dtype )
305- pts ["X" ] = np .random .uniform (0 , 20 , size = 120 )
306- pts ["Y" ] = np .random .uniform (0 , 20 , size = 120 )
307- pts ["HeightAboveGround" ] = np .random .uniform (0 , 5 , size = 120 )
308- # Two flightlines
309- pts ["PointSourceId" ][:60 ] = 10
310- pts ["PointSourceId" ][60 :] = 11
311-
312- mock_pipeline = MagicMock ()
313- mock_pipeline .execute .return_value = True
314- mock_pipeline .arrays = [pts ]
315- mock_pipeline_cls .return_value = mock_pipeline
316-
317- call_counts = {10 : 0 , 11 : 0 }
318-
319- def _per_group (arrays , thin_radius ):
320- arr = arrays [0 ]
321- # Track which group is being thinned
322- psid = int (arr ["PointSourceId" ][0 ])
323- call_counts [psid ] += 1
324- return [arr [::2 ]]
325-
326- mock_downsample .side_effect = _per_group
327-
328- out_dir = tmp_path / "test_by_flightline"
329- out_dir .mkdir ()
330-
331- process_with_tiles (
332- ept_file = "fake_ept_path" ,
333- tile_size = (20 , 20 ),
334- output_path = str (out_dir ),
335- metric = "fhd" ,
336- voxel_size = (2 , 2 , 1 ),
337- buffer_size = 0.0 ,
338- srs = "EPSG:32610" ,
339- bounds = ([0 , 20 ], [0 , 20 ], [0 , 10 ]),
340- by_flightline = True ,
341- thin_radius = 0.5 ,
342- )
343-
344- # One downsample call per PSID
345- assert call_counts [10 ] == 1 and call_counts [11 ] == 1
346- created_tifs = list (out_dir .glob ("tile_*_fhd.tif" ))
347- assert len (created_tifs ) >= 1 , "Expected a single FHD output tile when processing by flightline."
348-
349-
350- @patch ("pyforestscan.process.pdal.Pipeline" )
351- def test_process_with_tiles_by_flightline_missing_dim (mock_pipeline_cls , tmp_path ):
352- """
353- If PointSourceId is missing and by_flightline=True, raise a clear error.
354- """
355- dtype = [("X" , "f8" ), ("Y" , "f8" ), ("HeightAboveGround" , "f8" )]
356- pts = np .zeros (40 , dtype = dtype )
357- pts ["X" ] = np .random .uniform (0 , 10 , size = 40 )
358- pts ["Y" ] = np .random .uniform (0 , 10 , size = 40 )
359- pts ["HeightAboveGround" ] = np .random .uniform (0 , 3 , size = 40 )
360-
361- mock_pipeline = MagicMock ()
362- mock_pipeline .execute .return_value = True
363- mock_pipeline .arrays = [pts ]
364- mock_pipeline_cls .return_value = mock_pipeline
365-
366- out_dir = tmp_path / "test_by_flightline_err"
367- out_dir .mkdir ()
368-
369- with pytest .raises (ValueError ):
370- process_with_tiles (
371- ept_file = "fake_ept_path" ,
372- tile_size = (20 , 20 ),
373- output_path = str (out_dir ),
374- metric = "pai" ,
375- voxel_size = (2 , 2 , 1 ),
376- voxel_height = 1.0 ,
377- buffer_size = 0.0 ,
378- srs = "EPSG:32610" ,
379- bounds = ([0 , 20 ], [0 , 20 ], [0 , 10 ]),
380- by_flightline = True ,
381- thin_radius = 0.3 ,
382- )
0 commit comments