Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #46

Merged
merged 24 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f5997b6
changed 'success' to 'good news!'
Aug 7, 2024
7c5cc78
fixed race condition between server phase frac pings and repr results
Aug 7, 2024
ef16844
can view image info from topbar
Aug 7, 2024
7cb9ff4
aligned name on LHS
Aug 7, 2024
2643d1f
updated examples & drag drop
Aug 7, 2024
2e9d649
Merge pull request #41 from tldr-group/development
amirDahari1 Aug 7, 2024
6605ac7
having real validations
amirDahari1 Sep 4, 2024
fa7c8f8
Merge branch 'paper' of https://github.com/tldr-group/Representativit…
amirDahari1 Sep 4, 2024
a6329e9
added validation of sofc anode 2d
amirDahari1 Sep 6, 2024
c8ed207
Move the porespy gens to SI
amirDahari1 Sep 6, 2024
14174e4
Changing the validation fig
amirDahari1 Sep 6, 2024
24dd692
continuing model_accuracy fig for 4 rows
amirDahari1 Sep 10, 2024
ed067e2
organising figures
amirDahari1 Sep 18, 2024
d86fbcd
separated between tables and validation figure, and added separator r…
amirDahari1 Sep 19, 2024
59d62ad
validation figures update
amirDahari1 Sep 23, 2024
21927f6
finished validation figure
amirDahari1 Sep 25, 2024
d5c1e5a
Add introduction figure
amirDahari1 Sep 25, 2024
4b7e68e
halfway through introduction fig
amirDahari1 Sep 25, 2024
1f1de41
more intro figure
amirDahari1 Sep 30, 2024
5fd1152
almost finishing with intro fig, before adding microlib images
amirDahari1 Oct 1, 2024
3a74f0e
Finished intro fig
amirDahari1 Oct 7, 2024
1d08096
Updated README, closes #42
amirDahari1 Oct 7, 2024
2ba7f79
Updated README
amirDahari1 Oct 7, 2024
6b315c6
Merge pull request #45 from tldr-group/paper
amirDahari1 Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ authors:
orcid: 0000-0003-0142-8597
- name: Ronan Docherty
orcid: 0000-0002-7332-0924
- name: Steve Kench
orcid: 0000-0002-7263-6724
- name: Samuel J. Cooper
orcid: 0000-0003-4055-6903
title: "Predicting Microstructural Representativity from a Single Image"
title: "Prediction of Microstructural Representativity from a Single Image"
doi: ARXIV_DOI
url: "https://github.com/tldr-group/Representativity"
url: "https://github.com/tldr-group/ImageRep"
preferred-citation:
type: article
authors:
- name: Amir Dahari
orcid: 0000-0003-0142-8597
- name: Ronan Docherty
orcid: 0000-0002-7332-0924
- name: Steve Kench
orcid: 0000-0002-7263-6724
- name: Samuel J. Cooper
orcid: 0000-0003-4055-6903
doi: ARXIV_DOI
journal: "arXiV preprint"
month: 8
title: "Predicting Microstructural Representativity from a Single Image"
title: "Prediction of Microstructural Representativity from a Single Image"
year: 2024
24 changes: 10 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
# Representativity

![Tests](https://github.com/tldr-group/Representativity/actions/workflows/tests.yml/badge.svg)
# ImageRep

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

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?
Here we define 'representativity' as [1]
> 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.

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

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

## Usage:

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

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

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

## Limitations:
- **This is not the only source of uncertainty!** Other sources *i.e,* segmentation uncertainty, also contribute and may be larger
- 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)
- 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)
- Not validated for for images smaller than 200x200 or 200x200x200
- Not validated for large integral ranges/features sizes (>70 px)
- Not designed for periodic structures
- '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

## Local Installation Instructions

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

### Preliminaries

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

**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.
**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.

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

Expand Down Expand Up @@ -86,6 +80,8 @@ yarn && yarn start

## Testing Instructions

![Tests](https://github.com/tldr-group/Representativity/actions/workflows/tests.yml/badge.svg)

1. Run (with your virtual enviroment activated!)

```
Expand Down
14 changes: 10 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import NormalSlider from "./components/NormalSlider";
import { Menu } from "./components/Menu";
import { ErrorMessage, CLSModal, MoreInfo } from "./components/Popups"

import { loadFromTIFF, loadFromImage } from "./components/imageLogic";
import { loadFromTIFF, loadFromImage, getPhaseFraction } from "./components/imageLogic";

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

const PATH = "http://127.0.0.1:5000";
//const PATH = "https://samba-segment.azurewebsites.net";
//const PATH = "http://127.0.0.1:5000";
const PATH = "https://samba-segment.azurewebsites.net";
//const PATH = "http://localhost:7071/api";
//const PATH = "https://representative.azurewebsites.net/api"
const PF_ENDPOINT = PATH + "/phasefraction"
Expand Down Expand Up @@ -147,7 +147,13 @@ const App = () => {
})

const vals = imageInfo?.phaseVals!
const phaseFrac = accurateFractions![vals[selectedPhase - 1]]
const phaseFrac = (accurateFractions != null) ?
accurateFractions[vals[selectedPhase - 1]]
: getPhaseFraction(
imageInfo?.previewData.data!,
vals[selectedPhase - 1]
);

setPfB([phaseFrac - absErr, phaseFrac + absErr])

if (obj["cls"] > IR_LIMIT_PX) { setShowWarning("cls") }
Expand Down
File renamed without changes.
Binary file added frontend/src/assets/default_3D.tiff
Binary file not shown.
9 changes: 5 additions & 4 deletions frontend/src/components/DragDrop.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import { DragDropProps } from "./interfaces";

const DEFAULT_IMAGE = "../assets/default.tiff";
const DEFAULT_IMAGE_2D = "../assets/default_2D.tiff";
const DEFAULT_IMAGE_3D = "../assets/default_3D.tiff";

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

const viewExample = async () => {
const url = new URL(DEFAULT_IMAGE, location.origin);
const viewExample = async (path: string) => {
const url = new URL(path, location.origin);
console.log(url)
const resp = await fetch(url);
const data = await resp.blob();
Expand All @@ -43,7 +44,7 @@ const DragDrop = ({ loadFromFile }: DragDropProps): JSX.Element => {
onDragOver={handleDrag}
onDrop={handeDrop}
>
{(!isMobile) && <span>Drag microstructure file or <a style={{ cursor: "pointer", color: 'blue' }} onClick={viewExample}> view example!</a></span>}
{(!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>}
</div>
);
}
Expand Down
28 changes: 21 additions & 7 deletions frontend/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,24 @@ const Result = () => {
const lResultRef = useRef<HTMLHeadingElement>(null);

const vals = imageInfo?.phaseVals!
const phaseFrac = (accurateFractions != null) ?
accurateFractions[vals[selectedPhase - 1]]
: getPhaseFraction(
imageInfo?.previewData.data!,
vals[selectedPhase - 1]
);

const getPhaseFracs = () => {
const accurateAvailable = accurateFractions != null
const coarseAvailable = (imageInfo != null) && (imageInfo.previewData.data != null)

if (accurateAvailable) {
return accurateFractions[vals[selectedPhase - 1]]
} else if (coarseAvailable) {
return getPhaseFraction(
imageInfo?.previewData.data!,
vals[selectedPhase - 1]
);
} else {
return 0
}
}

const phaseFrac = getPhaseFracs();


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

const modalTitle = `Results for "${imageInfo?.file?.name}"`

const title = "Phase Fraction Estimation of the Material"

const smallResults = (
Expand Down Expand Up @@ -313,7 +327,7 @@ const Result = () => {
const largeResults = (<>
<Modal show={showFull} onHide={handleClose} size="lg">
<Modal.Header style={{ backgroundColor: '#212529', color: '#ffffff' }} closeVariant="white" closeButton>
<Modal.Title>Results!</Modal.Title>
<Modal.Title>{modalTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Accordion defaultActiveKey={['0', '1']} flush alwaysOpen>
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/Popups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const CLSModal = () => {
}
}

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

return (
Expand All @@ -95,12 +95,14 @@ export const MoreInfo = () => {
imageInfo: [imageInfo,],
analysisInfo: [analysisInfo,],
showInfo: [showInfo, setShowInfo],
menuState: [, setMenuState]
menuState: [menuState, setMenuState]
} = useContext(AppContext)!;

const handleClose = () => {
setShowInfo(false);
setMenuState('conf_result');
if (menuState == "conf_result_full") {
setMenuState('conf_result');
}
};

return (
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/components/Topbar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, { useContext, useEffect, useRef, useState } from "react";
import AppContext from "./interfaces";

import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import { TopbarProps } from "./interfaces";

const Topbar = ({ loadFromFile, reset, changePhase }: TopbarProps) => {

const {
showInfo: [showInfo, setShowInfo],
} = useContext(AppContext)!;
const fileInputRef = useRef<HTMLInputElement>(null);

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

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

<Nav>
<Nav.Link onClick={e => setShowInfo(true)}>Model Info</Nav.Link>
<Nav.Link onClick={addData}>Add Data</Nav.Link>
<Nav.Link onClick={changePhase} style={{ color: "#f2cd29" }}>Change Phase</Nav.Link>
<input
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/imageLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const getPhaseFraction = (arr: Uint8ClampedArray, val: number, nChannels:
console.log('ahhhh')
const uniqueVals = arr.filter((_, i, __) => { return i % nChannels == 0 })
const matching = uniqueVals.filter((v) => v == val);
return (100 * matching.length) / (arr.length / nChannels);
return (matching.length) / (arr.length / nChannels);
}

export const loadFromTIFF = (tiffBuffer: ArrayBuffer): ImageLoadInfo => {
Expand Down
58 changes: 58 additions & 0 deletions paper_figures/SI_figures/porespy_ims.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import numpy as np
import matplotlib.pyplot as plt
import random
from itertools import product
import porespy as ps
from representativity.validation import validation


if __name__ == '__main__':
num_generators = 50
num_images = 5
generators_chosen = np.random.choice(num_generators, num_images, replace=False)
images = []
large_img_size = np.array([1000, 1000])
img_size = np.array([200, 200])
alpha = 1
ps_generators = validation.get_ps_generators()
rand_iter = 0
for generator, params in ps_generators.items():
for value_comb in product(*params.values()):
if rand_iter in generators_chosen:
args = {key: value for key, value in zip(params.keys(), value_comb)}
args = validation.factors_to_params(args, im_shape=large_img_size)
image = validation.get_large_im_stack(generator, large_img_size, 1, args)
image = image[0]
image = image[:img_size[0], :img_size[1]]
images.append(image)
rand_iter += 1
random.shuffle(images)

layers = num_images # How many images should be stacked.
x_offset, y_offset = img_size[0]-25, 30 # Number of pixels to offset each image.

new_shape = ((layers - 1)*y_offset + images[0].shape[0],
(layers - 1)*x_offset + images[0].shape[1]
) # the last number, i.e. 4, refers to the 4 different channels, being RGB + alpha

stacked = np.zeros(new_shape)


for layer in range(layers):
cur_im = images[layer]
stacked[layer*y_offset:layer*y_offset + cur_im.shape[0],
layer*x_offset:layer*x_offset + cur_im.shape[1]
] += cur_im
stacked = 1 - stacked



# Create the PoreSpy images:
ax_porespy_im = fig.add_subplot(gs[0, 0])
ax_porespy_im.imshow(stacked, vmin=0, vmax=1, cmap='gray', interpolation='nearest')
ax_porespy_im.set_title('(a)')
ax_porespy_im.axis('off')

pos1 = ax_porespy_im.get_position() # get the original position
pos2 = [pos1.x0 - 0.15, pos1.y0+0, pos1.width+0.1, pos1.height+0.1]
ax_porespy_im.set_position(pos2)
Loading
Loading