Skip to content

Commit

Permalink
Port random selection algorithms to new API
Browse files Browse the repository at this point in the history
And heavily optimise random selection within subsets alg
  • Loading branch information
nyalldawson committed Aug 5, 2017
1 parent a64d199 commit 7ab8244
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 79 deletions.
7 changes: 4 additions & 3 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -99,6 +99,8 @@
from .RandomPointsExtent import RandomPointsExtent
from .RandomPointsLayer import RandomPointsLayer
from .RandomPointsPolygons import RandomPointsPolygons
from .RandomSelection import RandomSelection
from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets
from .RasterLayerStatistics import RasterLayerStatistics
from .RegularPoints import RegularPoints
from .ReverseLineDirection import ReverseLineDirection
Expand Down Expand Up @@ -135,8 +137,6 @@
from .ZonalStatistics import ZonalStatistics

# from .ExtractByLocation import ExtractByLocation
# from .RandomSelection import RandomSelection
# from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets
# from .SelectByLocation import SelectByLocation
# from .SpatialJoin import SpatialJoin
# from .GridLine import GridLine
Expand Down Expand Up @@ -185,7 +185,6 @@ def __init__(self):

def getAlgs(self):
# algs = [
# RandomSelection(), RandomSelectionWithinSubsets(),
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
Expand Down Expand Up @@ -270,6 +269,8 @@ def getAlgs(self):
RandomPointsExtent(),
RandomPointsLayer(),
RandomPointsPolygons(),
RandomSelection(),
RandomSelectionWithinSubsets(),
RasterLayerStatistics(),
RegularPoints(),
ReverseLineDirection(),
Expand Down
45 changes: 23 additions & 22 deletions python/plugins/processing/algs/qgis/RandomSelection.py
Expand Up @@ -30,13 +30,16 @@
import random

from qgis.PyQt.QtGui import QIcon
from qgis.core import QgsFeatureSink, QgsProcessingUtils
from qgis.core import (QgsFeatureSink,
QgsProcessingException,
QgsProcessingUtils,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterEnum,
QgsProcessingParameterNumber,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputVectorLayer)

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.outputs import OutputVector

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]

Expand All @@ -61,13 +64,14 @@ def initAlgorithm(self, config=None):
self.methods = [self.tr('Number of selected features'),
self.tr('Percentage of selected features')]

self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer')))
self.addParameter(ParameterSelection(self.METHOD,
self.tr('Method'), self.methods, 0))
self.addParameter(ParameterNumber(self.NUMBER,
self.tr('Number/percentage of selected features'), 0, None, 10))
self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection'), True))
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterEnum(self.METHOD,
self.tr('Method'), self.methods, False, 0))
self.addParameter(QgsProcessingParameterNumber(self.NUMBER,
self.tr('Number/percentage of selected features'), QgsProcessingParameterNumber.Integer,
10, False, 0.0, 999999999999.0))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (random)')))

def name(self):
return 'randomselection'
Expand All @@ -76,28 +80,25 @@ def displayName(self):
return self.tr('Random selection')

def processAlgorithm(self, parameters, context, feedback):
filename = self.getParameterValue(self.INPUT)
layer = QgsProcessingUtils.mapLayerFromString(filename, context)
method = self.getParameterValue(self.METHOD)
layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
method = self.parameterAsEnum(parameters, self.METHOD, context)

featureCount = layer.featureCount()
value = int(self.getParameterValue(self.NUMBER))

layer.removeSelection()
value = self.parameterAsInt(parameters, self.NUMBER, context)

if method == 0:
if value > featureCount:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr('Selected number is greater than feature count. '
'Choose a lower value and try again.'))
else:
if value > 100:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr("Percentage can't be greater than 100. Set a "
"different value and try again."))
value = int(round(value / 100.0, 4) * featureCount)

selran = random.sample(list(range(featureCount)), value)

layer.selectByIds(selran)
self.setOutputValue(self.OUTPUT, filename)
return {self.OUTPUT: parameters[self.INPUT]}
102 changes: 48 additions & 54 deletions python/plugins/processing/algs/qgis/RandomSelectionWithinSubsets.py
Expand Up @@ -31,15 +31,17 @@

from qgis.PyQt.QtGui import QIcon

from qgis.core import QgsFeature, QgsFeatureSink, QgsProcessingUtils

from qgis.core import (QgsFeatureRequest,
QgsProcessingException,
QgsProcessingUtils,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterEnum,
QgsProcessingParameterField,
QgsProcessingParameterNumber,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputVectorLayer)
from collections import defaultdict
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterSelection
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterTableField
from processing.core.outputs import OutputVector

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]

Expand All @@ -65,16 +67,17 @@ def initAlgorithm(self, config=None):
self.methods = [self.tr('Number of selected features'),
self.tr('Percentage of selected features')]

self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer')))
self.addParameter(ParameterTableField(self.FIELD,
self.tr('ID Field'), self.INPUT))
self.addParameter(ParameterSelection(self.METHOD,
self.tr('Method'), self.methods, 0))
self.addParameter(ParameterNumber(self.NUMBER,
self.tr('Number/percentage of selected features'), 1, None, 10))

self.addOutput(OutputVector(self.OUTPUT, self.tr('Selection stratified'), True))
self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterField(self.FIELD,
self.tr('ID field'), None, self.INPUT))
self.addParameter(QgsProcessingParameterEnum(self.METHOD,
self.tr('Method'), self.methods, False, 0))
self.addParameter(QgsProcessingParameterNumber(self.NUMBER,
self.tr('Number/percentage of selected features'),
QgsProcessingParameterNumber.Integer,
10, False, 0.0, 999999999999.0))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (stratified random)')))

def name(self):
return 'randomselectionwithinsubsets'
Expand All @@ -83,61 +86,52 @@ def displayName(self):
return self.tr('Random selection within subsets')

def processAlgorithm(self, parameters, context, feedback):
filename = self.getParameterValue(self.INPUT)

layer = QgsProcessingUtils.mapLayerFromString(filename, context)
field = self.getParameterValue(self.FIELD)
method = self.getParameterValue(self.METHOD)
layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
method = self.parameterAsEnum(parameters, self.METHOD, context)
field = self.parameterAsString(parameters, self.FIELD, context)

layer.removeSelection()
index = layer.fields().lookupField(field)

unique = QgsProcessingUtils.uniqueValues(layer, index, context)
unique = layer.uniqueValues(index)
featureCount = layer.featureCount()

value = int(self.getParameterValue(self.NUMBER))
value = self.parameterAsInt(parameters, self.NUMBER, context)
if method == 0:
if value > featureCount:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr('Selected number is greater that feature count. '
'Choose lesser value and try again.'))
else:
if value > 100:
raise GeoAlgorithmExecutionException(
raise QgsProcessingException(
self.tr("Percentage can't be greater than 100. Set a "
"different value and try again."))
value = value / 100.0

selran = []
inFeat = QgsFeature()

current = 0
total = 100.0 / (featureCount * len(unique)) if featureCount else 1

if not len(unique) == featureCount:
for i in unique:
features = QgsProcessingUtils.getFeatures(layer, context)
FIDs = []
for inFeat in features:
attrs = inFeat.attributes()
if attrs[index] == i:
FIDs.append(inFeat.id())
current += 1
feedback.setProgress(int(current * total))

if method == 1:
selValue = int(round(value * len(FIDs), 0))
else:
selValue = value

if selValue >= len(FIDs):
selFeat = FIDs
else:
selFeat = random.sample(FIDs, selValue)

selran.extend(selFeat)
classes = defaultdict(list)

features = layer.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([index]))

for i, feature in enumerate(features):
if feedback.isCanceled():
break

classes[feature.attributes()[index]].append(feature.id())
feedback.setProgress(int(i * total))

selran = []
for subset in classes.values():
if feedback.isCanceled():
break

selValue = value if method != 1 else int(round(value * len(subset), 0))
selran.extend(random.sample(subset, selValue))

layer.selectByIds(selran)
else:
layer.selectByIds(list(range(featureCount))) # FIXME: implies continuous feature ids

self.setOutputValue(self.OUTPUT, filename)
return {self.OUTPUT: parameters[self.INPUT]}

0 comments on commit 7ab8244

Please sign in to comment.