1# -*- coding: utf-8 -*-
2"""QGIS Unit tests for QgsPointCloudRgbRenderer
3
4.. note:: This program is free software; you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by
6the Free Software Foundation; either version 2 of the License, or
7(at your option) any later version.
8"""
9__author__ = 'Nyall Dawson'
10__date__ = '09/11/2020'
11__copyright__ = 'Copyright 2020, The QGIS Project'
12
13import qgis  # NOQA
14from qgis.PyQt.QtCore import QDir, QSize
15from qgis.PyQt.QtGui import QPainter
16from qgis.PyQt.QtXml import QDomDocument
17from qgis.core import (
18    QgsProviderRegistry,
19    QgsPointCloudLayer,
20    QgsPointCloudRgbRenderer,
21    QgsReadWriteContext,
22    QgsRenderContext,
23    QgsPointCloudRenderContext,
24    QgsVector3D,
25    QgsMultiRenderChecker,
26    QgsMapSettings,
27    QgsRectangle,
28    QgsContrastEnhancement,
29    QgsUnitTypes,
30    QgsMapUnitScale,
31    QgsCoordinateReferenceSystem,
32    QgsDoubleRange,
33    QgsPointCloudRenderer,
34    QgsMapClippingRegion,
35    QgsGeometry
36)
37from qgis.testing import start_app, unittest
38
39from utilities import unitTestDataPath
40
41start_app()
42
43
44class TestQgsPointCloudRgbRenderer(unittest.TestCase):
45
46    @classmethod
47    def setUpClass(cls):
48        cls.report = "<h1>Python QgsPointCloudRgbRenderer Tests</h1>\n"
49
50    @classmethod
51    def tearDownClass(cls):
52        report_file_path = "%s/qgistest.html" % QDir.tempPath()
53        with open(report_file_path, 'a') as report_file:
54            report_file.write(cls.report)
55
56    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
57    def testSetLayer(self):
58        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
59        self.assertTrue(layer.isValid())
60
61        # test that a point cloud with RGB attributes is automatically assigned the RGB renderer by default
62        self.assertIsInstance(layer.renderer(), QgsPointCloudRgbRenderer)
63
64        # for this point cloud, we should default to 0-255 ranges (ie. no contrast enhancement)
65        self.assertIsNone(layer.renderer().redContrastEnhancement())
66        self.assertIsNone(layer.renderer().greenContrastEnhancement())
67        self.assertIsNone(layer.renderer().blueContrastEnhancement())
68
69    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
70    def testSetLayer16(self):
71        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb16/ept.json', 'test', 'ept')
72        self.assertTrue(layer.isValid())
73
74        # test that a point cloud with RGB attributes is automatically assigned the RGB renderer by default
75        self.assertIsInstance(layer.renderer(), QgsPointCloudRgbRenderer)
76
77        # for this point cloud, we should default to 0-65024 ranges with contrast enhancement
78        self.assertEqual(layer.renderer().redContrastEnhancement().minimumValue(), 0)
79        self.assertEqual(layer.renderer().redContrastEnhancement().maximumValue(), 65535.0)
80        self.assertEqual(layer.renderer().redContrastEnhancement().contrastEnhancementAlgorithm(),
81                         QgsContrastEnhancement.StretchToMinimumMaximum)
82        self.assertEqual(layer.renderer().greenContrastEnhancement().minimumValue(), 0)
83        self.assertEqual(layer.renderer().greenContrastEnhancement().maximumValue(), 65535.0)
84        self.assertEqual(layer.renderer().greenContrastEnhancement().contrastEnhancementAlgorithm(),
85                         QgsContrastEnhancement.StretchToMinimumMaximum)
86        self.assertEqual(layer.renderer().blueContrastEnhancement().minimumValue(), 0)
87        self.assertEqual(layer.renderer().blueContrastEnhancement().maximumValue(), 65535.0)
88        self.assertEqual(layer.renderer().blueContrastEnhancement().contrastEnhancementAlgorithm(),
89                         QgsContrastEnhancement.StretchToMinimumMaximum)
90
91    def testBasic(self):
92        renderer = QgsPointCloudRgbRenderer()
93        renderer.setBlueAttribute('b')
94        self.assertEqual(renderer.blueAttribute(), 'b')
95        renderer.setGreenAttribute('g')
96        self.assertEqual(renderer.greenAttribute(), 'g')
97        renderer.setRedAttribute('r')
98        self.assertEqual(renderer.redAttribute(), 'r')
99
100        redce = QgsContrastEnhancement()
101        redce.setMinimumValue(100)
102        redce.setMaximumValue(120)
103        redce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchAndClipToMinimumMaximum)
104        renderer.setRedContrastEnhancement(redce)
105
106        greence = QgsContrastEnhancement()
107        greence.setMinimumValue(130)
108        greence.setMaximumValue(150)
109        greence.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
110        renderer.setGreenContrastEnhancement(greence)
111
112        bluece = QgsContrastEnhancement()
113        bluece.setMinimumValue(170)
114        bluece.setMaximumValue(190)
115        bluece.setContrastEnhancementAlgorithm(QgsContrastEnhancement.ClipToMinimumMaximum)
116        renderer.setBlueContrastEnhancement(bluece)
117
118        renderer.setMaximumScreenError(18)
119        renderer.setMaximumScreenErrorUnit(QgsUnitTypes.RenderInches)
120        renderer.setPointSize(13)
121        renderer.setPointSizeUnit(QgsUnitTypes.RenderPoints)
122        renderer.setPointSizeMapUnitScale(QgsMapUnitScale(1000, 2000))
123
124        rr = renderer.clone()
125        self.assertEqual(rr.maximumScreenError(), 18)
126        self.assertEqual(rr.maximumScreenErrorUnit(), QgsUnitTypes.RenderInches)
127        self.assertEqual(rr.pointSize(), 13)
128        self.assertEqual(rr.pointSizeUnit(), QgsUnitTypes.RenderPoints)
129        self.assertEqual(rr.pointSizeMapUnitScale().minScale, 1000)
130        self.assertEqual(rr.pointSizeMapUnitScale().maxScale, 2000)
131
132        self.assertEqual(rr.blueAttribute(), 'b')
133        self.assertEqual(rr.greenAttribute(), 'g')
134        self.assertEqual(rr.redAttribute(), 'r')
135        self.assertEqual(rr.redContrastEnhancement().minimumValue(), 100)
136        self.assertEqual(rr.redContrastEnhancement().maximumValue(), 120)
137        self.assertEqual(rr.redContrastEnhancement().contrastEnhancementAlgorithm(),
138                         QgsContrastEnhancement.StretchAndClipToMinimumMaximum)
139        self.assertEqual(rr.greenContrastEnhancement().minimumValue(), 130)
140        self.assertEqual(rr.greenContrastEnhancement().maximumValue(), 150)
141        self.assertEqual(rr.greenContrastEnhancement().contrastEnhancementAlgorithm(),
142                         QgsContrastEnhancement.StretchToMinimumMaximum)
143        self.assertEqual(rr.blueContrastEnhancement().minimumValue(), 170)
144        self.assertEqual(rr.blueContrastEnhancement().maximumValue(), 190)
145        self.assertEqual(rr.blueContrastEnhancement().contrastEnhancementAlgorithm(),
146                         QgsContrastEnhancement.ClipToMinimumMaximum)
147
148        doc = QDomDocument("testdoc")
149        elem = renderer.save(doc, QgsReadWriteContext())
150
151        r2 = QgsPointCloudRgbRenderer.create(elem, QgsReadWriteContext())
152        self.assertEqual(r2.maximumScreenError(), 18)
153        self.assertEqual(r2.maximumScreenErrorUnit(), QgsUnitTypes.RenderInches)
154        self.assertEqual(r2.pointSize(), 13)
155        self.assertEqual(r2.pointSizeUnit(), QgsUnitTypes.RenderPoints)
156        self.assertEqual(r2.pointSizeMapUnitScale().minScale, 1000)
157        self.assertEqual(r2.pointSizeMapUnitScale().maxScale, 2000)
158
159        self.assertEqual(r2.blueAttribute(), 'b')
160        self.assertEqual(r2.greenAttribute(), 'g')
161        self.assertEqual(r2.redAttribute(), 'r')
162        self.assertEqual(r2.redContrastEnhancement().minimumValue(), 100)
163        self.assertEqual(r2.redContrastEnhancement().maximumValue(), 120)
164        self.assertEqual(r2.redContrastEnhancement().contrastEnhancementAlgorithm(),
165                         QgsContrastEnhancement.StretchAndClipToMinimumMaximum)
166        self.assertEqual(r2.greenContrastEnhancement().minimumValue(), 130)
167        self.assertEqual(r2.greenContrastEnhancement().maximumValue(), 150)
168        self.assertEqual(r2.greenContrastEnhancement().contrastEnhancementAlgorithm(),
169                         QgsContrastEnhancement.StretchToMinimumMaximum)
170        self.assertEqual(r2.blueContrastEnhancement().minimumValue(), 170)
171        self.assertEqual(r2.blueContrastEnhancement().maximumValue(), 190)
172        self.assertEqual(r2.blueContrastEnhancement().contrastEnhancementAlgorithm(),
173                         QgsContrastEnhancement.ClipToMinimumMaximum)
174
175    def testUsedAttributes(self):
176        renderer = QgsPointCloudRgbRenderer()
177        renderer.setBlueAttribute('b')
178        renderer.setGreenAttribute('g')
179        renderer.setRedAttribute('r')
180
181        rc = QgsRenderContext()
182        prc = QgsPointCloudRenderContext(rc, QgsVector3D(), QgsVector3D(), 1, 0)
183
184        self.assertEqual(renderer.usedAttributes(prc), {'r', 'g', 'b'})
185
186    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
187    def testRender(self):
188        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
189        self.assertTrue(layer.isValid())
190
191        layer.renderer().setPointSize(2)
192        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
193
194        mapsettings = QgsMapSettings()
195        mapsettings.setOutputSize(QSize(400, 400))
196        mapsettings.setOutputDpi(96)
197        mapsettings.setDestinationCrs(layer.crs())
198        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
199        mapsettings.setLayers([layer])
200
201        renderchecker = QgsMultiRenderChecker()
202        renderchecker.setMapSettings(mapsettings)
203        renderchecker.setControlPathPrefix('pointcloudrenderer')
204        renderchecker.setControlName('expected_rgb_render')
205        result = renderchecker.runTest('expected_rgb_render')
206        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
207        self.assertTrue(result)
208
209    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
210    def testRenderCircles(self):
211        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
212        self.assertTrue(layer.isValid())
213
214        layer.renderer().setPointSize(3)
215        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
216        layer.renderer().setPointSymbol(QgsPointCloudRenderer.Circle)
217
218        mapsettings = QgsMapSettings()
219        mapsettings.setOutputSize(QSize(400, 400))
220        mapsettings.setOutputDpi(96)
221        mapsettings.setDestinationCrs(layer.crs())
222        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
223        mapsettings.setLayers([layer])
224
225        renderchecker = QgsMultiRenderChecker()
226        renderchecker.setMapSettings(mapsettings)
227        renderchecker.setControlPathPrefix('pointcloudrenderer')
228        renderchecker.setControlName('expected_rgb_circle_render')
229        result = renderchecker.runTest('expected_rgb_circle_render')
230        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
231        self.assertTrue(result)
232
233    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
234    def testRenderCrsTransform(self):
235        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
236        self.assertTrue(layer.isValid())
237
238        layer.renderer().setPointSize(2)
239        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
240
241        mapsettings = QgsMapSettings()
242        mapsettings.setOutputSize(QSize(400, 400))
243        mapsettings.setOutputDpi(96)
244        mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
245        mapsettings.setExtent(QgsRectangle(152.977434544, -26.663017454, 152.977424882, -26.663009624))
246        mapsettings.setLayers([layer])
247        renderchecker = QgsMultiRenderChecker()
248        renderchecker.setMapSettings(mapsettings)
249        renderchecker.setControlPathPrefix('pointcloudrenderer')
250        renderchecker.setControlName('expected_rgb_render_crs_transform')
251        result = renderchecker.runTest('expected_rgb_render_crs_transform')
252        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
253        self.assertTrue(result)
254
255    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
256    def testRenderWithContrast(self):
257        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
258        self.assertTrue(layer.isValid())
259
260        layer.renderer().setPointSize(2)
261        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
262
263        redce = QgsContrastEnhancement()
264        redce.setMinimumValue(100)
265        redce.setMaximumValue(120)
266        redce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
267        layer.renderer().setRedContrastEnhancement(redce)
268
269        greence = QgsContrastEnhancement()
270        greence.setMinimumValue(130)
271        greence.setMaximumValue(150)
272        greence.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
273        layer.renderer().setGreenContrastEnhancement(greence)
274
275        bluece = QgsContrastEnhancement()
276        bluece.setMinimumValue(170)
277        bluece.setMaximumValue(190)
278        bluece.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
279        layer.renderer().setBlueContrastEnhancement(bluece)
280
281        mapsettings = QgsMapSettings()
282        mapsettings.setOutputSize(QSize(400, 400))
283        mapsettings.setOutputDpi(96)
284        mapsettings.setDestinationCrs(layer.crs())
285        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
286        mapsettings.setLayers([layer])
287
288        renderchecker = QgsMultiRenderChecker()
289        renderchecker.setMapSettings(mapsettings)
290        renderchecker.setControlPathPrefix('pointcloudrenderer')
291        renderchecker.setControlName('expected_rgb_contrast')
292        result = renderchecker.runTest('expected_rgb_contrast')
293        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
294        self.assertTrue(result)
295
296    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
297    def testRenderOpacity(self):
298        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
299        self.assertTrue(layer.isValid())
300
301        layer.renderer().setPointSize(2)
302        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
303
304        layer.setOpacity(0.5)
305
306        mapsettings = QgsMapSettings()
307        mapsettings.setOutputSize(QSize(400, 400))
308        mapsettings.setOutputDpi(96)
309        mapsettings.setDestinationCrs(layer.crs())
310        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
311        mapsettings.setLayers([layer])
312
313        renderchecker = QgsMultiRenderChecker()
314        renderchecker.setMapSettings(mapsettings)
315        renderchecker.setControlPathPrefix('pointcloudrenderer')
316        renderchecker.setControlName('expected_opacity')
317        result = renderchecker.runTest('expected_opacity')
318        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
319        self.assertTrue(result)
320
321    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
322    def testRenderBlendMode(self):
323        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
324        self.assertTrue(layer.isValid())
325
326        layer.renderer().setPointSize(2)
327        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
328
329        layer.setBlendMode(QPainter.CompositionMode_ColorBurn)
330
331        mapsettings = QgsMapSettings()
332        mapsettings.setOutputSize(QSize(400, 400))
333        mapsettings.setOutputDpi(96)
334        mapsettings.setDestinationCrs(layer.crs())
335        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
336        mapsettings.setLayers([layer])
337
338        renderchecker = QgsMultiRenderChecker()
339        renderchecker.setMapSettings(mapsettings)
340        renderchecker.setControlPathPrefix('pointcloudrenderer')
341        renderchecker.setControlName('expected_blendmode')
342        result = renderchecker.runTest('expected_blendmode')
343        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
344        self.assertTrue(result)
345
346    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
347    def testRenderPointSize(self):
348        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
349        self.assertTrue(layer.isValid())
350
351        layer.renderer().setPointSize(0.05)
352        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMapUnits)
353
354        mapsettings = QgsMapSettings()
355        mapsettings.setOutputSize(QSize(400, 400))
356        mapsettings.setOutputDpi(96)
357        mapsettings.setDestinationCrs(layer.crs())
358        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
359        mapsettings.setLayers([layer])
360
361        renderchecker = QgsMultiRenderChecker()
362        renderchecker.setMapSettings(mapsettings)
363        renderchecker.setControlPathPrefix('pointcloudrenderer')
364        renderchecker.setControlName('expected_pointsize')
365        result = renderchecker.runTest('expected_pointsize')
366        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
367        self.assertTrue(result)
368
369    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
370    def testRenderZRange(self):
371        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
372        self.assertTrue(layer.isValid())
373
374        layer.renderer().setPointSize(2)
375        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
376
377        mapsettings = QgsMapSettings()
378        mapsettings.setOutputSize(QSize(400, 400))
379        mapsettings.setOutputDpi(96)
380        mapsettings.setDestinationCrs(layer.crs())
381        mapsettings.setExtent(QgsRectangle(497753.5, 7050887.5, 497754.6, 7050888.6))
382        mapsettings.setLayers([layer])
383        mapsettings.setZRange(QgsDoubleRange(1.1, 1.2))
384
385        renderchecker = QgsMultiRenderChecker()
386        renderchecker.setMapSettings(mapsettings)
387        renderchecker.setControlPathPrefix('pointcloudrenderer')
388        renderchecker.setControlName('expected_zfilter')
389        result = renderchecker.runTest('expected_zfilter')
390        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
391        self.assertTrue(result)
392
393    @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available')
394    def testRenderClipRegion(self):
395        layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept')
396        self.assertTrue(layer.isValid())
397
398        layer.renderer().setPointSize(2)
399        layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters)
400
401        mapsettings = QgsMapSettings()
402        mapsettings.setOutputSize(QSize(400, 400))
403        mapsettings.setOutputDpi(96)
404        mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
405        mapsettings.setExtent(QgsRectangle(152.977434544, -26.663017454, 152.977424882, -26.663009624))
406        mapsettings.setLayers([layer])
407
408        region = QgsMapClippingRegion(QgsGeometry.fromWkt(
409            'Polygon ((152.97742833685992991 -26.66301088198133584, 152.97742694456141521 -26.66301085776744983, 152.97742676295726483 -26.66301358182974468, 152.97742895431403554 -26.66301349708113833, 152.97742833685992991 -26.66301088198133584))'))
410        region.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.ClipPainterOnly)
411        region2 = QgsMapClippingRegion(QgsGeometry.fromWkt(
412            'Polygon ((152.97743215054714483 -26.66301111201326535, 152.97742715037946937 -26.66301116044103736, 152.97742754990858316 -26.66301436878107367, 152.97743264693181686 -26.66301491359353193, 152.97743215054714483 -26.66301111201326535))'))
413        region2.setFeatureClip(QgsMapClippingRegion.FeatureClippingType.ClipToIntersection)
414        mapsettings.addClippingRegion(region)
415        mapsettings.addClippingRegion(region2)
416
417        renderchecker = QgsMultiRenderChecker()
418        renderchecker.setMapSettings(mapsettings)
419        renderchecker.setControlPathPrefix('pointcloudrenderer')
420        renderchecker.setControlName('expected_clip_region')
421        result = renderchecker.runTest('expected_clip_region')
422        TestQgsPointCloudRgbRenderer.report += renderchecker.report()
423        self.assertTrue(result)
424
425
426if __name__ == '__main__':
427    unittest.main()
428