basic firebrand recognition

Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
master
michael 2022-11-14 22:47:43 -08:00
parent 59e0f2d861
commit 1de21e93e1
8 changed files with 264 additions and 47 deletions

View File

@ -28,7 +28,11 @@ gray = cv.copyMakeBorder(
value=0
)
contours = measure.find_contours(array=gray, level=100)
# cv.imshow('gray', gray)
# cv.waitKey(0)
# contours = measure.find_contours(array=gray, level=100)
_img, contours = cv.findContours(gray, cv.RETR_LIST, cv.CHAIN_APPROX_NONE)[0]
fig, ax = plt.subplots()
ax.imshow(gray, cmap=plt.cm.gray, alpha=1)
@ -39,11 +43,34 @@ def calculate_area(countour):
return cv.contourArea(c)
def center_of_mass(X):
x = X[:,0]
y = X[:,1]
g = (x[:-1]*y[1:] - x[1:]*y[:-1])
A = 0.5*g.sum()
cx = ((x[:-1] + x[1:])*g).sum()
cy = ((y[:-1] + y[1:])*g).sum()
return 1./(6*A)*np.array([cx,cy])
img_new = cv.cvtColor(gray, cv.COLOR_GRAY2BGR)
for contour in contours:
area = calculate_area(contour)
if calculate_area(contour) > 250:
ax.plot(contour[:, 1], contour[:, 0], linewidth=0.5, color='orangered')
# if area > 250:
# cnt = np.array(contour).reshape((-1, 1, 2)).astype(np.int32)
# cv.drawContours(img_new, [cnt], -1, (0, 200, 255), thickness=10)
cv.drawContours(img_new, [contour], -1, (0, 200, 255), thickness=3)
# ax.plot(contour[:, 1], contour[:, 0], linewidth=0.5, color='orangered')
# cv.imshow('contours', img_new)
# cv.waitKey(0)
cv.imwrite("firebrand_contours_opencv.png", img_new)
ax.axis('image')
ax.set_xticks([])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,11 +1,10 @@
from flask import Flask, render_template, request, send_file
import numpy as np
from plotly_util import generate_plotly_temperature_pdf
from ratio_pyrometry import ratio_pyrometry_pipeline
from size_projection import get_projected_area
import base64
import cv2 as cv
import plotly.figure_factory as ff
import pandas as pd
app = Flask(
__name__,
@ -21,7 +20,7 @@ def index():
def ratio_pyro():
f = request.files['file']
f_bytes = np.fromstring(f.read(), np.uint8)
img_orig, img_res, key, ptemps = ratio_pyrometry_pipeline(
img_orig, img_res, key, ptemps, indiv_firebrands = ratio_pyrometry_pipeline(
f_bytes,
ISO=float(request.form['iso']),
I_Darkcurrent=float(request.form['i_darkcurrent']),
@ -31,47 +30,28 @@ def ratio_pyro():
MIN_TEMP=float(request.form['min_temp']),
smoothing_radius=int(request.form['smoothing_radius']),
key_entries=int(request.form['legend_entries']),
eqn_scaling_factor=float(request.form['equation_scaling_factor'])
eqn_scaling_factor=float(request.form['equation_scaling_factor']),
firebrand_min_intensity_threshold=float(request.form['intensity_threshold']),
firebrand_min_area=float(request.form['min_area']),
)
# get base64 encoded images
img_orig_b64 = base64.b64encode(cv.imencode('.png', img_orig)[1]).decode(encoding='utf-8')
img_res_b64 = base64.b64encode(cv.imencode('.png', img_res)[1]).decode(encoding='utf-8')
# generate prob. distribution histogram & return embed
fig = ff.create_distplot(
[ptemps],
group_labels=[f.filename],
show_rug=False,
show_hist=False,
)
fig.update_layout(
autosize=False,
width=800,
height=600,
)
fig.update_xaxes(
title_text="Temperature (°C)",
)
fig.update_yaxes(
title_text="Probability (1/°C)",
)
freq_plot = fig.to_html()
ptemps_list = [ptemps]
# create csv-formatted stuff
# currently only supports 1 firebrand (grabs first object in plot).
plot_data=fig.to_dict()
x_data = plot_data["data"][0]["x"]
y_data = plot_data["data"][0]["y"]
for i in range(len(indiv_firebrands)):
# base64 encode image data
brand_data = indiv_firebrands[i]
unencoded = brand_data["img_data"]
brand_data["img_data"] = base64.b64encode(cv.imencode('.png', unencoded)[1]).decode(encoding='utf-8')
indiv_firebrands[i] = brand_data
tdata = [["Temperature", "Frequency"]]
for i in range(len(x_data)):
r = []
r.append(x_data[i])
r.append(y_data[i])
tdata.append(r)
# add ptemp data to list
ptemps_list.append(brand_data["ptemps"])
csvstr = pd.DataFrame(tdata).to_csv(index=False, header=False)
freq_plot, csvstrs = generate_plotly_temperature_pdf(ptemps_list)
return render_template(
'pyrometry-results.html',
@ -79,7 +59,8 @@ def ratio_pyro():
img_res_b64=img_res_b64,
legend=key,
freq_plot=freq_plot,
csv_data=csvstr
csv_data=csvstrs[0],
individual_firebrands=indiv_firebrands,
)

64
plotly_util.py Normal file
View File

@ -0,0 +1,64 @@
from typing import List
import plotly.figure_factory as ff
import pandas as pd
def generate_plotly_temperature_pdf(ptemps_list: List[list]):
"""
Generate plotly graph HTML & raw CSV data for temperature pdf
ptemps: pixel temperature LIST in order of:
- Ptemps of firebrands "overview" image
- Ptemps list for each individual firebrand
plotname: what to call the plot
Returns result in form (plot_html, csv_data)
"""
# generate prob. distribution histogram & return embed
labels = ["Full Image"]
for i in range(len(ptemps_list[1:])):
labels.append(f"Firebrand {i+1}")
labels.reverse()
fig = ff.create_distplot(
ptemps_list,
group_labels=labels,
show_rug=False,
show_hist=False,
)
fig.update_layout(
autosize=False,
width=800,
height=600,
)
fig.update_xaxes(
title_text="Temperature (°C)",
)
fig.update_yaxes(
title_text="Probability (1/°C)",
)
freq_plot = fig.to_html()
# create csv-formatted stuff
csvstrs = []
plot_data=fig.to_dict()
for i in range(len(plot_data["data"])):
x_data = plot_data["data"][i]["x"]
y_data = plot_data["data"][i]["y"]
tdata = [["Temperature", "Frequency"]]
for i in range(len(x_data)):
r = []
r.append(x_data[i])
r.append(y_data[i])
tdata.append(r)
csvstr = pd.DataFrame(tdata).to_csv(index=False, header=False)
csvstrs.append(csvstr)
return (
freq_plot,
csvstrs
)

View File

@ -3,6 +3,7 @@ from multiprocessing.sharedctypes import Value
import cv2 as cv
import numpy as np
from numba import jit
from skimage import measure
@jit(nopython=True)
def rg_ratio_normalize(
@ -85,11 +86,104 @@ def ratio_pyrometry_pipeline(
smoothing_radius: int,
key_entries: int,
eqn_scaling_factor: float,
# firebrand detection
firebrand_min_intensity_threshold: float,
firebrand_min_area: float
):
# read image & crop
img_orig = cv.imdecode(file_bytes, cv.IMREAD_UNCHANGED)
# ---------------------------------------------------------
# -- Firebrand detection
# ---------------------------------------------------------
img = cv.copyMakeBorder(
img_orig,
20,
20,
20,
20,
cv.BORDER_CONSTANT,
value=0
)
retval, thresh_gray = cv.threshold(img, firebrand_min_intensity_threshold, 255, cv.THRESH_BINARY)
kernel = np.ones((7, 7), np.uint8)
image = cv.morphologyEx(thresh_gray, cv.MORPH_CLOSE, kernel, iterations=1)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
retval, gray = cv.threshold(gray, 0, 255, cv.THRESH_BINARY)
contours = measure.find_contours(array=gray, level=100)
def calculate_area(countour):
c = np.expand_dims(countour.astype(np.float32), 1)
c = cv.UMat(c)
return cv.contourArea(c)
individual_firebrands = []
for contour in contours:
if calculate_area(contour) > firebrand_min_area:
mask = np.zeros(img.shape[0:2], dtype='uint8')
cv.fillPoly(mask, pts=np.int32([np.flip(contour, 1)]), color=(255,255,255))
retval, mask = cv.threshold(mask, 0, 255, cv.THRESH_BINARY)
#apply the mask to the img
masked = cv.bitwise_and(img, img, mask=mask)
masked_ratio_rg, ptemps_indiv = rg_ratio_normalize(
masked,
I_Darkcurrent,
f_stop,
exposure_time,
ISO,
MIN_TEMP,
MAX_TEMP,
eqn_scaling_factor,
)
# build & apply smoothing conv kernel
k = []
for i in range(smoothing_radius):
k.append([1/(smoothing_radius**2) for i in range(smoothing_radius)])
kernel = np.array(k)
masked_ratio_rg = cv.filter2D(src=masked_ratio_rg, ddepth=-1, kernel=kernel)
# write colormapped image
masked_ratio_rg_jet = cv.applyColorMap(masked_ratio_rg, cv.COLORMAP_JET)
# Generate key
step = (MAX_TEMP - MIN_TEMP) / (key_entries-1)
temps = []
key_img_arr = [[]]
for i in range(key_entries):
res_temp = MIN_TEMP + (i * step)
res_color = scale_temp(res_temp, MIN_TEMP, MAX_TEMP)
temps.append(math.floor(res_temp))
key_img_arr[0].append([res_color, res_color, res_color])
key_img = np.array(key_img_arr).astype(np.uint8)
key_img_jet = cv.applyColorMap(key_img, cv.COLORMAP_JET)
tempkey = {}
for i in range(len(temps)):
c = key_img_jet[0][i]
tempkey[temps[i]] = f"rgb({c[2]}, {c[1]}, {c[0]})"
individual_firebrands.append({
"img_data": masked_ratio_rg_jet,
"legend": tempkey,
"ptemps": ptemps_indiv
})
img, ptemps = rg_ratio_normalize(
img_orig,
I_Darkcurrent,
@ -112,7 +206,9 @@ def ratio_pyrometry_pipeline(
# write colormapped image
img_jet = cv.applyColorMap(img, cv.COLORMAP_JET)
# --- Generate temperature key ---
# ---------------------------------------------------------
# -- Generate temperature key
# ---------------------------------------------------------
# Generate key
step = (MAX_TEMP - MIN_TEMP) / (key_entries-1)
@ -133,4 +229,4 @@ def ratio_pyrometry_pipeline(
tempkey[temps[i]] = f"rgb({c[2]}, {c[1]}, {c[0]})"
# original, transformed, legend
return img_orig, img_jet, tempkey, ptemps
return img_orig, img_jet, tempkey, ptemps, individual_firebrands

View File

@ -50,8 +50,13 @@
<h4>Firebrand Detection Settings</h4>
<label for="edge_amp">Edge Amplification</label>
<input type="number" name="edge_amp" value="10", step="0.1"/>
<label for="intensity_threshold">intensity threshold (0-255)</label>
<input type="number" name="intensity_threshold" value="250", step="0.1"/>
<br>
<label for="min_area">min area (removes small particles)</label>
<input type="number" name="min_area" value="115", step="0.1"/>
<br>
<h4>Output Settings</h4>

View File

@ -7,6 +7,9 @@
{% block content %}
<div style="display:flex; flex-direction: column;">
<h2>General Results</h2>
<table class="img-table">
<tr>
<th class="img-table-heading">Input Image</th>
@ -40,6 +43,44 @@
</tr>
</table>
<h2>Individual Firebrands</h2>
<table>
<tr>
<th>Output Heatmap</th>
<th>Legend</th>
</tr>
{% for item in individual_firebrands %}
<tr>
{# output heatmap #}
<td>
<img class="img-out" src="data:image/png;base64,{{ item['img_data'] }}" alt="result image">
</td>
</td>
{# legend #}
<td>
<h3>Firebrand {{ loop.index }}</h3>
<table class="legend" id="legend">
<tr>
<th class="legend-heading">Color</th>
<th class="legend-heading">Temperature</th>
</tr>
{% for temp, color in item["legend"].items() %}
<tr>
<td class="legend-cell"><div style="width:30px;height:20px;background-color:{{ color }};"></div></td>
<td class="legend-cell">{{ temp }}°C</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</table>
<br>
{# Temperature Frequency Plot #}
<div style="display: flex; flex-direction: row; align-items: center;">
@ -50,6 +91,9 @@ onclick="saveData(`{{csv_data}}`, 'temperature-data.csv')">Download Data as CSV<
</div>
{{ freq_plot | safe }}
<!-- Firebrands: {{ individual_firebrands }} -->
</div>
{% endblock %}