Compare commits
14 Commits
407a103913
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1de21e93e1 | |||
| 59e0f2d861 | |||
| cc2820da90 | |||
| f8d4f85858 | |||
| 76e178176d | |||
| fa1e988344 | |||
| dcf78bb88d | |||
| 481de4dfb1 | |||
| 8a364b81e6 | |||
| 97ab6a1ed1 | |||
| 405b9f7f91 | |||
| 52fe136f3f | |||
| e4516e12d9 | |||
|
|
55110d6736 |
1
Pipfile
@@ -14,6 +14,7 @@ matplotlib = "*"
|
||||
plotly = "*"
|
||||
pandas = "*"
|
||||
scipy = "==1.8.1"
|
||||
scikit-image = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
||||
120
Pipfile.lock
generated
@@ -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",
|
||||
|
||||
15
README.md
@@ -1,4 +1,4 @@
|
||||
# Pyrometry image processing
|
||||
# Fire Lab Work
|
||||
|
||||
## Interface Screenshots
|
||||
|
||||
@@ -6,13 +6,26 @@
|
||||
| --- | --- | --- |
|
||||
|  |  | 
|
||||
|
||||
| Labeling Input | Labeling Results |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## 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:
|
||||
|
||||
BIN
examples/projected-area/output.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
examples/projected-area/proj-area-1-processed.jpg
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
examples/projected-area/proj-area-1.jpg
Normal file
|
After Width: | Height: | Size: 561 KiB |
BIN
examples/projected-area/proj-area-2.jpg
Normal file
|
After Width: | Height: | Size: 539 KiB |
BIN
examples/projected-area/proj-area-3.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
@@ -0,0 +1,80 @@
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
from skimage import measure, morphology, color, segmentation
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
file = 'proj-area-3.jpg'
|
||||
|
||||
original = cv.imread(file)
|
||||
original = cv.cvtColor(original, cv.COLOR_BGR2RGB)
|
||||
img = original
|
||||
|
||||
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
|
||||
retval, thresh_gray = cv.threshold(img, 200, 255, cv.THRESH_BINARY)
|
||||
|
||||
def remove_dirt(image):
|
||||
image = morphology.area_closing(image, area_threshold=250, connectivity=1)
|
||||
# image = morphology.opening(image, morphology.square(5))
|
||||
|
||||
return image
|
||||
|
||||
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])
|
||||
|
||||
img = remove_dirt(thresh_gray)
|
||||
|
||||
|
||||
# alpha = 1 # Contrast control (1.0-3.0)
|
||||
# beta = 1 # Brightness control (0-100)
|
||||
|
||||
# img = cv.convertScaleAbs(img, alpha=alpha, beta=beta)
|
||||
|
||||
# img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
|
||||
# img = cv.medianBlur(img, 5)
|
||||
|
||||
# retval, thresh_gray = cv.threshold(img, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)
|
||||
|
||||
|
||||
# img = cv.medianBlur(img, 5)
|
||||
|
||||
# img = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 51, 15)
|
||||
|
||||
contours = measure.find_contours(array=img, level=100)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.imshow(img, cmap=plt.cm.gray, alpha=0)
|
||||
|
||||
index = 1
|
||||
|
||||
for contour in contours:
|
||||
if calculate_area(contour) > 300:
|
||||
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)
|
||||
|
||||
index += 1
|
||||
|
||||
ax.axis('image')
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
|
||||
plt.savefig('output.png', dpi=300)
|
||||
|
||||
# cv.imwrite('proj-area-1-processed.jpg', remove_dirt(img))
|
||||
BIN
examples/pyrometry/asdf.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
@@ -2,26 +2,77 @@
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
from skimage import measure, morphology, color, segmentation
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# edge-detection kernel amplification
|
||||
AMPLIFIER=9
|
||||
|
||||
MIN_INTENSITY=100
|
||||
|
||||
# file = '01-0001-cropped.png'
|
||||
file = 'streaktest.png'
|
||||
file_name = file.split(".")[0]
|
||||
file_ext = file.split(".")[1]
|
||||
|
||||
file = 'streaktest2.png'
|
||||
img = cv.imread(file)
|
||||
|
||||
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
# blurred = cv.GaussianBlur(img, (8, 8), 0)
|
||||
|
||||
kernel = np.array([
|
||||
[-1, -1, -1],
|
||||
[-1, AMPLIFIER, -1],
|
||||
[-1, -1, -1],
|
||||
])
|
||||
img = cv.filter2D(src=img, ddepth=-1, kernel=kernel)
|
||||
retval, thresh_gray = cv.threshold(img, 120, 255, cv.THRESH_BINARY)
|
||||
|
||||
cv.imwrite(f'{file_name}-edge-detection.{file_ext}', img)
|
||||
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)
|
||||
|
||||
gray = cv.copyMakeBorder(
|
||||
gray,
|
||||
20,
|
||||
20,
|
||||
20,
|
||||
20,
|
||||
cv.BORDER_CONSTANT,
|
||||
value=0
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
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])
|
||||
|
||||
|
||||
img_new = cv.cvtColor(gray, cv.COLOR_GRAY2BGR)
|
||||
|
||||
for contour in contours:
|
||||
area = calculate_area(contour)
|
||||
|
||||
# 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([])
|
||||
ax.set_yticks([])
|
||||
plt.savefig("edge_detection_figure.png", dpi=500)
|
||||
|
||||
BIN
examples/pyrometry/edge_detection_figure.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
examples/pyrometry/firebrand_contours_opencv.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
examples/pyrometry/streaktest2-edge-detection.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
examples/pyrometry/streaktest2.png
Normal file
|
After Width: | Height: | Size: 445 KiB |
0
firebrand_detection.py
Normal file
@@ -1,10 +1,10 @@
|
||||
from flask import Flask, render_template, request
|
||||
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
|
||||
from scipy import stats
|
||||
|
||||
app = Flask(
|
||||
__name__,
|
||||
@@ -14,13 +14,13 @@ app = Flask(
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return render_template('index.jinja2')
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/ratio_pyro', methods=['POST'])
|
||||
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']),
|
||||
@@ -30,37 +30,64 @@ 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]
|
||||
|
||||
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
|
||||
|
||||
# add ptemp data to list
|
||||
ptemps_list.append(brand_data["ptemps"])
|
||||
|
||||
freq_plot, csvstrs = generate_plotly_temperature_pdf(ptemps_list)
|
||||
|
||||
return render_template(
|
||||
'results.jinja2',
|
||||
'pyrometry-results.html',
|
||||
img_orig_b64=img_orig_b64,
|
||||
img_res_b64=img_res_b64,
|
||||
legend=key,
|
||||
freq_plot=freq_plot
|
||||
freq_plot=freq_plot,
|
||||
csv_data=csvstrs[0],
|
||||
individual_firebrands=indiv_firebrands,
|
||||
)
|
||||
|
||||
|
||||
@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']),
|
||||
float(request.form['paper_width']),
|
||||
float(request.form['paper_width'])
|
||||
)
|
||||
|
||||
return render_template(
|
||||
'projected-area-results.html',
|
||||
img_b64=img,
|
||||
dtable=dtable
|
||||
)
|
||||
|
||||
# @app.route("/download_pyrometry_temps")
|
||||
# def download_pyrometry_temps():
|
||||
# return send_file()
|
||||
|
||||
64
plotly_util.py
Normal 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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
BIN
screenshots/projected_area_in.png
Normal file
|
After Width: | Height: | Size: 436 KiB |
BIN
screenshots/projected_area_out.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 826 KiB After Width: | Height: | Size: 1.3 MiB |
75
size_projection.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# use headless backend
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
|
||||
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, paper_width, paper_height):
|
||||
total_px = image.size
|
||||
total_mm = paper_width * paper_height * 25.4
|
||||
|
||||
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(image=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])
|
||||
77
static/app.css
Normal file
@@ -0,0 +1,77 @@
|
||||
* {
|
||||
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;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin: 0rem 1rem;
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#img-preview {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
#pyro-eqn {
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
28
static/js/csv_download.js
Normal file
@@ -0,0 +1,28 @@
|
||||
function saveData(csvStr, filename) {
|
||||
// string rep
|
||||
// var csvStr = "";
|
||||
// for (let r = 0; r < csvData.length; r++) {
|
||||
// let row = csvData[r]
|
||||
// for (let c = 0; c < row.length; c++) {
|
||||
// let item = row[c]
|
||||
// csvStr += item;
|
||||
// if (c < row.length - 1)
|
||||
// csvStr += ",";
|
||||
// }
|
||||
// if (r < csvStr.length - 1)
|
||||
// csvStr += "\r\n";
|
||||
// }
|
||||
|
||||
// define data blob
|
||||
var data = new Blob([csvStr]);
|
||||
|
||||
// create & click temp link
|
||||
// slightly modded https://stackoverflow.com/a/15832662
|
||||
var link = document.createElement("a");
|
||||
link.download = filename;
|
||||
link.href = URL.createObjectURL(data);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
delete link;
|
||||
}
|
||||
9
static/js/img_preview.js
Normal file
@@ -0,0 +1,9 @@
|
||||
let imgPreview = document.getElementById('img-preview');
|
||||
let imgUpload = document.getElementById('img-upload');
|
||||
imgUpload.onchange = event => {
|
||||
const [file] = imgUpload.files;
|
||||
if (file) {
|
||||
console.log(file)
|
||||
imgPreview.src = URL.createObjectURL(file);
|
||||
}
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
22
templates/base.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Pyrometry Application</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</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>
|
||||
<div class="content">
|
||||
{% block content required %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<br><br><br>
|
||||
</body>
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Pyrometry Application</title>
|
||||
<link rel="app.css">
|
||||
</head>
|
||||
<body>
|
||||
{% block content required %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,11 @@
|
||||
{% extends "base.jinja2" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
||||
{% 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>
|
||||
@@ -44,6 +48,16 @@
|
||||
<input type="number" name="equation_scaling_factor" value="0.55" step="0.001"/>
|
||||
<br>
|
||||
|
||||
<h4>Firebrand Detection Settings</h4>
|
||||
|
||||
<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>
|
||||
|
||||
<label for="smoothing_radius">Smoothing Radius (px)</label>
|
||||
@@ -58,25 +72,7 @@
|
||||
<input type="submit" value="Generate Heatmap"/>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
let imgPreview = document.getElementById('img-preview');
|
||||
let imgUpload = document.getElementById('img-upload');
|
||||
imgUpload.onchange = event => {
|
||||
const [file] = imgUpload.files;
|
||||
if (file) {
|
||||
console.log(file)
|
||||
imgPreview.src = URL.createObjectURL(file);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#img-preview {
|
||||
width: 18rem;
|
||||
}
|
||||
#pyro-eqn {
|
||||
width: 800px;
|
||||
}
|
||||
</style>
|
||||
<!-- Image Preview -->
|
||||
<script src="/s/js/img_preview.js"></script>
|
||||
|
||||
{% endblock %}
|
||||
30
templates/projected-area-results.html
Normal 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 (mm²)</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 }}</td>
|
||||
<td>
|
||||
<button onclick="() => navigator.clipboard.writeText('{{ item.1 }}')">Copy</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
38
templates/projected-area.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<form action="/projected_area_results" method="POST" enctype="multipart/form-data">
|
||||
<h2>Projected Area Interface</h2>
|
||||
|
||||
<img src="#" id="img-preview" type="file"/>
|
||||
<br>
|
||||
|
||||
<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>
|
||||
<br>
|
||||
|
||||
<label for="min_display_threshold">Minimum display threshold (in px)</label>
|
||||
<input type="number" name="min_display_threshold" value="300"/>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<label for="min_display_threshold">Page size</label>
|
||||
<input type="number" name="paper_width" value="8.5"/>
|
||||
X
|
||||
<input type="number" name="paper_height" value="11"/> inches
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<input type="submit" value="Generate Projected Sizes"/>
|
||||
</form>
|
||||
|
||||
<!-- Image Preview -->
|
||||
<script src="/s/js/img_preview.js"></script>
|
||||
|
||||
{% endblock %}
|
||||
99
templates/pyrometry-results.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<script src="/s/js/csv_download.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% 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>
|
||||
<th class="img-table-heading">Output Heatmap</th>
|
||||
<th class="img-table-heading">Legend</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{# Original image #}
|
||||
<td class="img-table-cell" id="orig-cell">
|
||||
<img class="img-out" src="data:image/png;base64,{{ img_orig_b64 }}" alt="original image">
|
||||
</td>
|
||||
{# Result image #}
|
||||
<td class="img-table-cell" id="res-cell">
|
||||
<img class="img-out" src="data:image/png;base64,{{ img_res_b64 }}" alt="result image">
|
||||
</td>
|
||||
{# Legend #}
|
||||
<td>
|
||||
<table class="legend" id="legend">
|
||||
<tr>
|
||||
<th class="legend-heading">Color</th>
|
||||
<th class="legend-heading">Temperature</th>
|
||||
</tr>
|
||||
{% for temp, color in 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>
|
||||
</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;">
|
||||
<strong>Temperature Distribution</strong>
|
||||
<button
|
||||
style="width: 10rem; height: 2rem; margin-left: 1rem;"
|
||||
onclick="saveData(`{{csv_data}}`, 'temperature-data.csv')">Download Data as CSV</button>
|
||||
</div>
|
||||
{{ freq_plot | safe }}
|
||||
|
||||
|
||||
<!-- Firebrands: {{ individual_firebrands }} -->
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,70 +0,0 @@
|
||||
{% extends "base.jinja2" %}
|
||||
{% block content %}
|
||||
|
||||
<table class="img-table">
|
||||
<tr>
|
||||
<th class="img-table-heading">Input Image</th>
|
||||
<th class="img-table-heading">Output Heatmap</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{# Original image #}
|
||||
<td class="img-table-cell" id="orig-cell">
|
||||
<img class="img-out" src="data:image/png;base64,{{ img_orig_b64 }}" alt="original image">
|
||||
</td>
|
||||
{# Result image #}
|
||||
<td class="img-table-cell" id="res-cell">
|
||||
<img class="img-out" src="data:image/png;base64,{{ img_res_b64 }}" alt="result image">
|
||||
</td>
|
||||
</tr>
|
||||
{# Legend #}
|
||||
<tr>
|
||||
<td class="img-table-cell"></td>
|
||||
<td class="img-table-cell">
|
||||
<table class="legend" id="legend">
|
||||
<tr>
|
||||
<th class="legend-heading">Color</th>
|
||||
<th class="legend-heading">Temperature</th>
|
||||
</tr>
|
||||
{% for temp, color in 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>
|
||||
</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>
|
||||
|
||||
{% endblock %}
|
||||