Compare commits

..

2 Commits

Author SHA1 Message Date
michael 97ab6a1ed1 size projections in web interface
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-10-28 20:11:50 -07:00
michael 405b9f7f91 restructure 2022-10-28 17:52:47 -07:00
12 changed files with 354 additions and 45 deletions

View File

@ -14,6 +14,7 @@ matplotlib = "*"
plotly = "*"
pandas = "*"
scipy = "==1.8.1"
scikit-image = "*"
[dev-packages]

120
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "3428842daebc7c8a255790fde5231377c05479a39d5ce2977f043e58c7c80826"
"sha256": "4ce52e44137325bfa984f7d78467dce4463d471bfc344ecd116c2beb2af52d60"
},
"pipfile-spec": 6,
"requires": {
@ -131,6 +131,14 @@
"index": "pypi",
"version": "==20.1.0"
},
"imageio": {
"hashes": [
"sha256:9bdafe9c5a3d336a187f3f554f3e30bcdbf8a1d7d46f0e4d94e4a535adfb64c7",
"sha256:db7010cd10712518819a4187baf61b05988361ea20c23e829918727b27acb977"
],
"markers": "python_version >= '3.7'",
"version": "==2.22.2"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
@ -348,6 +356,14 @@
"index": "pypi",
"version": "==3.6.1"
},
"networkx": {
"hashes": [
"sha256:15cdf7f7c157637107ea690cabbc488018f8256fa28242aed0fb24c93c03a06d",
"sha256:815383fd52ece0a7024b5fd8408cc13a389ea350cd912178b82eed8b96f82cd3"
],
"markers": "python_version >= '3.8'",
"version": "==2.8.7"
},
"numba": {
"hashes": [
"sha256:0744cf4214ed795eb2df3ed1635d77a6ffcbd990a66a06125548b5fb8ee46323",
@ -413,7 +429,7 @@
"sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef",
"sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7"
],
"markers": "python_version >= '3.10'",
"markers": "python_version >= '3.8'",
"version": "==1.23.4"
},
"opencv-python": {
@ -565,6 +581,37 @@
],
"version": "==2022.5"
},
"pywavelets": {
"hashes": [
"sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b",
"sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4",
"sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4",
"sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6",
"sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de",
"sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c",
"sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa",
"sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93",
"sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875",
"sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd",
"sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356",
"sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1",
"sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c",
"sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2",
"sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784",
"sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4",
"sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b",
"sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc",
"sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966",
"sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e",
"sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426",
"sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c",
"sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202",
"sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc",
"sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"
],
"markers": "python_version >= '3.8'",
"version": "==1.4.1"
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
@ -611,6 +658,67 @@
"index": "pypi",
"version": "==6.0"
},
"scikit-image": {
"hashes": [
"sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099",
"sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d",
"sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790",
"sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450",
"sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef",
"sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245",
"sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919",
"sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6",
"sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8",
"sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d",
"sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19",
"sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2",
"sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92",
"sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced",
"sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e",
"sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732",
"sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34",
"sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83",
"sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f",
"sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9",
"sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe",
"sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7",
"sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b",
"sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6",
"sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc",
"sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"
],
"index": "pypi",
"version": "==0.19.3"
},
"scipy": {
"hashes": [
"sha256:02b567e722d62bddd4ac253dafb01ce7ed8742cf8031aea030a41414b86c1125",
"sha256:1166514aa3bbf04cb5941027c6e294a000bba0cf00f5cdac6c77f2dad479b434",
"sha256:1da52b45ce1a24a4a22db6c157c38b39885a990a566748fc904ec9f03ed8c6ba",
"sha256:23b22fbeef3807966ea42d8163322366dd89da9bebdc075da7034cee3a1441ca",
"sha256:28d2cab0c6ac5aa131cc5071a3a1d8e1366dad82288d9ec2ca44df78fb50e649",
"sha256:2ef0fbc8bcf102c1998c1f16f15befe7cffba90895d6e84861cd6c6a33fb54f6",
"sha256:3b69b90c9419884efeffaac2c38376d6ef566e6e730a231e15722b0ab58f0328",
"sha256:4b93ec6f4c3c4d041b26b5f179a6aab8f5045423117ae7a45ba9710301d7e462",
"sha256:4e53a55f6a4f22de01ffe1d2f016e30adedb67a699a310cdcac312806807ca81",
"sha256:6311e3ae9cc75f77c33076cb2794fb0606f14c8f1b1c9ff8ce6005ba2c283621",
"sha256:65b77f20202599c51eb2771d11a6b899b97989159b7975e9b5259594f1d35ef4",
"sha256:6cc6b33139eb63f30725d5f7fa175763dc2df6a8f38ddf8df971f7c345b652dc",
"sha256:70de2f11bf64ca9921fda018864c78af7147025e467ce9f4a11bc877266900a6",
"sha256:70ebc84134cf0c504ce6a5f12d6db92cb2a8a53a49437a6bb4edca0bc101f11c",
"sha256:83606129247e7610b58d0e1e93d2c5133959e9cf93555d3c27e536892f1ba1f2",
"sha256:93d07494a8900d55492401917a119948ed330b8c3f1d700e0b904a578f10ead4",
"sha256:9c4e3ae8a716c8b3151e16c05edb1daf4cb4d866caa385e861556aff41300c14",
"sha256:9dd4012ac599a1e7eb63c114d1eee1bcfc6dc75a29b589ff0ad0bb3d9412034f",
"sha256:9e3fb1b0e896f14a85aa9a28d5f755daaeeb54c897b746df7a55ccb02b340f33",
"sha256:a0aa8220b89b2e3748a2836fbfa116194378910f1a6e78e4675a095bcd2c762d",
"sha256:d3b3c8924252caaffc54d4a99f1360aeec001e61267595561089f8b5900821bb",
"sha256:e013aed00ed776d790be4cb32826adb72799c61e318676172495383ba4570aa4",
"sha256:f3e7a8867f307e3359cc0ed2c63b61a1e33a19080f92fe377bc7d49f646f2ec1"
],
"index": "pypi",
"version": "==1.8.1"
},
"setuptools": {
"hashes": [
"sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17",
@ -635,6 +743,14 @@
"markers": "python_version >= '3.6'",
"version": "==8.1.0"
},
"tifffile": {
"hashes": [
"sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e",
"sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503"
],
"markers": "python_version >= '3.8'",
"version": "==2022.10.10"
},
"werkzeug": {
"hashes": [
"sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",

View File

@ -1,4 +1,4 @@
# Pyrometry image processing
# Fire Lab Work
## Interface Screenshots
@ -8,11 +8,20 @@
## Using the web version
### Ratio Pyrometry
1. Go to [pyro.turtlebasket.ml](https://pyro.turtlebasket.ml).
2. Select an input image.
3. Enter your DSLR camera settings.
4. Click "Generate Heatmap".
### Projected Object Area
1. Go to [pyro.turtlebasket.ml](https://pyro.turtlebasket.ml).
2. Navigate to "Object Area".
3. Select an input image.
4. Click "Generate Projected Sizes".
## Using the local (batch) version
Create a new config file:

View File

@ -1,6 +1,7 @@
from flask import Flask, render_template, request
import numpy as np
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
@ -58,9 +59,32 @@ def ratio_pyro():
freq_plot = fig.to_html()
return render_template(
'results.html',
'pyrometry-results.html',
img_orig_b64=img_orig_b64,
img_res_b64=img_res_b64,
legend=key,
freq_plot=freq_plot
)
@app.route('/projected_area')
def projected_area():
return render_template('projected-area.html')
@app.route('/projected_area_results', methods=['POST'])
def projected_area_results():
f = request.files['file']
f_bytes = np.fromstring(f.read(), np.uint8)
img, dtable = get_projected_area(
f_bytes,
int(request.form['area_threshold']),
int(request.form['min_display_threshold']),
)
return render_template(
'projected-area-results.html',
img_b64=img,
dtable=dtable
)

71
size_projection.py Normal file
View File

@ -0,0 +1,71 @@
import base64
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
from skimage import measure, morphology, color, segmentation
import io
def get_projected_area(image, area_threshold, display_threshold):
total_px = image.size
total_mm = 60322.46
output = []
original = cv.imdecode(image, cv.IMREAD_UNCHANGED)
original = cv.cvtColor(original, cv.COLOR_BGR2RGB)
img = cv.cvtColor(original, cv.COLOR_BGR2GRAY)
_retval, thresh_gray = cv.threshold(img, 200, 255, cv.THRESH_BINARY)
img = morphology.area_closing(thresh_gray, area_threshold=area_threshold, connectivity=1)
contours = measure.find_contours(array=img, level=100)
fig, ax = plt.subplots()
ax.imshow(original, cmap=plt.cm.gray, alpha=0.3)
index = 1
for contour in contours:
area = calculate_area(contour)
if calculate_area(contour) > display_threshold:
ax.plot(contour[:, 1], contour[:, 0], linewidth=0.5, color='orangered')
cX, cY = center_of_mass(contour)
plt.text(cY, cX, index, color='black', fontsize=6)
output.append((index, round(area / total_px * total_mm, 2)))
# print(area, total_px)
index += 1
ax.axis('image')
ax.set_xticks([])
ax.set_yticks([])
ax.margins(0)
my_stringIObytes = io.BytesIO()
plt.savefig(my_stringIObytes, format='png', dpi=500, bbox_inches='tight')
my_stringIObytes.seek(0)
image_arr = base64.b64encode(my_stringIObytes.read()).decode(encoding='utf-8')
return image_arr, output
def calculate_area(countour):
c = np.expand_dims(countour.astype(np.float32), 1)
c = cv.UMat(c)
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])

64
static/app.css Normal file
View File

@ -0,0 +1,64 @@
* {
font-family: sans-serif;
}
html {
margin: 0px;
padding: 0px;
}
.navbar {
display: flex;
flex-direction: row;
align-items: center;
padding: 0px 2rem;
background-color: #e0e0e0;
border-radius: 0.8rem;
}
.navbar-links {
display: flex;
flex-direction: row;
align-items: center;
margin-left: auto;
}
.navbar-link {
display: flex;
margin-left: 3rem;
}
.form {
display: flex;
flex-direction: column;
}
.img-table-heading {
padding: 4px 10px;
}
.img-table-cell {
padding: 10px 20px;
}
.img-out {
width: 32rem;
}
.image-out-pa {
width: 60rem;
}
.legend {
border-spacing: 0px;
border-collapse: collapse;
}
.legend-heading {
padding: 4px;
}
.legend-cell {
border-width: 0px;
padding: 0px 20px;
}

View File

@ -1,4 +0,0 @@
.form {
display: flex;
flex-direction: column;
}

View File

@ -1,15 +1,18 @@
<!DOCTYPE html>
<head>
<title>Pyrometry Application</title>
<link rel="app.css">
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
</head>
<body>
<div class="navbar">
<h3>Pyrometry Toolkit</h3>
<div class="navbar-links">
<a class="navbar-link" href="/">Ratio Pyrometry</a>
<a class="navbar-link" href="/projected_area">Projected Area</a>
</div>
</div>
<br>
{% block content required %}
{% endblock %}
<br><br><br>
</body>
<style>
* {
font-family: sans-serif;
}
</style>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block content %}
<form action="/ratio_pyro" method="POST" enctype="multipart/form-data">
<h2>Simple Ratio Pyrometry Interface</h2>
<h2>Ratio Pyrometry Interface</h2>
<img src="#" id="img-preview" type="file"/>
<br>

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<h2>Results</h2>
<a href="/projected_area">Process another image</a>
<div style="display: flex; align-items: start;">
<img class="image-out-pa" src="data:image/png;base64,{{ img_b64 }}" alt="original image">
<div style="overflow-y: scroll;">
<table style="background: #f1f1f1">
<tr>
<th class="legend-heading">Index</th>
<th class="legend-heading">Area</th>
<th class="legend-heading">Copy</th>
</tr>
{% for item in dtable %}
<tr>
<td class="legend-cell">{{ item.0 }}</td>
<td class="legend-cell">{{ item.1 }} mm²</td>
<td>
<button onclick="() => navigator.clipboard.writeText('{{ item.1 }}')">Copy</button>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block content %}
<form action="/projected_area_results" method="POST" enctype="multipart/form-data">
<h2>Projected Area Interface</h2>
<input id="img-upload" type="file" name="file" accept=".png,.jpg,.jpeg,.tiff" value="Choose Image"/>
<h4>Settings</h4>
<label for="area_threshold">Area threshold (to remove dust particles) in px</label>
<input type="number" name="area_threshold" value="250"/>
<br>
<label for="min_display_threshold">Minimum display threshold (in px)</label>
<input type="number" name="min_display_threshold" value="300"/>
<br>
<br>
<input type="submit" value="Generate Projected Sizes"/>
</form>
{% endblock %}

View File

@ -37,34 +37,6 @@
</table>
{# Temperature Frequency Plot #}
<strong>Temperature Distribution</strong>
{{freq_plot}}
<style>
.img-table-heading {
padding: 4px 10px;
}
.img-table-cell {
padding: 10px 20px;
}
.img-out {
width: 32rem;
}
.legend {
border-spacing: 0px;
border-collapse: collapse;
}
.legend-heading {
padding: 4px;
}
.legend-cell {
border-width: 0px;
padding: 0px 20px;
}
</style>
{{ freq_plot | safe }}
{% endblock %}