Skip to content

Commit

Permalink
Merge pull request #46 from tldr-group/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
amirDahari1 authored Oct 10, 2024
2 parents 021cd2f + 6b315c6 commit 0195450
Show file tree
Hide file tree
Showing 24 changed files with 1,376 additions and 483 deletions.
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

0 comments on commit 0195450

Please sign in to comment.