formatting issues and introduction of pelt algorithm to display suggested transition times
This commit is contained in:
parent
1c8775c807
commit
b0d94f2318
3 changed files with 81 additions and 13 deletions
|
|
@ -20,6 +20,7 @@ import uuid
|
|||
import zlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import ruptures as rpt
|
||||
|
||||
import jinja2
|
||||
import loky
|
||||
|
|
@ -352,14 +353,46 @@ class CO2Data(BaseRequestHandler):
|
|||
"""
|
||||
pass
|
||||
|
||||
def generate_ventilation_plot(self, CO2Data, transition_times = None, ventilation_values = None):
|
||||
def find_change_points_with_pelt(self, CO2_data: dict):
|
||||
"""
|
||||
Perform change point detection using Pelt algorithm from ruptures library with pen=15.
|
||||
Returns a list of tuples containing (index, X-axis value) for the detected significant changes.
|
||||
"""
|
||||
|
||||
times = CO2_data['times']
|
||||
CO2_values = CO2_data['CO2']
|
||||
|
||||
if len(times) != len(CO2_values):
|
||||
raise ValueError("times and CO2 values must have the same length.")
|
||||
|
||||
# Convert the input lists to numpy arrays for use with the ruptures library
|
||||
times_np = np.array(times)
|
||||
CO2_np = np.array(CO2_values)
|
||||
|
||||
# Define the model for change point detection (Radial Basis Function kernel)
|
||||
model = "rbf"
|
||||
|
||||
# Fit the Pelt algorithm to the data with the specified model
|
||||
algo = rpt.Pelt(model=model).fit(CO2_np)
|
||||
|
||||
# Predict change points using the Pelt algorithm with a penalty value of 15
|
||||
result = algo.predict(pen=15)
|
||||
|
||||
return [times_np[idx] for idx in result[:-1]]
|
||||
|
||||
def generate_ventilation_plot(self, CO2_data: dict, transition_times: typing.Optional[list] = None, ventilation_values: typing.Optional[list] = None):
|
||||
times = CO2_data['times']
|
||||
CO2_data = CO2_data['CO2']
|
||||
|
||||
fig = plt.figure(figsize=(7, 4), dpi=110)
|
||||
plt.plot(CO2Data['times'], CO2Data['CO2'])
|
||||
if (transition_times and ventilation_values):
|
||||
for index, time in enumerate(transition_times[:-1]):
|
||||
plt.plot(times, CO2_data)
|
||||
|
||||
if (transition_times):
|
||||
for index, time in enumerate(transition_times):
|
||||
plt.axvline(x = time, color = 'grey', linewidth=0.5, linestyle='--')
|
||||
y_location = (CO2Data['CO2'][min(range(len(CO2Data['times'])), key=lambda i: abs(CO2Data['times'][i]-time))])
|
||||
plt.text(x = time + 0.04, y = y_location, s=round(ventilation_values[index], 2))
|
||||
if ventilation_values:
|
||||
y_location = (CO2_data[min(range(len(times)), key=lambda i: abs(times[i]-time))])
|
||||
plt.text(x = time + 0.04, y = y_location, s="{:.2g}".format(ventilation_values[index]))
|
||||
plt.xlabel('Time of day')
|
||||
plt.ylabel('Concentration (ppm)')
|
||||
return img2base64(_figure2bytes(fig))
|
||||
|
|
@ -377,8 +410,8 @@ class CO2Data(BaseRequestHandler):
|
|||
self.finish(json.dumps(response_json))
|
||||
return
|
||||
|
||||
if endpoint == 'plot':
|
||||
self.finish({'CO2_plot': self.generate_ventilation_plot(form.CO2_data)})
|
||||
if endpoint.rstrip('/') == 'plot':
|
||||
self.finish({'CO2_plot': self.generate_ventilation_plot(form.CO2_data, self.find_change_points_with_pelt(form.CO2_data))})
|
||||
else:
|
||||
executor = loky.get_reusable_executor(
|
||||
max_workers=self.settings['handler_worker_pool_size'],
|
||||
|
|
@ -392,7 +425,7 @@ class CO2Data(BaseRequestHandler):
|
|||
result = dict(report.CO2_fit_params())
|
||||
result['fitting_ventilation_type'] = form.fitting_ventilation_type
|
||||
result['transition_times'] = report.ventilation_transition_times
|
||||
result['CO2_plot'] = self.generate_ventilation_plot(form.CO2_data, report.ventilation_transition_times, result['ventilation_values'])
|
||||
result['CO2_plot'] = self.generate_ventilation_plot(form.CO2_data, report.ventilation_transition_times[:-1], result['ventilation_values'])
|
||||
self.finish(result)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,37 @@ function validateCO2Form() {
|
|||
// validate input format
|
||||
try {
|
||||
const parsedValue = JSON.parse(element.value);
|
||||
if (!Array.isArray(parsedValue)) {
|
||||
if (Array.isArray(parsedValue)) {
|
||||
if (parsedValue.length <= 1) {
|
||||
insertErrorFor(
|
||||
$("#DIVCO2_fitting_result"),
|
||||
`'${element.name}' must have more than one element.<br />`
|
||||
);
|
||||
submit = false;
|
||||
}
|
||||
else {
|
||||
const infected_finish = $(`[name=infected_finish]`)[0].value;
|
||||
const exposed_finish = $(`[name=exposed_finish]`)[0].value;
|
||||
|
||||
const [hours_infected, minutes_infected] = infected_finish.split(":").map(Number);
|
||||
const elapsed_time_infected = hours_infected * 60 + minutes_infected;
|
||||
|
||||
const [hours_exposed, minutes_exposed] = exposed_finish.split(":").map(Number);
|
||||
const elapsed_time_exposed = hours_exposed * 60 + minutes_exposed;
|
||||
|
||||
const max_presence_time = Math.max(elapsed_time_infected, elapsed_time_exposed);
|
||||
const max_transition_time = parsedValue[parsedValue.length - 1] * 60;
|
||||
|
||||
if (max_transition_time < max_presence_time) {
|
||||
insertErrorFor(
|
||||
$("#DIVCO2_fitting_result"),
|
||||
`The last transition time (${parsedValue[parsedValue.length - 1]}) should be after the last presence time (${max_presence_time / 60}).<br />`
|
||||
);
|
||||
submit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
insertErrorFor(
|
||||
$("#DIVCO2_fitting_result"),
|
||||
`'${element.name}' must be a list.</br>`
|
||||
|
|
@ -325,11 +355,15 @@ function submitFittingAlgorithm(url) {
|
|||
.then((response) => response.json())
|
||||
.then((json_response) => {
|
||||
displayFittingData(json_response);
|
||||
// Hide the suggestion transition lines warning
|
||||
$("#suggestion_lines_txt").hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clearFittingResultComponent() {
|
||||
// Add the warning suggestion line
|
||||
$("#suggestion_lines_txt").show();
|
||||
// Remove all the previously generated fitting elements
|
||||
$("#generate_fitting_data").prop("disabled", true);
|
||||
$("#CO2_fitting_result").val("");
|
||||
|
|
|
|||
|
|
@ -200,13 +200,13 @@
|
|||
<div>
|
||||
<input type="radio" id="no_ventilation" name="ventilation_type" value="no_ventilation" checked>
|
||||
<label for="no_ventilation">No ventilation</label>
|
||||
<input class="ml-2" type="radio" id="mechanical_ventilation" name="ventilation_type" value="mechanical_ventilation" data-enables="#DIVmechanical_ventilation">
|
||||
<input class="ml-3" type="radio" id="mechanical_ventilation" name="ventilation_type" value="mechanical_ventilation" data-enables="#DIVmechanical_ventilation">
|
||||
<label for="mechanical_ventilation">Mechanical</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="ml-0">
|
||||
<input type="radio" id="natural_ventilation" name="ventilation_type" value="natural_ventilation" data-enables="#DIVnatural_ventilation" data-toggle="modal" data-target="#warning_modal">
|
||||
<label for="natural_ventilation">Natural</label>
|
||||
<input class="ml-2" type="radio" id="from_fitting" name="ventilation_type" value="from_fitting" data-enables="#DIVfrom_fitting">
|
||||
<input class="ml-3" type="radio" id="from_fitting" name="ventilation_type" value="from_fitting" data-enables="#DIVfrom_fitting">
|
||||
<label for="from_fitting">Fitting</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -339,6 +339,7 @@
|
|||
|
||||
<div id="DIVCO2_fitting_to_submit" style="display: none">
|
||||
<img id="CO2_data_plot"/><br>
|
||||
<p id="suggestion_lines_txt" class="text-danger mb-3 text-center">The dashed lines are suggestions for the ventilation transition times (Pelt algorithm).</p>
|
||||
<strong>Ventilation scheme:</strong>
|
||||
<div>
|
||||
<input class="ml-2" type="radio" id="fitting_natural_ventilation" name="fitting_ventilation_type" value='fitting_natural_ventilation' checked="checked" data-enables="#DIVfitting_natural_ventilation" form="not-submitted" >
|
||||
|
|
|
|||
Loading…
Reference in a new issue