almost-working prototype
parent
63eb8bdb85
commit
bd3569bace
28
README.md
28
README.md
|
@ -1,5 +1,19 @@
|
||||||
# Pyrometry image processing
|
# Pyrometry image processing
|
||||||
|
|
||||||
|
## Developing the web frontend
|
||||||
|
|
||||||
|
To serve in production:
|
||||||
|
|
||||||
|
```
|
||||||
|
gunicorn flask_frontend:app
|
||||||
|
```
|
||||||
|
|
||||||
|
To autoreload on source file changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
gunicorn flask_frontend:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
## Temperature maps
|
## Temperature maps
|
||||||
|
|
||||||
**Grayscale pyrometry:** currently basic; uses grayscale opencv import, then just applies a jet filter. Doesn't yet copy the full impl in the paper.
|
**Grayscale pyrometry:** currently basic; uses grayscale opencv import, then just applies a jet filter. Doesn't yet copy the full impl in the paper.
|
||||||
|
@ -8,14 +22,14 @@
|
||||||
|
|
||||||
**Test image:**
|
**Test image:**
|
||||||
|
|
||||||
![](01-0001.png)
|
![](examples/01-0001-cropped.png)
|
||||||
|
|
||||||
**Grayscale pyrometry result:**
|
**Ratio pyrometry result (with convolutional smoothing):**
|
||||||
|
|
||||||
![](01-0001-transformed-grayscale.png)
|
|
||||||
|
|
||||||
**Ratio pyrometry result (with 2x2 convolutional smoothing):**
|
|
||||||
|
|
||||||
According to general researcher consensus, ratio pyrometry is supposed to be more accurate.
|
According to general researcher consensus, ratio pyrometry is supposed to be more accurate.
|
||||||
|
|
||||||
![](01-0001-cropped-transformed-ratio.png)
|
![](examples/01-0001-cropped-transformed-ratio.png)
|
||||||
|
|
||||||
|
**Grayscale pyrometry result:**
|
||||||
|
|
||||||
|
![](examples/01-0001-transformed-grayscale.png)
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
<table class="legend">
|
|
||||||
<tr>
|
|
||||||
<th>Color</th>
|
|
||||||
<th>Temperature</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="legend-cell"><div style="width:20px;height:20px;background-color:rgb(256, 176, 0);"></div></td>
|
|
||||||
<td class="legend-cell">0°C</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.legend-cell {
|
|
||||||
padding: 4px 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,16 +1,34 @@
|
||||||
from flask import Flask, render_template, request
|
from flask import Flask, render_template, request
|
||||||
|
import numpy as np
|
||||||
|
from ratio_pyrometry import ratio_pyrometry_pipeline
|
||||||
|
import base64
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.jinja2')
|
||||||
|
|
||||||
@app.route('/ratio_pyro', methods=['POST'])
|
@app.route('/ratio_pyro', methods=['POST'])
|
||||||
def ratio_pyro():
|
def ratio_pyro():
|
||||||
f = request.files['file']
|
f = request.files['file']
|
||||||
I_Darkcurrent = 150.5
|
f_bytes = np.fromstring(f.read(), np.uint8)
|
||||||
exposure_time = 0.500
|
img_orig, img_res, key = ratio_pyrometry_pipeline(
|
||||||
f_stop = 2.4
|
f_bytes,
|
||||||
ISO = 64
|
ISO=float(request.form['iso']),
|
||||||
return 200
|
I_Darkcurrent=float(request.form['i_darkcurrent']),
|
||||||
|
exposure_time=float(request.form['exposure_time']),
|
||||||
|
f_stop=float(request.form['f_stop']),
|
||||||
|
MAX_TEMP=float(request.form['max_temp']),
|
||||||
|
MIN_TEMP=float(request.form['min_temp'])
|
||||||
|
)
|
||||||
|
|
||||||
|
img_orig_bytes = base64.urlsafe_b64encode(img_orig)
|
||||||
|
img_res_bytes = base64.urlsafe_b64encode(img_res)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'results.jinja2',
|
||||||
|
img_orig_bytes=img_orig_bytes,
|
||||||
|
img_res_bytes=img_res_bytes,
|
||||||
|
legend=key
|
||||||
|
)
|
||||||
|
|
|
@ -4,25 +4,11 @@ import numpy as np
|
||||||
from numba import jit
|
from numba import jit
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# camera settings
|
|
||||||
file = '01-0001.png'
|
|
||||||
I_Darkcurrent = 150.5
|
|
||||||
exposure_time = 0.500
|
|
||||||
f_stop = 2.4
|
|
||||||
ISO = 64 # basically brightness
|
|
||||||
|
|
||||||
# pyrometry config
|
|
||||||
MAX_TEMP = 1200
|
|
||||||
MIN_TEMP = 60
|
|
||||||
# original range from paper
|
|
||||||
# MAX_GR_RATIO = 1200
|
|
||||||
# MIN_GR_RATIO = 600
|
|
||||||
|
|
||||||
# Cropping config
|
# Cropping config
|
||||||
x1 = 420
|
# x1 = 420
|
||||||
x2 = 1200
|
# x2 = 1200
|
||||||
y1 = 400
|
# y1 = 400
|
||||||
y2 = -1
|
# y2 = -1
|
||||||
|
|
||||||
# post-processing
|
# post-processing
|
||||||
smoothing_radius = 2
|
smoothing_radius = 2
|
||||||
|
@ -32,7 +18,15 @@ key_entries = 6
|
||||||
|
|
||||||
|
|
||||||
@jit(nopython=True)
|
@jit(nopython=True)
|
||||||
def rg_ratio_normalize(imgarr):
|
def rg_ratio_normalize(
|
||||||
|
imgarr,
|
||||||
|
I_Darkcurrent,
|
||||||
|
f_stop,
|
||||||
|
exposure_time,
|
||||||
|
ISO,
|
||||||
|
MIN_TEMP,
|
||||||
|
MAX_TEMP
|
||||||
|
):
|
||||||
# set max & min to most extreme values,
|
# set max & min to most extreme values,
|
||||||
# work up & down respectively from there
|
# work up & down respectively from there
|
||||||
tmin = MAX_TEMP
|
tmin = MAX_TEMP
|
||||||
|
@ -42,8 +36,10 @@ def rg_ratio_normalize(imgarr):
|
||||||
for i in range(len(imgarr)):
|
for i in range(len(imgarr)):
|
||||||
for j in range(len(imgarr[i])):
|
for j in range(len(imgarr[i])):
|
||||||
px = imgarr[i][j]
|
px = imgarr[i][j]
|
||||||
r_norm = normalization_func(px[0])
|
|
||||||
g_norm = normalization_func(px[1])
|
# normalize R & G pixels
|
||||||
|
r_norm = (px[0] - I_Darkcurrent) * (f_stop ** 2) / (ISO * exposure_time)
|
||||||
|
g_norm = (px[1] - I_Darkcurrent) * (f_stop ** 2) / (ISO * exposure_time)
|
||||||
|
|
||||||
# apply camera calibration func
|
# apply camera calibration func
|
||||||
temp_C = pyrometry_calibration_formula(g_norm, r_norm)
|
temp_C = pyrometry_calibration_formula(g_norm, r_norm)
|
||||||
|
@ -62,14 +58,6 @@ def rg_ratio_normalize(imgarr):
|
||||||
return imgnew, tmin, tmax
|
return imgnew, tmin, tmax
|
||||||
|
|
||||||
|
|
||||||
@jit(nopython=True)
|
|
||||||
def normalization_func(i):
|
|
||||||
"""
|
|
||||||
does something to the pixels that i don't understand lol
|
|
||||||
"""
|
|
||||||
return (i - I_Darkcurrent) * (f_stop ** 2) / (ISO * exposure_time)
|
|
||||||
|
|
||||||
|
|
||||||
@jit(nopython=True)
|
@jit(nopython=True)
|
||||||
def pyrometry_calibration_formula(i_ng, i_nr):
|
def pyrometry_calibration_formula(i_ng, i_nr):
|
||||||
"""
|
"""
|
||||||
|
@ -84,18 +72,31 @@ def pyrometry_calibration_formula(i_ng, i_nr):
|
||||||
(i_ng / i_nr) ** 3
|
(i_ng / i_nr) ** 3
|
||||||
) + 3753.5
|
) + 3753.5
|
||||||
|
|
||||||
def ratio_pyrometry_pipeline(file):
|
def ratio_pyrometry_pipeline(
|
||||||
|
file_bytes,
|
||||||
|
# camera settings
|
||||||
|
I_Darkcurrent: float,
|
||||||
|
exposure_time: float,
|
||||||
|
f_stop: float,
|
||||||
|
ISO: float,
|
||||||
|
# pyrometry config
|
||||||
|
MAX_TEMP: float,
|
||||||
|
MIN_TEMP: float
|
||||||
|
):
|
||||||
|
|
||||||
# read image & crop
|
# read image & crop
|
||||||
file_name = file.split(".")[0]
|
img_orig = cv.imdecode(file_bytes, cv.IMREAD_UNCHANGED)
|
||||||
file_ext = file.split(".")[1]
|
# img = img[y1:y2, x1:x2]
|
||||||
img = cv.imread(file)
|
|
||||||
img = img[y1:y2, x1:x2]
|
|
||||||
cv.imwrite(f'{file_name}-cropped.{file_ext}', img)
|
|
||||||
|
|
||||||
# img = cv.imread('ember_test.png')
|
img, tmin, tmax = rg_ratio_normalize(
|
||||||
|
img_orig,
|
||||||
img, tmin, tmax = rg_ratio_normalize(img)
|
I_Darkcurrent,
|
||||||
|
f_stop,
|
||||||
|
exposure_time,
|
||||||
|
ISO,
|
||||||
|
MIN_TEMP,
|
||||||
|
MAX_TEMP
|
||||||
|
)
|
||||||
|
|
||||||
# build & apply smoothing conv kernel
|
# build & apply smoothing conv kernel
|
||||||
k = []
|
k = []
|
||||||
|
@ -107,7 +108,7 @@ def ratio_pyrometry_pipeline(file):
|
||||||
|
|
||||||
# write colormapped image
|
# write colormapped image
|
||||||
img_jet = cv.applyColorMap(img, cv.COLORMAP_JET)
|
img_jet = cv.applyColorMap(img, cv.COLORMAP_JET)
|
||||||
cv.imwrite(f'{file_name}-cropped-transformed-ratio.{file_ext}', img_jet)
|
# cv.imwrite(f'{file_name}-cropped-transformed-ratio.{file_ext}', img_jet)
|
||||||
|
|
||||||
# --- Generate temperature key ---
|
# --- Generate temperature key ---
|
||||||
|
|
||||||
|
@ -131,3 +132,6 @@ def ratio_pyrometry_pipeline(file):
|
||||||
for i in range(len(temps)):
|
for i in range(len(temps)):
|
||||||
c = key_img_jet[0][i]
|
c = key_img_jet[0][i]
|
||||||
tempkey[temps[i]] = f"rgb({c[0]}, {c[1]}, {c[2]})"
|
tempkey[temps[i]] = f"rgb({c[0]}, {c[1]}, {c[2]})"
|
||||||
|
|
||||||
|
# original, transformed, legend
|
||||||
|
return img_orig, img_jet, tempkey
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
<link rel="app.css">
|
<link rel="app.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block content %}
|
{% block content required %}
|
||||||
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,7 +1,38 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.jinja2" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="form">
|
<form action="/ratio_pyro" method="POST" enctype="multipart/form-data">
|
||||||
<button onclick="">
|
<h2>Simple Ratio Pyrometry Interface</h2>
|
||||||
</button>
|
<input type="file" name="file" accept=".png,.jpg,.jpeg" value="Choose Image"/>
|
||||||
</div>
|
|
||||||
|
<h4>Camera Settings</h4>
|
||||||
|
|
||||||
|
<label for="iso">ISO</label>
|
||||||
|
<input type="number" name="iso" value="2.4"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="i_darkcurrent">I_Darkcurrent</label>
|
||||||
|
<input type="number" name="i_darkcurrent" value="150.5"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="exposure_time">Exposure Time <i>t</i></label>
|
||||||
|
<input type="number" name="exposure_time" value="0.5"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="f_stop">F-stop <i>f</i></label>
|
||||||
|
<input type="number" name="f_stop" value="2.4"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<h4>Temperature Settings</h4>
|
||||||
|
|
||||||
|
<label for="min_temp">Min Temp (°C)</label>
|
||||||
|
<input type="number" name="min_temp" value="60"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label for="max_temp">Max Temp (°C)</label>
|
||||||
|
<input type="number" name="max_temp" value="1200"/>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Generate Heatmap"/>
|
||||||
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends "base.jinja2" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class="img-table">
|
||||||
|
<tr>
|
||||||
|
<th class="img-table-heading">Input Image</th>
|
||||||
|
<th class="img-table-heading">Resultant Heatmap</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{# Original image #}
|
||||||
|
<td>
|
||||||
|
<img data="{{ img_orig_bytes }}" alt="original image">
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{# Result image #}
|
||||||
|
<td>
|
||||||
|
<img data="{{ img_res_bytes }}" alt="result image">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h1>{{ img_orig_bytes }}</h1>
|
||||||
|
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
<table class="legend">
|
||||||
|
<tr>
|
||||||
|
<th>Color</th>
|
||||||
|
<th>Temperature</th>
|
||||||
|
</tr>
|
||||||
|
{% for temp, color in legend.items() %}
|
||||||
|
<tr>
|
||||||
|
<td class="legend-cell"><div style="width:20px;height:20px;background-color:{{ color }};"></div></td>
|
||||||
|
<td class="legend-cell">{{ temp }}°C</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.img-table {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-table-heading {
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-table-cell {
|
||||||
|
padding: 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-cell {
|
||||||
|
padding: 4px 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue