Skip to content

Commit 4b1a8cc

Browse files
author
Jason Wang
committed
[Add] WSI Preview (Frontend)
1 parent 7915669 commit 4b1a8cc

File tree

6 files changed

+177
-31
lines changed

6 files changed

+177
-31
lines changed

web/apps/labelstudio/src/app/App.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ const content = document.querySelector("#main-content");
9797

9898
render(<App content={content.innerHTML} />, root);
9999

100+
setTimeout(() => {
101+
const script = document.createElement('script');
102+
script.src = '/static/js/openseadragon.js';
103+
script.defer = true;
104+
document.body.appendChild(script);
105+
const script2 = document.createElement('script');
106+
script2.src = '/static/js/jquery.min.js';
107+
script2.defer = true;
108+
document.body.appendChild(script2);
109+
110+
}, 0)
111+
100112
if (module?.hot) {
101113
module.hot.accept(); // Enable HMR for React components
102114
}

web/apps/labelstudio/src/pages/CreateProject/Config/Config.jsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -478,33 +478,36 @@ const Configurator = ({
478478
<div className={configClass.elem("editor")}>
479479
{configure === "code" && (
480480
<div className={configClass.elem("code")} style={{ display: configure === "code" ? undefined : "none" }}>
481-
<CodeMirror
482-
name="code"
483-
id="edit_code"
484-
value={config}
485-
autoCloseTags={true}
486-
smartIndent={true}
487-
detach
488-
extensions={["hint", "xml-hint"]}
489-
options={{
490-
mode: "xml",
491-
theme: "default",
492-
lineNumbers: true,
493-
extraKeys: {
494-
"'<'": completeAfter,
495-
// "'/'": completeIfAfterLt,
496-
"' '": completeIfInTag,
497-
"'='": completeIfInTag,
498-
"Ctrl-Space": "autocomplete",
499-
},
500-
hintOptions: { schemaInfo: tags },
501-
}}
502-
// don't close modal with Escape while editing config
503-
onKeyDown={(editor, e) => {
504-
if (e.code === "Escape") e.stopPropagation();
505-
}}
506-
onChange={(editor, data, value) => onChange(value)}
507-
/>
481+
<textarea value={config} onChange={(e) => {
482+
onChange(e.target.value);
483+
}}></textarea>
484+
{/*<CodeMirror*/}
485+
{/* name="code"*/}
486+
{/* id="edit_code"*/}
487+
{/* value={config}*/}
488+
{/* autoCloseTags={true}*/}
489+
{/* smartIndent={true}*/}
490+
{/* detach*/}
491+
{/* extensions={["hint", "xml-hint"]}*/}
492+
{/* options={{*/}
493+
{/* mode: "xml",*/}
494+
{/* theme: "default",*/}
495+
{/* lineNumbers: true,*/}
496+
{/* extraKeys: {*/}
497+
{/* "'<'": completeAfter,*/}
498+
{/* // "'/'": completeIfAfterLt,*/}
499+
{/* "' '": completeIfInTag,*/}
500+
{/* "'='": completeIfInTag,*/}
501+
{/* "Ctrl-Space": "autocomplete",*/}
502+
{/* },*/}
503+
{/* hintOptions: { schemaInfo: tags },*/}
504+
{/* }}*/}
505+
{/* // don't close modal with Escape while editing config*/}
506+
{/* onKeyDown={(editor, e) => {*/}
507+
{/* if (e.code === "Escape") e.stopPropagation();*/}
508+
{/* }}*/}
509+
{/* onChange={(editor, data, value) => onChange(value)}*/}
510+
{/*/>*/}
508511
</div>
509512
)}
510513
{visualLoaded && (

web/libs/datamanager/src/components/CellViews/ImageCell.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ export const ImageCell = (column) => {
1919

2020
if (!imgSrc) return null;
2121

22+
const getImageSrc = (src) => {
23+
if (data.some(key => src?.includes(key))) {
24+
return `${src}&level=6&col=0&row=0`;
25+
}
26+
return src;
27+
}
28+
2229
return renderImagePreview ? (
2330
<img
2431
{...imgDefaultProps}
2532
key={imgSrc}
26-
src={imgSrc}
33+
src={getImageSrc(imgSrc)}
2734
alt="Data"
2835
style={{
2936
maxHeight: "100%",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { observer } from "mobx-react";
2+
import { forwardRef, useCallback, useMemo, useEffect } from "react";
3+
import { Block, Elem } from '../../utils/bem';
4+
import { FF_LSDV_4711, isFF } from '../../utils/feature-flags';
5+
import messages from '../../utils/messages';
6+
import { ErrorMessage } from "../ErrorMessage/ErrorMessage";
7+
import { drawSeaDragon } from "./setOpenSeaDragon";
8+
9+
export const RELATIVE_STAGE_WIDTH = 100;
10+
export const RELATIVE_STAGE_HEIGHT = 100;
11+
export const SNAP_TO_PIXEL_MODE = {
12+
EDGE: "edge",
13+
CENTER: "center",
14+
};
15+
16+
export const Dragon = observer(
17+
forwardRef(({imageStyles, imageEntity}, ref) => {
18+
const getImageData = async (src) => {
19+
fetch(src).then(response => {
20+
if (!response.ok) {
21+
22+
}
23+
return response.text();
24+
}).then(xmlString => {
25+
const parser = new DOMParser();
26+
const xmlDoc = parser.parseFromString(xmlString, "application/xml");
27+
const imageElement = xmlDoc.documentElement;
28+
const sizeElement = xmlDoc.querySelector("Size");
29+
30+
const tileSize = imageElement.getAttribute("TileSize");
31+
const width = sizeElement.getAttribute("Width");
32+
const height = sizeElement.getAttribute('Height')
33+
viewer = drawSeaDragon(imageEntity.src, width -0,height-0,tileSize-0);
34+
})
35+
36+
}
37+
38+
let viewer = null;
39+
if (imageEntity.src) {
40+
getImageData(imageEntity.src);
41+
}
42+
useEffect(() => {
43+
if(viewer) {
44+
viewer.destroy();
45+
viewer = null;
46+
}
47+
if (imageEntity.src) {
48+
getImageData(imageEntity.src);
49+
}
50+
}, [imageStyles, imageEntity.src])
51+
return (<></>);
52+
}),
53+
);

web/libs/editor/src/components/ImageView/ImageView.jsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from "../../utils/feature-flags";
3333
import { Pagination } from "../../common/Pagination/Pagination";
3434
import { Image } from "./Image";
35+
import { Dragon } from "./Dragron";
3536

3637
Konva.showWarnings = false;
3738

@@ -916,9 +917,21 @@ export default observer(
916917

917918
const imageIsLoaded = item.imageIsLoaded || !isFF(FF_LSDV_4583_6);
918919
const isViewingAll = store.annotationStore.viewingAll;
919-
920+
const isDragon = (src) => {
921+
if (!src) {
922+
return true;
923+
}
924+
const data = ['.svs', '.csp','.sdpc', 'tiff', '.ndpi', '.scn', '.mrxs', '.bif','.svslide']
925+
return !data.some(key => src?.includes(key));
926+
}
920927
return (
921-
<ObjectTag item={item} className={wrapperClasses.join(" ")}>
928+
(!isDragon(item?.currentImageEntity?.src) ? <>
929+
<>
930+
<div id="img-mem-view" style={{width: '100%', height:'100%'}}></div>
931+
<Dragon imageEntity={item.currentImageEntity}></Dragon>
932+
</>
933+
</> :
934+
<ObjectTag item={item} className={wrapperClasses.join(" ")}>
922935
{paginationEnabled ? (
923936
<div
924937
className={styles.pagination}
@@ -1056,7 +1069,8 @@ export default observer(
10561069
</div>
10571070
)}
10581071
</ObjectTag>
1059-
);
1072+
)
1073+
);
10601074
}
10611075
},
10621076
);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
let viewer: any = null;
2+
3+
export const drawSeaDragon = (src: any, width: any, height: any, tileSize: any)=> {
4+
const OpenSeaDragon = (window as any)?.OpenSeaDragon;
5+
const container = document.querySelector('#img-mem-view');
6+
if (!src || !height || !width || !tileSize) {
7+
return;
8+
}
9+
10+
if (viewer) {
11+
viewer.destroy();
12+
viewer = null;
13+
}
14+
15+
if (!src && viewer) {
16+
viewer.destroy();
17+
viewer = null;
18+
return;
19+
}
20+
if (!container && viewer) {
21+
viewer.destroy();
22+
viewer = null;
23+
}
24+
25+
if (container && !viewer && OpenSeaDragon) {
26+
viewer = new OpenSeadragon({
27+
id: "img-mem-view",
28+
tileSources: [
29+
{
30+
width,
31+
height,
32+
tileSize,
33+
getTileUrl: function (level: string, x: string, y: string) {
34+
return `${src}&level=${level}&row=${y}&col=${x}`;
35+
}
36+
}
37+
],
38+
sequenceMode: true,
39+
preserveViewport: true,
40+
prefixUrl: "/static/images/",
41+
showNavigator: true,
42+
showRotationControl: true,
43+
animationTime: 0.5,
44+
blendTime: 0.1,
45+
constrainDuringPan: true,
46+
maxZoomPixelRatio: 2,
47+
minZoomImageRatio: 1,
48+
visibilityRatio: 1,
49+
zoomPerScroll: 2,
50+
timeout: 120000,
51+
showNavigationControl: true,
52+
setMouseNavEnabled: true,
53+
});
54+
}
55+
56+
return viewer;
57+
};

0 commit comments

Comments
 (0)