From fc3d61aaea2c4a2c0c39b8129f64e4d0b920ad8b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 2 Dec 2020 08:52:10 +1000 Subject: [PATCH] Cleanups, unit tests --- ...gspointcloudattributebyramprenderer.sip.in | 37 ++- .../pointcloud/qgspointcloudrenderer.sip.in | 2 + .../qgspointcloudattributebyramprenderer.cpp | 14 +- .../qgspointcloudattributebyramprenderer.h | 37 ++- src/core/pointcloud/qgspointcloudrenderer.h | 2 + ...ointcloudattributebyramprendererwidget.cpp | 10 +- tests/src/python/CMakeLists.txt | 1 + ...st_qgspointcloudattributebyramprenderer.py | 258 ++++++++++++++++++ .../python/test_qgspointcloudrgbrenderer.py | 5 - .../expected_ramp_pointsize.png | Bin 0 -> 471523 bytes .../expected_ramp_render.png | Bin 0 -> 471523 bytes .../expected_ramp_render_crs_transform.png | Bin 0 -> 471523 bytes .../expected_ramp_zfilter.png | Bin 0 -> 471523 bytes 13 files changed, 331 insertions(+), 35 deletions(-) create mode 100644 tests/src/python/test_qgspointcloudattributebyramprenderer.py create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_ramp_pointsize/expected_ramp_pointsize.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_ramp_render/expected_ramp_render.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_ramp_render_crs_transform/expected_ramp_render_crs_transform.png create mode 100644 tests/testdata/control_images/pointcloudrenderer/expected_ramp_zfilter/expected_ramp_zfilter.png diff --git a/python/core/auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip.in index b48dff53f51..c07726167ec 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip.in @@ -75,23 +75,40 @@ Sets the color ramp ``shader`` function used to visualize the attribute. .. seealso:: :py:func:`colorRampShader` %End - double min() const; + double minimum() const; %Docstring -Returns min -%End - void setMin( double value ); -%Docstring -Sets min +Returns the minimum value for attributes which will be used by the color ramp shader. + +.. seealso:: :py:func:`setMinimum` + +.. seealso:: :py:func:`maximum` %End - double max() const; + void setMinimum( double minimum ); %Docstring -Returns max +Sets the ``minimum`` value for attributes which will be used by the color ramp shader. + +.. seealso:: :py:func:`minimum` + +.. seealso:: :py:func:`setMaximum` %End - void setMax( double value ); + double maximum() const; %Docstring -Sets max +Returns the maximum value for attributes which will be used by the color ramp shader. + +.. seealso:: :py:func:`setMaximum` + +.. seealso:: :py:func:`minimum` +%End + + void setMaximum( double maximum ); +%Docstring +Sets the ``maximum`` value for attributes which will be used by the color ramp shader. + +.. seealso:: :py:func:`maximum` + +.. seealso:: :py:func:`setMinimum` %End }; diff --git a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in index 6bf5c610fa4..771fca888b5 100644 --- a/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointcloudrenderer.sip.in @@ -135,6 +135,8 @@ Abstract base class for 2d point cloud renderers. if ( type == QLatin1String( "rgb" ) ) sipType = sipType_QgsPointCloudRgbRenderer; + else if ( type == QLatin1String( "ramp" ) ) + sipType = sipType_QgsPointCloudAttributeByRampRenderer; else sipType = 0; %End diff --git a/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp b/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp index 5ddb565fb00..23a6cae2d61 100644 --- a/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp +++ b/src/core/pointcloud/qgspointcloudattributebyramprenderer.cpp @@ -143,8 +143,8 @@ QgsPointCloudRenderer *QgsPointCloudAttributeByRampRenderer::create( QDomElement QDomElement elemShader = element.firstChildElement( QStringLiteral( "colorrampshader" ) ); r->mColorRampShader.readXml( elemShader ); - r->setMin( element.attribute( QStringLiteral( "min" ), QStringLiteral( "0" ) ).toDouble() ); - r->setMax( element.attribute( QStringLiteral( "max" ), QStringLiteral( "100" ) ).toDouble() ); + r->setMinimum( element.attribute( QStringLiteral( "min" ), QStringLiteral( "0" ) ).toDouble() ); + r->setMaximum( element.attribute( QStringLiteral( "max" ), QStringLiteral( "100" ) ).toDouble() ); r->restoreCommonProperties( element, context ); @@ -208,22 +208,22 @@ void QgsPointCloudAttributeByRampRenderer::setColorRampShader( const QgsColorRam mColorRampShader = shader; } -double QgsPointCloudAttributeByRampRenderer::min() const +double QgsPointCloudAttributeByRampRenderer::minimum() const { return mMin; } -void QgsPointCloudAttributeByRampRenderer::setMin( double value ) +void QgsPointCloudAttributeByRampRenderer::setMinimum( double minimum ) { - mMin = value; + mMin = minimum; } -double QgsPointCloudAttributeByRampRenderer::max() const +double QgsPointCloudAttributeByRampRenderer::maximum() const { return mMax; } -void QgsPointCloudAttributeByRampRenderer::setMax( double value ) +void QgsPointCloudAttributeByRampRenderer::setMaximum( double value ) { mMax = value; } diff --git a/src/core/pointcloud/qgspointcloudattributebyramprenderer.h b/src/core/pointcloud/qgspointcloudattributebyramprenderer.h index b7752eca5e6..633d4e17767 100644 --- a/src/core/pointcloud/qgspointcloudattributebyramprenderer.h +++ b/src/core/pointcloud/qgspointcloudattributebyramprenderer.h @@ -79,16 +79,37 @@ class CORE_EXPORT QgsPointCloudAttributeByRampRenderer : public QgsPointCloudRen */ void setColorRampShader( const QgsColorRampShader &shader ); - //! Returns min - double min() const; - //! Sets min - void setMin( double value ); + /** + * Returns the minimum value for attributes which will be used by the color ramp shader. + * + * \see setMinimum() + * \see maximum() + */ + double minimum() const; - //! Returns max - double max() const; + /** + * Sets the \a minimum value for attributes which will be used by the color ramp shader. + * + * \see minimum() + * \see setMaximum() + */ + void setMinimum( double minimum ); - //! Sets max - void setMax( double value ); + /** + * Returns the maximum value for attributes which will be used by the color ramp shader. + * + * \see setMaximum() + * \see minimum() + */ + double maximum() const; + + /** + * Sets the \a maximum value for attributes which will be used by the color ramp shader. + * + * \see maximum() + * \see setMinimum() + */ + void setMaximum( double maximum ); private: diff --git a/src/core/pointcloud/qgspointcloudrenderer.h b/src/core/pointcloud/qgspointcloudrenderer.h index 39a520706a1..73f958f25e7 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.h +++ b/src/core/pointcloud/qgspointcloudrenderer.h @@ -204,6 +204,8 @@ class CORE_EXPORT QgsPointCloudRenderer if ( type == QLatin1String( "rgb" ) ) sipType = sipType_QgsPointCloudRgbRenderer; + else if ( type == QLatin1String( "ramp" ) ) + sipType = sipType_QgsPointCloudAttributeByRampRenderer; else sipType = 0; SIP_END diff --git a/src/gui/pointcloud/qgspointcloudattributebyramprendererwidget.cpp b/src/gui/pointcloud/qgspointcloudattributebyramprendererwidget.cpp index f9f8a1d8047..ac4f28d2d42 100644 --- a/src/gui/pointcloud/qgspointcloudattributebyramprendererwidget.cpp +++ b/src/gui/pointcloud/qgspointcloudattributebyramprendererwidget.cpp @@ -63,8 +63,8 @@ QgsPointCloudRenderer *QgsPointCloudAttributeByRampRendererWidget::renderer() std::unique_ptr< QgsPointCloudAttributeByRampRenderer > renderer = qgis::make_unique< QgsPointCloudAttributeByRampRenderer >(); renderer->setAttribute( mAttributeComboBox->currentAttribute() ); - renderer->setMin( mMinSpin->value() ); - renderer->setMax( mMaxSpin->value() ); + renderer->setMinimum( mMinSpin->value() ); + renderer->setMaximum( mMaxSpin->value() ); renderer->setColorRampShader( mScalarColorRampShaderWidget->shader() ); @@ -90,11 +90,11 @@ void QgsPointCloudAttributeByRampRendererWidget::setFromRenderer( const QgsPoint { mAttributeComboBox->setAttribute( mbcr->attribute() ); - mMinSpin->setValue( mbcr->min() ); - mMaxSpin->setValue( mbcr->max() ); + mMinSpin->setValue( mbcr->minimum() ); + mMaxSpin->setValue( mbcr->maximum() ); whileBlocking( mScalarColorRampShaderWidget )->setFromShader( mbcr->colorRampShader() ); - whileBlocking( mScalarColorRampShaderWidget )->setMinimumMaximum( mbcr->min(), mbcr->max() ); + whileBlocking( mScalarColorRampShaderWidget )->setMinimumMaximum( mbcr->minimum(), mbcr->maximum() ); } else { diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 91d985de1b7..c10554ebbf6 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -206,6 +206,7 @@ ADD_PYTHON_TEST(PyQgsPanelWidget test_qgspanelwidget.py) ADD_PYTHON_TEST(PyQgsPanelWidgetStack test_qgspanelwidgetstack.py) ADD_PYTHON_TEST(PyQgsPathResolver test_qgspathresolver.py) ADD_PYTHON_TEST(PyQgsPoint test_qgspoint.py) +ADD_PYTHON_TEST(PyQgsPointCloudAttributeByRampRenderer test_qgspointcloudattributebyramprenderer.py) ADD_PYTHON_TEST(PyQgsPointCloudAttributeComboBox test_qgspointcloudattributecombobox.py) ADD_PYTHON_TEST(PyQgsPointCloudAttributeModel test_qgspointcloudattributemodel.py) ADD_PYTHON_TEST(PyQgsPointCloudRgbRenderer test_qgspointcloudrgbrenderer.py) diff --git a/tests/src/python/test_qgspointcloudattributebyramprenderer.py b/tests/src/python/test_qgspointcloudattributebyramprenderer.py new file mode 100644 index 00000000000..162f2a60cfa --- /dev/null +++ b/tests/src/python/test_qgspointcloudattributebyramprenderer.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsPointCloudAttributeByRampRenderer + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '09/11/2020' +__copyright__ = 'Copyright 2020, The QGIS Project' + +import qgis # NOQA + +from qgis.core import ( + QgsProviderRegistry, + QgsPointCloudLayer, + QgsPointCloudAttributeByRampRenderer, + QgsReadWriteContext, + QgsRenderContext, + QgsPointCloudRenderContext, + QgsVector3D, + QgsMultiRenderChecker, + QgsMapSettings, + QgsRectangle, + QgsUnitTypes, + QgsMapUnitScale, + QgsCoordinateReferenceSystem, + QgsDoubleRange, + QgsColorRampShader, + QgsStyle +) + +from qgis.PyQt.QtCore import QDir, QSize +from qgis.PyQt.QtGui import QPainter +from qgis.PyQt.QtXml import QDomDocument + +from qgis.testing import start_app, unittest +from utilities import unitTestDataPath + +start_app() + + +class TestQgsPointCloudAttributeByRampRenderer(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.report = "

Python QgsPointCloudAttributeByRampRenderer Tests

\n" + + @classmethod + def tearDownClass(cls): + report_file_path = "%s/qgistest.html" % QDir.tempPath() + with open(report_file_path, 'a') as report_file: + report_file.write(cls.report) + + def testBasic(self): + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('attr') + self.assertEqual(renderer.attribute(), 'attr') + renderer.setMinimum(5) + self.assertEqual(renderer.minimum(), 5) + renderer.setMaximum(15) + self.assertEqual(renderer.maximum(), 15) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(20, 30, ramp) + renderer.setColorRampShader(shader) + self.assertEqual(renderer.colorRampShader().minimumValue(), 20) + self.assertEqual(renderer.colorRampShader().maximumValue(), 30) + + renderer.setMaximumScreenError(18) + renderer.setMaximumScreenErrorUnit(QgsUnitTypes.RenderInches) + renderer.setPointSize(13) + renderer.setPointSizeUnit(QgsUnitTypes.RenderPoints) + renderer.setPointSizeMapUnitScale(QgsMapUnitScale(1000, 2000)) + + rr = renderer.clone() + self.assertEqual(rr.maximumScreenError(), 18) + self.assertEqual(rr.maximumScreenErrorUnit(), QgsUnitTypes.RenderInches) + self.assertEqual(rr.pointSize(), 13) + self.assertEqual(rr.pointSizeUnit(), QgsUnitTypes.RenderPoints) + self.assertEqual(rr.pointSizeMapUnitScale().minScale, 1000) + self.assertEqual(rr.pointSizeMapUnitScale().maxScale, 2000) + + self.assertEqual(rr.attribute(), 'attr') + self.assertEqual(rr.minimum(), 5) + self.assertEqual(rr.maximum(), 15) + self.assertEqual(rr.colorRampShader().minimumValue(), 20) + self.assertEqual(rr.colorRampShader().maximumValue(), 30) + self.assertEqual(rr.colorRampShader().sourceColorRamp().color1(), + renderer.colorRampShader().sourceColorRamp().color1()) + self.assertEqual(rr.colorRampShader().sourceColorRamp().color2(), + renderer.colorRampShader().sourceColorRamp().color2()) + + doc = QDomDocument("testdoc") + elem = renderer.save(doc, QgsReadWriteContext()) + + r2 = QgsPointCloudAttributeByRampRenderer.create(elem, QgsReadWriteContext()) + self.assertEqual(r2.maximumScreenError(), 18) + self.assertEqual(r2.maximumScreenErrorUnit(), QgsUnitTypes.RenderInches) + self.assertEqual(r2.pointSize(), 13) + self.assertEqual(r2.pointSizeUnit(), QgsUnitTypes.RenderPoints) + self.assertEqual(r2.pointSizeMapUnitScale().minScale, 1000) + self.assertEqual(r2.pointSizeMapUnitScale().maxScale, 2000) + self.assertEqual(r2.attribute(), 'attr') + self.assertEqual(r2.minimum(), 5) + self.assertEqual(r2.maximum(), 15) + self.assertEqual(r2.colorRampShader().minimumValue(), 20) + self.assertEqual(r2.colorRampShader().maximumValue(), 30) + self.assertEqual(r2.colorRampShader().sourceColorRamp().color1(), + renderer.colorRampShader().sourceColorRamp().color1()) + self.assertEqual(r2.colorRampShader().sourceColorRamp().color2(), + renderer.colorRampShader().sourceColorRamp().color2()) + + def testUsedAttributes(self): + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('attr') + + rc = QgsRenderContext() + prc = QgsPointCloudRenderContext(rc, QgsVector3D(), QgsVector3D()) + + self.assertEqual(renderer.usedAttributes(prc), {'attr'}) + + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRender(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('Intensity') + renderer.setMinimum(200) + renderer.setMaximum(1000) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(200, 1000, ramp) + shader.classifyColorRamp() + renderer.setColorRampShader(shader) + + layer.setRenderer(renderer) + + layer.renderer().setPointSize(2) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(498061, 7050991, 498069, 7050999)) + mapsettings.setLayers([layer]) + + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlPathPrefix('pointcloudrenderer') + renderchecker.setControlName('expected_ramp_render') + result = renderchecker.runTest('expected_ramp_render') + TestQgsPointCloudAttributeByRampRenderer.report += renderchecker.report() + self.assertTrue(result) + + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderCrsTransform(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('Intensity') + renderer.setMinimum(200) + renderer.setMaximum(1000) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(200, 1000, ramp) + shader.classifyColorRamp() + renderer.setColorRampShader(shader) + + layer.setRenderer(renderer) + layer.renderer().setPointSize(2) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326')) + mapsettings.setExtent(QgsRectangle(152.980508492, -26.662023491, 152.980586020, -26.662071137)) + mapsettings.setLayers([layer]) + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlPathPrefix('pointcloudrenderer') + renderchecker.setControlName('expected_ramp_render_crs_transform') + result = renderchecker.runTest('expected_ramp_render_crs_transform') + TestQgsPointCloudAttributeByRampRenderer.report += renderchecker.report() + self.assertTrue(result) + + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderPointSize(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('Intensity') + renderer.setMinimum(200) + renderer.setMaximum(1000) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(200, 1000, ramp) + shader.classifyColorRamp() + renderer.setColorRampShader(shader) + + layer.setRenderer(renderer) + layer.renderer().setPointSize(.15) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMapUnits) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(498061, 7050991, 498069, 7050999)) + mapsettings.setLayers([layer]) + + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlPathPrefix('pointcloudrenderer') + renderchecker.setControlName('expected_ramp_pointsize') + result = renderchecker.runTest('expected_ramp_pointsize') + TestQgsPointCloudAttributeByRampRenderer.report += renderchecker.report() + self.assertTrue(result) + + @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') + def testRenderZRange(self): + layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/sunshine-coast/ept.json', 'test', 'ept') + self.assertTrue(layer.isValid()) + + renderer = QgsPointCloudAttributeByRampRenderer() + renderer.setAttribute('Intensity') + renderer.setMinimum(200) + renderer.setMaximum(1000) + ramp = QgsStyle.defaultStyle().colorRamp("Viridis") + shader = QgsColorRampShader(200, 1000, ramp) + shader.classifyColorRamp() + renderer.setColorRampShader(shader) + + layer.setRenderer(renderer) + layer.renderer().setPointSize(2) + layer.renderer().setPointSizeUnit(QgsUnitTypes.RenderMillimeters) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(400, 400)) + mapsettings.setOutputDpi(96) + mapsettings.setDestinationCrs(layer.crs()) + mapsettings.setExtent(QgsRectangle(498061, 7050991, 498069, 7050999)) + mapsettings.setLayers([layer]) + mapsettings.setZRange(QgsDoubleRange(74.7, 75)) + + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlPathPrefix('pointcloudrenderer') + renderchecker.setControlName('expected_ramp_zfilter') + result = renderchecker.runTest('expected_ramp_zfilter') + TestQgsPointCloudAttributeByRampRenderer.report += renderchecker.report() + self.assertTrue(result) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgspointcloudrgbrenderer.py b/tests/src/python/test_qgspointcloudrgbrenderer.py index d0594bcc231..1a8144b6168 100644 --- a/tests/src/python/test_qgspointcloudrgbrenderer.py +++ b/tests/src/python/test_qgspointcloudrgbrenderer.py @@ -149,11 +149,6 @@ class TestQgsPointCloudRgbRenderer(unittest.TestCase): self.assertEqual(renderer.usedAttributes(prc), {'r', 'g', 'b'}) - # if context is filtering by z, we also need the z attribute - rc.setZRange(QgsDoubleRange(1, 10)) - prc = QgsPointCloudRenderContext(rc, QgsVector3D(), QgsVector3D()) - self.assertEqual(renderer.usedAttributes(prc), {'r', 'g', 'b', 'Z'}) - @unittest.skipIf('ept' not in QgsProviderRegistry.instance().providerList(), 'EPT provider not available') def testRender(self): layer = QgsPointCloudLayer(unitTestDataPath() + '/point_clouds/ept/rgb/ept.json', 'test', 'ept') diff --git a/tests/testdata/control_images/pointcloudrenderer/expected_ramp_pointsize/expected_ramp_pointsize.png b/tests/testdata/control_images/pointcloudrenderer/expected_ramp_pointsize/expected_ramp_pointsize.png new file mode 100644 index 0000000000000000000000000000000000000000..58bfc0b774a07381a154b6a3d063459be7cf7d78 GIT binary patch literal 471523 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 z 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 literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/pointcloudrenderer/expected_ramp_render/expected_ramp_render.png b/tests/testdata/control_images/pointcloudrenderer/expected_ramp_render/expected_ramp_render.png new file mode 100644 index 0000000000000000000000000000000000000000..b1956d31f26da5779aa63672a6259481995df3f3 GIT binary patch literal 471523 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 literal 0 HcmV?d00001 diff --git a/tests/testdata/control_images/pointcloudrenderer/expected_ramp_render_crs_transform/expected_ramp_render_crs_transform.png b/tests/testdata/control_images/pointcloudrenderer/expected_ramp_render_crs_transform/expected_ramp_render_crs_transform.png new file mode 100644 index 0000000000000000000000000000000000000000..47eea09c69b8738295ccdbf7d404dd3c4c86f327 GIT binary patch literal 471523 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%0D&Y4T>8*m%~p5; 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 TPQPg)r6orkwRq#