Skip to content

Commit 0195450

Browse files
authored
Merge pull request #46 from tldr-group/development
Development
2 parents 021cd2f + 6b315c6 commit 0195450

24 files changed

+1376
-483
lines changed

CITATION.cff

+7-3
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,26 @@ authors:
55
orcid: 0000-0003-0142-8597
66
- name: Ronan Docherty
77
orcid: 0000-0002-7332-0924
8+
- name: Steve Kench
9+
orcid: 0000-0002-7263-6724
810
- name: Samuel J. Cooper
911
orcid: 0000-0003-4055-6903
10-
title: "Predicting Microstructural Representativity from a Single Image"
12+
title: "Prediction of Microstructural Representativity from a Single Image"
1113
doi: ARXIV_DOI
12-
url: "https://github.com/tldr-group/Representativity"
14+
url: "https://github.com/tldr-group/ImageRep"
1315
preferred-citation:
1416
type: article
1517
authors:
1618
- name: Amir Dahari
1719
orcid: 0000-0003-0142-8597
1820
- name: Ronan Docherty
1921
orcid: 0000-0002-7332-0924
22+
- name: Steve Kench
23+
orcid: 0000-0002-7263-6724
2024
- name: Samuel J. Cooper
2125
orcid: 0000-0003-4055-6903
2226
doi: ARXIV_DOI
2327
journal: "arXiV preprint"
2428
month: 8
25-
title: "Predicting Microstructural Representativity from a Single Image"
29+
title: "Prediction of Microstructural Representativity from a Single Image"
2630
year: 2024

README.md

+10-14
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
1-
# Representativity
2-
3-
![Tests](https://github.com/tldr-group/Representativity/actions/workflows/tests.yml/badge.svg)
1+
# ImageRep
42

53
[Try it out!](https://www.imagerep.io/)
64

7-
You take a micrograph of a material. You segment it, and measure the phase fractions. How sure are you that the phase fraction of the whole material is close to your measurements?
8-
Here we define 'representativity' as [1]
9-
> A microstructure is $(c, d)$-property representative if the measured value of the microstructural property deviates by no more than $d\%$ from the bulk material property, with at least $c\%$ confidence. For example, if $(c,d)=(95,3)$, and the property is phase-fraction, this means we can be $95\%$ confident that the measured phase-fraction is within $3\%$ of the bulk material phase-fraction.
10-
11-
We introduce the 'ImageRep' model for performing fast phase-fraction representativity estimation from a single microstructural image. This is achieved by estimating the Two-Point Correlation (TPC) function of the image via the FFT. From the TPC the 'Integral Range' can be directly determined - the Integral Range has previously been determined using (slow) statistical methods. We then represent the image as binary squares of length 'Integral Range' which are samples from a Bernoulli distribution with a probability determined by the measured phase fraction. From this we can establish the uncertainty in the phase fraction in the image to a given confidence, **and** the image size that would be needed to meet a given target uncertainty.
5+
Here we introduce the 'ImageRep' method for fast phase fraction representativity estimation from a single microstructural image. This is achieved by calculating the Two-Point Correlation (TPC) function of the image, combined with a data-driven analysis of the [MicroLib](https://microlib.io/) dataset. By applying a statistical framework that utilizes both data sources, we can establish the uncertainty in the phase fraction in the image with a given confidence, **and** the image size that would be needed to meet a given target uncertainty. Further details are provided in our [paper](CITATION.cff).
126

13-
If you use this model in your research, [please cite us](CITATION.cff).
7+
If you use this ImageRep in your research, [please cite us](CITATION.cff).
148

159
## Usage:
1610

17-
This model can be used as python package - see [`example.ipynb`](example.ipynb) or via the [website (imagerep.io)](https://www.imagerep.io/).
11+
This method can be used via the [website (imagerep.io)](https://www.imagerep.io/) or as python package - see [`example.ipynb`](example.ipynb).
1812

1913
<p align="center">
2014
<img src="https://sambasegment.blob.core.windows.net/resources/repr_repo_v2.gif">
2115
</p>
2216

23-
NB: the website may run out of memory for large volumes (>1000x1000x1000) - if this happens run the model locally or contact us
17+
NB: the website may run out of memory for large volumes (>1000x1000x1000) - if this happens run the method locally or contact us
2418

2519
## Limitations:
2620
- **This is not the only source of uncertainty!** Other sources *i.e,* segmentation uncertainty, also contribute and may be larger
27-
- For multi-phase materials, this model estimates the uncertainty in phase-fraction of a single (chosen) phase, counting all the others as a single phase (*i.e,* a binary microstructure)
21+
- For multi-phase materials, this method estimates the uncertainty in phase-fraction of a single (chosen) phase, counting all the others as a single phase (*i.e,* a binary microstructure)
2822
- Not validated for for images smaller than 200x200 or 200x200x200
2923
- Not validated for large integral ranges/features sizes (>70 px)
3024
- Not designed for periodic structures
3125
- 'Length needed for target uncertainty' is an intentionally conservative estimate - retry when you have measured the larger sample to see a more accurate estimate of that uncertainty
3226

3327
## Local Installation Instructions
3428

35-
These instructions are for installing and running the model locally. They assume a UNIX enviroment (mac or linux), but adapting for Windows is straightforward. Note you will need 2 terminals, one for the frontend local server and one for the backend local server.
29+
These instructions are for installing and running the method locally. They assume a UNIX enviroment (mac or linux), but adapting for Windows is straightforward. Note you will need 2 terminals, one for the frontend local server and one for the backend local server.
3630

3731
### Preliminaries
3832

@@ -51,7 +45,7 @@ git clone https://github.com/tldr-group/Representativity && cd Representativity
5145
pip install -e .
5246
```
5347

54-
**NOTE: this is all you need to do if you wish to use the model via the python package.** To run the website locally, follow the rest of the instructions.
48+
**NOTE: this is all you need to do if you wish to use the method via the python package.** To run the website locally, follow the rest of the instructions.
5549

5650
2. With your virtual environment activated, and inside the `representativity/` directory, run
5751

@@ -86,6 +80,8 @@ yarn && yarn start
8680

8781
## Testing Instructions
8882

83+
![Tests](https://github.com/tldr-group/Representativity/actions/workflows/tests.yml/badge.svg)
84+
8985
1. Run (with your virtual enviroment activated!)
9086

9187
```

frontend/src/App.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import NormalSlider from "./components/NormalSlider";
88
import { Menu } from "./components/Menu";
99
import { ErrorMessage, CLSModal, MoreInfo } from "./components/Popups"
1010

11-
import { loadFromTIFF, loadFromImage } from "./components/imageLogic";
11+
import { loadFromTIFF, loadFromImage, getPhaseFraction } from "./components/imageLogic";
1212

1313
import "./assets/scss/App.scss";
1414
import 'bootstrap/dist/css/bootstrap.min.css';
1515

16-
const PATH = "http://127.0.0.1:5000";
17-
//const PATH = "https://samba-segment.azurewebsites.net";
16+
//const PATH = "http://127.0.0.1:5000";
17+
const PATH = "https://samba-segment.azurewebsites.net";
1818
//const PATH = "http://localhost:7071/api";
1919
//const PATH = "https://representative.azurewebsites.net/api"
2020
const PF_ENDPOINT = PATH + "/phasefraction"
@@ -147,7 +147,13 @@ const App = () => {
147147
})
148148

149149
const vals = imageInfo?.phaseVals!
150-
const phaseFrac = accurateFractions![vals[selectedPhase - 1]]
150+
const phaseFrac = (accurateFractions != null) ?
151+
accurateFractions[vals[selectedPhase - 1]]
152+
: getPhaseFraction(
153+
imageInfo?.previewData.data!,
154+
vals[selectedPhase - 1]
155+
);
156+
151157
setPfB([phaseFrac - absErr, phaseFrac + absErr])
152158

153159
if (obj["cls"] > IR_LIMIT_PX) { setShowWarning("cls") }

frontend/src/assets/default_3D.tiff

15.4 MB
Binary file not shown.

frontend/src/components/DragDrop.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React, { useContext, useEffect, useRef, useState } from "react";
22
import { DragDropProps } from "./interfaces";
33

4-
const DEFAULT_IMAGE = "../assets/default.tiff";
4+
const DEFAULT_IMAGE_2D = "../assets/default_2D.tiff";
5+
const DEFAULT_IMAGE_3D = "../assets/default_3D.tiff";
56

67
export const dragDropStyle = {
78
height: '75vh', width: '75vw',
@@ -27,8 +28,8 @@ const DragDrop = ({ loadFromFile }: DragDropProps): JSX.Element => {
2728
};
2829
};
2930

30-
const viewExample = async () => {
31-
const url = new URL(DEFAULT_IMAGE, location.origin);
31+
const viewExample = async (path: string) => {
32+
const url = new URL(path, location.origin);
3233
console.log(url)
3334
const resp = await fetch(url);
3435
const data = await resp.blob();
@@ -43,7 +44,7 @@ const DragDrop = ({ loadFromFile }: DragDropProps): JSX.Element => {
4344
onDragOver={handleDrag}
4445
onDrop={handeDrop}
4546
>
46-
{(!isMobile) && <span>Drag microstructure file or <a style={{ cursor: "pointer", color: 'blue' }} onClick={viewExample}> view example!</a></span>}
47+
{(!isMobile) && <span>Drag microstructure file, or view example <a style={{ cursor: "pointer", color: 'blue' }} onClick={e => viewExample(DEFAULT_IMAGE_2D)}>in 2D</a> or <a style={{ cursor: "pointer", color: 'blue' }} onClick={e => viewExample(DEFAULT_IMAGE_3D)}> 3D</a></span>}
4748
</div>
4849
);
4950
}

frontend/src/components/Menu.tsx

+21-7
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,24 @@ const Result = () => {
191191
const lResultRef = useRef<HTMLHeadingElement>(null);
192192

193193
const vals = imageInfo?.phaseVals!
194-
const phaseFrac = (accurateFractions != null) ?
195-
accurateFractions[vals[selectedPhase - 1]]
196-
: getPhaseFraction(
197-
imageInfo?.previewData.data!,
198-
vals[selectedPhase - 1]
199-
);
194+
195+
const getPhaseFracs = () => {
196+
const accurateAvailable = accurateFractions != null
197+
const coarseAvailable = (imageInfo != null) && (imageInfo.previewData.data != null)
198+
199+
if (accurateAvailable) {
200+
return accurateFractions[vals[selectedPhase - 1]]
201+
} else if (coarseAvailable) {
202+
return getPhaseFraction(
203+
imageInfo?.previewData.data!,
204+
vals[selectedPhase - 1]
205+
);
206+
} else {
207+
return 0
208+
}
209+
}
210+
211+
const phaseFrac = getPhaseFracs();
200212

201213

202214
const l = analysisInfo?.lForDefaultErr;
@@ -257,6 +269,8 @@ const Result = () => {
257269
const vol = (ii?.nDims! == 3) ? (ii?.height! * ii?.width! * ii?.width!) : (ii?.height! * ii?.width!)
258270
const nMore = (Math.ceil(Math.pow(l!, imageInfo?.nDims!) / vol)) - 1
259271

272+
const modalTitle = `Results for "${imageInfo?.file?.name}"`
273+
260274
const title = "Phase Fraction Estimation of the Material"
261275

262276
const smallResults = (
@@ -313,7 +327,7 @@ const Result = () => {
313327
const largeResults = (<>
314328
<Modal show={showFull} onHide={handleClose} size="lg">
315329
<Modal.Header style={{ backgroundColor: '#212529', color: '#ffffff' }} closeVariant="white" closeButton>
316-
<Modal.Title>Results!</Modal.Title>
330+
<Modal.Title>{modalTitle}</Modal.Title>
317331
</Modal.Header>
318332
<Modal.Body>
319333
<Accordion defaultActiveKey={['0', '1']} flush alwaysOpen>

frontend/src/components/Popups.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const CLSModal = () => {
7171
}
7272
}
7373

74-
const txt = (showWarning == "over") ? 'Success!' : "Warning!"
74+
const txt = (showWarning == "over") ? 'Good news!' : "Warning!"
7575
const bg = (showWarning == "over") ? '#6ac40a' : '#fcba03'
7676

7777
return (
@@ -95,12 +95,14 @@ export const MoreInfo = () => {
9595
imageInfo: [imageInfo,],
9696
analysisInfo: [analysisInfo,],
9797
showInfo: [showInfo, setShowInfo],
98-
menuState: [, setMenuState]
98+
menuState: [menuState, setMenuState]
9999
} = useContext(AppContext)!;
100100

101101
const handleClose = () => {
102102
setShowInfo(false);
103-
setMenuState('conf_result');
103+
if (menuState == "conf_result_full") {
104+
setMenuState('conf_result');
105+
}
104106
};
105107

106108
return (

frontend/src/components/Topbar.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import React, { useContext, useEffect, useRef, useState } from "react";
2+
import AppContext from "./interfaces";
23

34
import Container from 'react-bootstrap/Container';
45
import Nav from 'react-bootstrap/Nav';
56
import Navbar from 'react-bootstrap/Navbar';
67
import { TopbarProps } from "./interfaces";
78

89
const Topbar = ({ loadFromFile, reset, changePhase }: TopbarProps) => {
10+
11+
const {
12+
showInfo: [showInfo, setShowInfo],
13+
} = useContext(AppContext)!;
914
const fileInputRef = useRef<HTMLInputElement>(null);
1015

1116
const addData = () => {
@@ -24,12 +29,13 @@ const Topbar = ({ loadFromFile, reset, changePhase }: TopbarProps) => {
2429

2530
return (
2631
<Navbar bg={"dark"} variant="dark" expand="lg" style={{ boxShadow: "1px 1px 1px grey" }}>
27-
<Container>
32+
<Container style={{ display: 'flex', justifyContent: 'space-around' }}>
2833
{/*path for these assets need to be relative to index.html in assets/*/}
2934
<Navbar.Brand><img src="favicon.png" width="40" height="40" className="d-inline-block align-top" /></Navbar.Brand>
30-
<Navbar.Brand style={{ marginTop: "3px", fontSize: "1.75em" }}>ImageRep</Navbar.Brand>
35+
<Navbar.Brand style={{ marginTop: "3px", fontSize: "1.75em", flexGrow: 2 }}>ImageRep</Navbar.Brand>
3136

3237
<Nav>
38+
<Nav.Link onClick={e => setShowInfo(true)}>Model Info</Nav.Link>
3339
<Nav.Link onClick={addData}>Add Data</Nav.Link>
3440
<Nav.Link onClick={changePhase} style={{ color: "#f2cd29" }}>Change Phase</Nav.Link>
3541
<input

frontend/src/components/imageLogic.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const getPhaseFraction = (arr: Uint8ClampedArray, val: number, nChannels:
3838
console.log('ahhhh')
3939
const uniqueVals = arr.filter((_, i, __) => { return i % nChannels == 0 })
4040
const matching = uniqueVals.filter((v) => v == val);
41-
return (100 * matching.length) / (arr.length / nChannels);
41+
return (matching.length) / (arr.length / nChannels);
4242
}
4343

4444
export const loadFromTIFF = (tiffBuffer: ArrayBuffer): ImageLoadInfo => {
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
import random
4+
from itertools import product
5+
import porespy as ps
6+
from representativity.validation import validation
7+
8+
9+
if __name__ == '__main__':
10+
num_generators = 50
11+
num_images = 5
12+
generators_chosen = np.random.choice(num_generators, num_images, replace=False)
13+
images = []
14+
large_img_size = np.array([1000, 1000])
15+
img_size = np.array([200, 200])
16+
alpha = 1
17+
ps_generators = validation.get_ps_generators()
18+
rand_iter = 0
19+
for generator, params in ps_generators.items():
20+
for value_comb in product(*params.values()):
21+
if rand_iter in generators_chosen:
22+
args = {key: value for key, value in zip(params.keys(), value_comb)}
23+
args = validation.factors_to_params(args, im_shape=large_img_size)
24+
image = validation.get_large_im_stack(generator, large_img_size, 1, args)
25+
image = image[0]
26+
image = image[:img_size[0], :img_size[1]]
27+
images.append(image)
28+
rand_iter += 1
29+
random.shuffle(images)
30+
31+
layers = num_images # How many images should be stacked.
32+
x_offset, y_offset = img_size[0]-25, 30 # Number of pixels to offset each image.
33+
34+
new_shape = ((layers - 1)*y_offset + images[0].shape[0],
35+
(layers - 1)*x_offset + images[0].shape[1]
36+
) # the last number, i.e. 4, refers to the 4 different channels, being RGB + alpha
37+
38+
stacked = np.zeros(new_shape)
39+
40+
41+
for layer in range(layers):
42+
cur_im = images[layer]
43+
stacked[layer*y_offset:layer*y_offset + cur_im.shape[0],
44+
layer*x_offset:layer*x_offset + cur_im.shape[1]
45+
] += cur_im
46+
stacked = 1 - stacked
47+
48+
49+
50+
# Create the PoreSpy images:
51+
ax_porespy_im = fig.add_subplot(gs[0, 0])
52+
ax_porespy_im.imshow(stacked, vmin=0, vmax=1, cmap='gray', interpolation='nearest')
53+
ax_porespy_im.set_title('(a)')
54+
ax_porespy_im.axis('off')
55+
56+
pos1 = ax_porespy_im.get_position() # get the original position
57+
pos2 = [pos1.x0 - 0.15, pos1.y0+0, pos1.width+0.1, pos1.height+0.1]
58+
ax_porespy_im.set_position(pos2)

0 commit comments

Comments
 (0)