Files
bachelorthesis/python-scripts/matplotlibLin.py
T
2024-06-28 12:23:19 +02:00

257 lines
8.3 KiB
Python

import math
import matplotlib.pyplot as plt
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFileDestination,
QgsProcessingParameterField,
QgsProcessingParameterString,
QgsProcessingParameterNumber,
QgsProcessingParameterBoolean)
import numpy as np
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use('Agg')
class matplotlibExp(QgsProcessingAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
X_VALUES = 'X_VALUES'
Y_VALUES = 'Y_VALUES'
X_MAX = 'X_MAX'
Y_MAX = 'Y_MAX'
B = 'B'
COLOR = 'COLOR'
TITLE = 'TITLE'
X_LABEL = 'X_LABEL'
Y_LABEL = 'Y_LABEL'
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return matplotlibExp()
def name(self):
return 'Exponential fit'
def displayName(self):
return self.tr('Exponential fit')
def group(self):
return self.tr('matplotlib vector')
def groupId(self):
return 'matplotlib vector'
def shortHelpString(self):
return self.tr("Generates a exponential fit for two selected numeric attributes from an input layer.")
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterFeatureSource(
self.INPUT,
self.tr('Input layer'),
[QgsProcessing.TypeVectorAnyGeometry]
)
)
self.addParameter(QgsProcessingParameterFileDestination(
'OUTPUT',
self.tr('Plot Output File'),
'PNG Files (*.png)'
)
)
self.addParameter(
QgsProcessingParameterField(
self.X_VALUES,
self.tr('X Values'),
None,
self.INPUT,
QgsProcessingParameterField.Numeric
)
)
self.addParameter(
QgsProcessingParameterField(
self.Y_VALUES,
self.tr('Y Values'),
None,
self.INPUT,
QgsProcessingParameterField.Numeric
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.X_MAX,
self.tr('X Max'),
type=QgsProcessingParameterNumber.Double,
optional=True
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.Y_MAX,
self.tr('Y Max'),
type=QgsProcessingParameterNumber.Double,
optional=True
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.B,
self.tr('b'),
type = QgsProcessingParameterNumber.Double,
optional=True
)
)
self.addParameter(
QgsProcessingParameterString(
self.COLOR,
self.tr('Color'),
defaultValue='blue'
)
)
self.addParameter(
QgsProcessingParameterString(
self.TITLE,
self.tr('Title'),
defaultValue=' '
)
)
self.addParameter(
QgsProcessingParameterString(
self.X_LABEL,
self.tr('X Label'),
defaultValue=' '
)
)
self.addParameter(
QgsProcessingParameterString(
self.Y_LABEL,
self.tr('Y Label'),
defaultValue=' '
)
)
@staticmethod
def exponential_decay(x, a, b, c):
return a * np.exp(b * x) + c
@staticmethod
def exponential_decay_no_offset(x,a,b):
return a * np.exp(b * x)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
x_field = self.parameterAsString(parameters, self.X_VALUES, context)
y_field = self.parameterAsString(parameters, self.Y_VALUES, context)
x_max = self.parameterAsDouble(parameters, self.X_MAX, context)
y_max = self.parameterAsDouble(parameters, self.Y_MAX, context)
b = self.parameterAsDouble(parameters, self.B, context)
color = self.parameterAsString(parameters, self.COLOR, context)
title = self.parameterAsString(parameters, self.TITLE, context)
x_label = self.parameterAsString(parameters, self.X_LABEL, context)
y_label = self.parameterAsString(parameters, self.Y_LABEL, context)
output = self.parameterAsFileOutput(parameters, self.OUTPUT, context)
feedback.pushInfo(f"Source: {source}")
feedback.pushInfo(f"x_field: {x_field}")
feedback.pushInfo(f"y_field: {y_field}")
feedback.pushInfo(f"x_max: {x_max}")
feedback.pushInfo(f"y_max: {y_max}")
feedback.pushInfo(f"title: {title}")
feedback.pushInfo(f"x_label: {x_label}")
feedback.pushInfo(f"y_label: {y_label}")
feedback.pushInfo(f"output: {output}")
x_values = []
y_values = []
for feature in source.getFeatures():
try:
x = float(feature[x_field])
y = float(feature[y_field])
if not (math.isnan(x) or math.isnan(y)):
x_values.append(x)
y_values.append(y)
except Exception as e:
continue
feedback.pushInfo(f"x_values: {x_values}")
feedback.pushInfo(f"y_values: {y_values}")
grouped_data = {}
for x, y in zip(x_values, y_values):
if x not in grouped_data:
grouped_data[x] = [y]
else:
grouped_data[x].append(y)
max_y_values = {x: max(y_list) for x, y_list in grouped_data.items()}
x_values_np = np.array(sorted(max_y_values.keys()))
y_values_np = np.array([max_y_values[x] for x in x_values_np])
positive_filter = y_values_np > 0
x_values_positive = x_values_np[positive_filter]
y_values_positive = y_values_np[positive_filter]
if np.any(y_values_positive.min() <= 0):
feedback.reportError("Negative values in y field")
return {}
initial_guess = [np.max(y_values_positive), b] # Assuming a starts at the max values, b is negative, c is zero
try:
popt, pcov = curve_fit(
matplotlibExp.exponential_decay_no_offset,
x_values_positive,
y_values_positive,
p0=initial_guess,
)
a_fit, b_fit = popt
x_axis = np.linspace(x_values_positive.min(), x_values_positive.max(),500)
y_fit = matplotlibExp.exponential_decay_no_offset(x_axis, a_fit, b_fit)
feedback.pushInfo(f"Inital guess: {initial_guess}")
feedback.pushInfo(f"Fit parameters: {popt}")
except RuntimeError as e:
feedback.reportError(f"Error during curve fitting: {e}")
return{}
try:
plt.scatter(x_values_positive, y_values_positive)
plt.plot(x_axis, y_fit, color=color, label='Fit Line')
plt.xlim(x_values_positive.min(), x_max if x_max is not None else x_values_positive.max())
plt.ylim(y_values_positive.min(), y_max if y_max is not None else y_values_positive.max())
plt.title(title)
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.legend()
plt.savefig(output)
plt.close()
feedback.pushInfo(f"Scatter plot saved to {output}")
return {self.OUTPUT: output}
except Exception as e:
feedback.reportError(f"Error: {e}")
return {}