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

Merge dev onto main #104

Merged
merged 5 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const App = () => {
overlayType: [, setOverlayType],
postProcess: [postProcess,],
features: [features,],
featureFlag: [featureFlag, setFeatureFlag],
processing: [, setProcessing],
errorObject: [errorObject, setErrorObject],
showToast: [, setShowToast],
Expand All @@ -128,7 +129,7 @@ const App = () => {

// Flags to check if featurising done (to block segmentation request on frontend)
const [segmentFlag, setSegmentFlag] = useState<boolean>(false);
const [featureFlag, setFeatureFlag] = useState<boolean>(false);

const [applyFlag, setApplyFlag] = useState<boolean>(false);

const { state } = useLocation();
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ const WelcomeModalContent = () => {

const FeatureModalContent = ({ closeModal, requestFeatures }: FeatureModalProps) => {
const {
features: [features, setFeatures]
features: [features, setFeatures],
featureFlag: [, setFeatureFlag],
} = useContext(AppContext)!;

const updateClose = () => {
Expand All @@ -146,6 +147,7 @@ const FeatureModalContent = ({ closeModal, requestFeatures }: FeatureModalProps)
}

const change = (e: any, feats: Features, newKey: string, newVal: string) => {
setFeatureFlag(false)
updateFeatures(feats, newKey, newVal);
}

Expand Down
120 changes: 118 additions & 2 deletions frontend/src/components/Stage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Canvas from "./Canvas"
import { BigModal, PostSegToast, MetricsToast, ErrorMessage } from "./Modals"
import AppContext from "./hooks/createContext";
import { DragDropProps, StageProps, themeBGs } from "./helpers/Interfaces";
import { imageDataToImage, getSplitInds } from "./helpers/canvasUtils";
import { imageDataToImage, getSplitInds, getSplitIndsHW } from "./helpers/canvasUtils";
import { LOAD_GALLERY_IMAGE_ENDPOINT } from "../App"

const UTIF = require("./UTIF.js")
Expand All @@ -21,6 +21,8 @@ const Stage = ({ loadImages, loadDefault, requestEmbedding, featuresUpdated, tra
image: [image,],
imgArrs: [imgArrs,],
imgType: [imgType, setImgType],
labelArr: [, setLabelArr],
labelArrs: [, setLabelArrs],
largeImg: [, setLargeImg],
errorObject: [, setErrorObject],
theme: [theme,],
Expand Down Expand Up @@ -57,6 +59,10 @@ const Stage = ({ loadImages, loadDefault, requestEmbedding, featuresUpdated, tra
}
}

// load tif, check if big or not
// if small and/or stack, loop through through each tiff and add as label by floor(255/val) = label
// if large, loop through, track whe

const loadPNGJPEG = (href: string) => {
// Load PNG or JPEF image via href.
const img = new Image();
Expand Down Expand Up @@ -104,6 +110,86 @@ const Stage = ({ loadImages, loadDefault, requestEmbedding, featuresUpdated, tra
setLargeImg(img);
}

const findDelta = (tifs: any) => {
const uniqueValues: number[] = [];
for (let tif of tifs) {
const imgDataArr = new Uint8ClampedArray(UTIF.toRGBA8(tif));
for (let i = 0; i < imgDataArr.length; i += 4) {
const val = imgDataArr[i];
if (!uniqueValues.includes(val) && val > 0) {
uniqueValues.push(val);
}
}
}
console.log(uniqueValues);
const delta = Math.min(...uniqueValues);
return delta;
}

const loadLabelTIFF = (result: ArrayBuffer) => {
const tifs = UTIF.decode(result);

for (let tif of tifs) {
UTIF.decodeImage(result, tif);
}
const tif_0 = tifs[0];
const h: number = tif_0.height;
const w: number = tif_0.width;
const isSmall = (w <= 1024 && h <= 1024);

const delta = findDelta(tifs);

let newLabelArrs: Uint8ClampedArray[] = []
if (isSmall) {
newLabelArrs = loadSmallLabelTIFF(tifs, delta);
} else {
newLabelArrs = loadLargeLabelTIFF(tif_0, delta, h, w);
}
setLabelArr(newLabelArrs[0]);
setLabelArrs(newLabelArrs);
}

const loadSmallLabelTIFF = (tifs: any, delta: number) => {
const newLabelArrs: Uint8ClampedArray[] = [];
for (let tif of tifs) {
const imgDataArr = new Uint8ClampedArray(UTIF.toRGBA8(tif));
const newLabelArr = new Uint8ClampedArray(imgDataArr.length / 4).fill(0);
for (let i = 0; i < imgDataArr.length; i += 4) {
const val = Math.round(imgDataArr[i] / delta);
newLabelArr[i / 4] = val;
}
newLabelArrs.push(newLabelArr);
}
return newLabelArrs
}

const loadLargeLabelTIFF = (tif: any, delta: number, h: number, w: number) => {
const newLabelArrs: Uint8ClampedArray[] = [];
const inds = getSplitIndsHW(h, w);
const [lw, lh] = [inds.dx, inds.dy]
const [nw, nh] = [inds.nW, inds.nH]
const imgDataArr = new Uint8ClampedArray(UTIF.toRGBA8(tif));
console.log(inds.h.length, nw * nh)
for (let j = 0; j < nw * nh; j++) {
const tempLabelArr = new Uint8ClampedArray(lw * lh).fill(0)
newLabelArrs.push(tempLabelArr)
}

for (let i_full = 0; i_full < imgDataArr.length; i_full += 4) {
const i = Math.floor(i_full / 4)
const x = i % w
const y = Math.floor(i / w)
const x_l = x % lw
const y_l = y % lh
const arr_n = Math.floor(x / lw) + nw * Math.floor(y / lh)
const new_i = x_l + lw * y_l
const val = Math.round(imgDataArr[i_full] / delta);
newLabelArrs[arr_n][new_i] = val
}

return newLabelArrs
}

const loadFromFile = (file: File) => {
// Load a file: reject if too large or not a JPG/PNG/TIFF then call correct function.
const reader = new FileReader();
Expand Down Expand Up @@ -141,6 +227,36 @@ const Stage = ({ loadImages, loadDefault, requestEmbedding, featuresUpdated, tra
};
}

const loadLabelFile = (file: File) => {
if (image === null) {
setErrorObject({ msg: `Must load image before loading labels!`, stackTrace: `No image` });
return
}
const reader = new FileReader();
const extension = file.name.split('.').pop()?.toLowerCase();
const isTIF = (extension === "tif" || extension === "tiff");
reader.onload = () => {
if (file.size > MAX_FILE_SIZE_BYTES) {
setErrorObject({ msg: `File size too large, please upload smaller image (<50MB).`, stackTrace: `File size ${file.size} > ${MAX_FILE_SIZE_BYTES}` });
return
}
try {
if (isTIF) {
loadLabelTIFF(reader.result as ArrayBuffer);
} else {
throw `Unsupported file format .${extension}`;
};
}
catch (e) {
const error = e as Error;
setErrorObject({ msg: "Label file must be .tif or .tiff", stackTrace: error.toString() });
}
}
if (isTIF) {
reader.readAsArrayBuffer(file); //array buffer for tif
}
}

const loadImageFromGallery = async (gallery_id: string) => {
const headers = new Headers();
headers.append('Content-Type', 'application/json;charset=utf-8');
Expand All @@ -161,7 +277,7 @@ const Stage = ({ loadImages, loadDefault, requestEmbedding, featuresUpdated, tra

return (
<div className={`w-full h-full`} style={{ background: themeBGs[theme][1] }}>
<Topbar loadFromFile={loadFromFile} deleteAll={deleteAll} deleteCurrent={deleteCurrent}
<Topbar loadFromFile={loadFromFile} loadLabelFile={loadLabelFile} deleteAll={deleteAll} deleteCurrent={deleteCurrent}
saveSeg={saveSeg} saveLabels={saveLabels} saveClassifier={saveClassifier}
loadClassifier={loadClassifier} applyClassifier={applyClassifier} />
<div className={`flex`} style={{ margin: '1.5%', background: themeBGs[theme][1] }} > {/*Canvas div on left, sidebar on right*/}
Expand Down
22 changes: 16 additions & 6 deletions frontend/src/components/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ToolTip = (str: string) => {



const Topbar = ({ loadFromFile, deleteAll, deleteCurrent, saveSeg, saveLabels, saveClassifier, loadClassifier, applyClassifier }: TopbarProps) => {
const Topbar = ({ loadFromFile, loadLabelFile, deleteAll, deleteCurrent, saveSeg, saveLabels, saveClassifier, loadClassifier, applyClassifier }: TopbarProps) => {
const {
modalShow: [modalShow, setModalShow],
labelType: [, setLabelType],
Expand All @@ -52,17 +52,20 @@ const Topbar = ({ loadFromFile, deleteAll, deleteCurrent, saveSeg, saveLabels, s
}

const togglePostProcess = (e: any) => {
setPostProcess(e.target.checked)
setPostProcess(e.target.checked);
}


const handleFileUpload = (e: any, type: "image" | "classifier") => {
const handleFileUpload = (e: any, type: "image" | "classifier" | "label") => {
// Open file dialog and load file.
const file: File | null = e.target.files?.[0] || null;
if (file != null) {
if (type === "image") {
loadFromFile(file);
} else {
} else if (type === "label") {
loadLabelFile(file);
}
else {
console.log("classifier");
loadClassifier(file);
}
Expand Down Expand Up @@ -103,15 +106,22 @@ const Topbar = ({ loadFromFile, deleteAll, deleteCurrent, saveSeg, saveLabels, s
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<NavDropdown title="Data" id="data-dropdown">
<NavDropdown.Item onClick={addData}>Add</NavDropdown.Item>
<NavDropdown.Item onClick={addData}>Load Image(s)</NavDropdown.Item>
<input
type='file'
id='file'
ref={fileInputRef}
style={{ display: 'none' }}
onChange={e => handleFileUpload(e, "image")} />
<NavDropdown.Item onClick={deleteCurrent}>Remove</NavDropdown.Item>
<NavDropdown.Item onClick={addData}>Load Labels</NavDropdown.Item>
<NavDropdown.Item onClick={deleteCurrent}>Remove Image</NavDropdown.Item>
<NavDropdown.Item onClick={e => setLabelType("Crop")}>Crop</NavDropdown.Item>
<input
type='file'
id='file'
ref={fileInputRef}
style={{ display: 'none' }}
onChange={e => handleFileUpload(e, "label")} />
<NavDropdown.Divider />
<NavDropdown.Item onClick={deleteAll}>Clear All</NavDropdown.Item>
</NavDropdown>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/helpers/Interfaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface StageProps {

export interface TopbarProps {
loadFromFile: (file: File) => void;
loadLabelFile: (file: File) => void;
deleteAll: () => void;
deleteCurrent: () => void;
saveSeg: () => void;
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/components/helpers/canvasUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,14 @@ export const drawImage = (ctx: CanvasRenderingContext2D, image: HTMLImageElement

// Methods to map our large image into a smaller one
export const getSplitInds = (image: HTMLImageElement) => {
const nW = Math.ceil(image.width / 1024);
const nH = Math.ceil(image.height / 1024);
const dx = image.width / nW;
const dy = image.height / nH;
return getSplitIndsHW(image.height, image.width)
}

export const getSplitIndsHW = (h: number, w: number) => {
const nW = Math.ceil(w / 1024);
const nH = Math.ceil(h / 1024);
const dx = w / nW;
const dy = h / nH;
const wInds = Array.from({ length: nW }, (x, i) => dx * i);
const hInds = Array.from({ length: nH }, (x, i) => dy * i);
const inds = { 'w': wInds, 'h': hInds, 'dx': dx, 'dy': dy, 'nW': nW, 'nH': nH };
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/hooks/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AppContextProvider = (props: {

// Segment Feature stuff
const [features, setFeatures] = useState<Features>(defaultFeatures)

const [featureFlag, setFeatureFlag] = useState<boolean>(false)
const [phaseFractions, setPhaseFractions] = useState<number[]>([0, 0, 0, 0, 0, 0, 0])

// Menus
Expand Down Expand Up @@ -90,6 +90,7 @@ const AppContextProvider = (props: {
processing: [processing, setProcessing],

features: [features, setFeatures],
featureFlag: [featureFlag, setFeatureFlag],
phaseFractions: [phaseFractions, setPhaseFractions],

errorObject: [errorObject, setErrorObject],
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/hooks/createContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ interface contextProps {
features: Features,
setFeatures: (e: Features) => void
]
featureFlag: [
featureFlag: boolean,
setFeatureFlag: (e: boolean) => void
]
phaseFractions: [
phaseFractions: number[],
setPhaseFractions: (e: number[]) => void
Expand Down