Compare commits

...

16 Commits

Author SHA1 Message Date
1de21e93e1 basic firebrand recognition
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-11-14 22:47:43 -08:00
59e0f2d861 firebrand edge detection demo
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-11-13 17:57:41 -08:00
cc2820da90 start firebrand detection 2022-11-11 19:39:19 -08:00
f8d4f85858 fully working csv download 2022-11-04 18:58:35 -07:00
76e178176d save csv + paper dims
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-11-04 18:42:22 -07:00
fa1e988344 use static js 2022-11-04 17:55:25 -07:00
dcf78bb88d fix mm^2 issue 2022-11-03 17:36:22 -07:00
481de4dfb1 update content 2022-10-31 21:01:37 -07:00
8a364b81e6 interface improvements
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-10-28 20:49:54 -07:00
97ab6a1ed1 size projections in web interface
Co-authored-by: Alex <AlexanderMcDowell@users.noreply.github.com>
2022-10-28 20:11:50 -07:00
405b9f7f91 restructure 2022-10-28 17:52:47 -07:00
52fe136f3f Merge branch 'master' of https://git.turtlebasket.ml/datadonkey-com/berkeley-firelab-general 2022-10-28 15:19:02 -07:00
e4516e12d9 update templates 2022-10-28 15:18:51 -07:00
Your Name
55110d6736 Added projected area code for images 2022-10-28 04:16:34 +00:00
407a103913 start projected area 2022-10-27 14:12:25 -07:00
6635404cf9 restructure 2022-10-27 14:11:06 -07:00
56 changed files with 904 additions and 171 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
@@ -6,13 +6,26 @@
| --- | --- | --- |
| ![](screenshots/pyro_input.png) | ![](screenshots/pyro_results.png) | ![](screenshots/temp_dist_plot.png)
| Labeling Input | Labeling Results |
| --- | --- |
| ![](screenshots/projected_area_in.png) | ![](screenshots/projected_area_out.png) |
## 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,27 +0,0 @@
# MONOCHROME EDGE DETECTION
import cv2 as cv
import numpy as np
# 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]
img = cv.imread(file)
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
kernel = np.array([
[-1, -1, -1],
[-1, AMPLIFIER, -1],
[-1, -1, -1],
])
img = cv.filter2D(src=img, ddepth=-1, kernel=kernel)
cv.imwrite(f'{file_name}-edge-detection.{file_ext}', img)

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -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))

View File

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 263 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 513 KiB

After

Width:  |  Height:  |  Size: 513 KiB

View File

Before

Width:  |  Height:  |  Size: 986 KiB

After

Width:  |  Height:  |  Size: 986 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 414 KiB

After

Width:  |  Height:  |  Size: 414 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 354 KiB

View File

Before

Width:  |  Height:  |  Size: 841 KiB

After

Width:  |  Height:  |  Size: 841 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 MiB

After

Width:  |  Height:  |  Size: 9.7 MiB

View File

Before

Width:  |  Height:  |  Size: 15 MiB

After

Width:  |  Height:  |  Size: 15 MiB

BIN
examples/pyrometry/asdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,78 @@
# MONOCHROME EDGE DETECTION
import cv2 as cv
import numpy as np
from skimage import measure, morphology, color, segmentation
import matplotlib.pyplot as plt
file = 'streaktest2.png'
img = cv.imread(file)
# blurred = cv.GaussianBlur(img, (8, 8), 0)
retval, thresh_gray = cv.threshold(img, 120, 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)
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

0
firebrand_detection.py Normal file
View File

View 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
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

75
size_projection.py Normal file
View 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
View 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
View 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
View 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);
}
};

View File

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

22
templates/base.html Normal file
View 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>

View File

@@ -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>

View File

@@ -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 %}

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 (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 %}

View 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 %}

View 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 %}

View File

@@ -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 %}