Yongbing chen/human in the loop (#517)
* upload analyze file to assets * move spinner label to strings * adjust labels * issuefix * typo * adjust getLabelValues * typo * remove option bar when upload asset in the future runs * feature: "Auto Labeling" * move AutoLabelingStatus to predictService.ts * update ocr when auto labeling (cherry picked from commit 42438598d5d7a325e82bbad191ed483f44094439) * fix tslint error * handle formRegion.text undefined error * decode asset name when upload asset * add HITL document override confirm * Merge branch 'yongbing-chen/human-in-the-loop' of https://github.com/microsoft/OCR-Form-Tools into yongbing-chen/human-in-the-loop * disable autolabeling button when no predict model * move runAutoLabelingOnCurrentDocument to canvas.tsx * remove assetMetadata if no labels find after auto-labeling * fix tslint check * object deep clone Co-authored-by: alex-krasn <64093224+alex-krasn@users.noreply.github.com>
This commit is contained in:
Родитель
b92b73bb80
Коммит
be9d564815
|
@ -37,3 +37,4 @@ secrets.sh
|
||||||
# complexity reports
|
# complexity reports
|
||||||
es6-src/
|
es6-src/
|
||||||
report/
|
report/
|
||||||
|
debug.log
|
||||||
|
|
|
@ -202,6 +202,14 @@ export const english: IAppStrings = {
|
||||||
downloadScript: "Analyze with python script",
|
downloadScript: "Analyze with python script",
|
||||||
defaultLocalFileInput: "Browse for a file...",
|
defaultLocalFileInput: "Browse for a file...",
|
||||||
defaultURLInput: "Paste or type URL...",
|
defaultURLInput: "Paste or type URL...",
|
||||||
|
editAndUploadToTrainingSet: "Edit & upload to training set",
|
||||||
|
editAndUploadToTrainingSetNotify: "by clicking on this button, this form will be added to this project, where you can edit these labels.",
|
||||||
|
editAndUploadToTrainingSetNotify2: "We are adding this file to your training set, where you could edit the labels and re-train the model.",
|
||||||
|
uploadInPrgoress: "Upload in progress...",
|
||||||
|
confirmDuplicatedAssetName: {
|
||||||
|
title: "Asset name exists",
|
||||||
|
message: "Asset with name '${name}' exists in project, override?"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
recentModelsView: {
|
recentModelsView: {
|
||||||
header: "Select a model to analyze with",
|
header: "Select a model to analyze with",
|
||||||
|
@ -434,6 +442,8 @@ export const english: IAppStrings = {
|
||||||
subIMenuItems: {
|
subIMenuItems: {
|
||||||
runOcrOnCurrentDocument: "Run OCR on current document",
|
runOcrOnCurrentDocument: "Run OCR on current document",
|
||||||
runOcrOnAllDocuments: "Run OCR on all documents",
|
runOcrOnAllDocuments: "Run OCR on all documents",
|
||||||
|
runAutoLabelingCurrentDocument: "Run AutoLabeling on current document",
|
||||||
|
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,6 +201,14 @@ export const spanish: IAppStrings = {
|
||||||
downloadScript: "Analizar con script python",
|
downloadScript: "Analizar con script python",
|
||||||
defaultLocalFileInput: "Busca un archivo...",
|
defaultLocalFileInput: "Busca un archivo...",
|
||||||
defaultURLInput: "Pegar o escribir URL...",
|
defaultURLInput: "Pegar o escribir URL...",
|
||||||
|
editAndUploadToTrainingSet: "Editar y cargar al conjunto de entrenamiento",
|
||||||
|
editAndUploadToTrainingSetNotify: "Al hacer clic en este botón, este formulario se agregará al Blob de Azure Storage para este proyecto, donde puede editar estas etiquetas.",
|
||||||
|
editAndUploadToTrainingSetNotify2: "Estamos agregando este archivo a su conjunto de entrenamiento, donde puede editar las etiquetas y volver a entrenar el modelo.",
|
||||||
|
uploadInPrgoress: "carga en curso...",
|
||||||
|
confirmDuplicatedAssetName: {
|
||||||
|
title: "El nombre del activo existe",
|
||||||
|
message: "El activo con el nombre '${name}' existe en el proyecto, ¿anularlo?"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
recentModelsView: {
|
recentModelsView: {
|
||||||
header: "Seleccionar modelo para analizar con",
|
header: "Seleccionar modelo para analizar con",
|
||||||
|
@ -435,6 +443,8 @@ export const spanish: IAppStrings = {
|
||||||
subIMenuItems: {
|
subIMenuItems: {
|
||||||
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
||||||
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
||||||
|
runAutoLabelingCurrentDocument: "Ejecutar AutoLabeling en el documento actual",
|
||||||
|
noPredictModelOnProject: "Predecir modelo no disponible, entrene el modelo primero.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -475,6 +475,7 @@ export default class MockFactory {
|
||||||
saveProject: jest.fn(() => Promise.resolve()),
|
saveProject: jest.fn(() => Promise.resolve()),
|
||||||
deleteProject: jest.fn(() => Promise.resolve()),
|
deleteProject: jest.fn(() => Promise.resolve()),
|
||||||
closeProject: jest.fn(() => Promise.resolve()),
|
closeProject: jest.fn(() => Promise.resolve()),
|
||||||
|
addAssetToProject: jest.fn(() => Promise.resolve()),
|
||||||
deleteAsset: jest.fn(() => Promise.resolve()),
|
deleteAsset: jest.fn(() => Promise.resolve()),
|
||||||
loadAssets: jest.fn(() => Promise.resolve()),
|
loadAssets: jest.fn(() => Promise.resolve()),
|
||||||
loadAssetMetadata: jest.fn(() => Promise.resolve()),
|
loadAssetMetadata: jest.fn(() => Promise.resolve()),
|
||||||
|
|
|
@ -200,6 +200,14 @@ export interface IAppStrings {
|
||||||
downloadScript: string,
|
downloadScript: string,
|
||||||
defaultLocalFileInput: string,
|
defaultLocalFileInput: string,
|
||||||
defaultURLInput: string,
|
defaultURLInput: string,
|
||||||
|
editAndUploadToTrainingSet: string,
|
||||||
|
editAndUploadToTrainingSetNotify: string,
|
||||||
|
editAndUploadToTrainingSetNotify2: string,
|
||||||
|
uploadInPrgoress: string,
|
||||||
|
confirmDuplicatedAssetName: {
|
||||||
|
title: string,
|
||||||
|
message: string
|
||||||
|
},
|
||||||
};
|
};
|
||||||
recentModelsView: {
|
recentModelsView: {
|
||||||
header: string;
|
header: string;
|
||||||
|
@ -277,7 +285,7 @@ export interface IAppStrings {
|
||||||
messages: {
|
messages: {
|
||||||
saveSuccess: string,
|
saveSuccess: string,
|
||||||
deleteSuccess: string,
|
deleteSuccess: string,
|
||||||
doNotAllowDuplicateNames:string,
|
doNotAllowDuplicateNames: string,
|
||||||
},
|
},
|
||||||
imageCorsWarning: string,
|
imageCorsWarning: string,
|
||||||
blobCorsWarning: string,
|
blobCorsWarning: string,
|
||||||
|
@ -429,6 +437,8 @@ export interface IAppStrings {
|
||||||
subIMenuItems: {
|
subIMenuItems: {
|
||||||
runOcrOnCurrentDocument: string,
|
runOcrOnCurrentDocument: string,
|
||||||
runOcrOnAllDocuments: string,
|
runOcrOnAllDocuments: string,
|
||||||
|
runAutoLabelingCurrentDocument: string,
|
||||||
|
noPredictModelOnProject: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ export interface IProviderOptions {
|
||||||
export interface IAppSettings {
|
export interface IAppSettings {
|
||||||
securityTokens: ISecurityToken[],
|
securityTokens: ISecurityToken[],
|
||||||
thumbnailSize?: ISize,
|
thumbnailSize?: ISize,
|
||||||
|
hideUploadingOption?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,6 +35,8 @@ import { constants } from "../../../../common/constants";
|
||||||
import { CanvasCommandBar } from "./canvasCommandBar";
|
import { CanvasCommandBar } from "./canvasCommandBar";
|
||||||
import { TooltipHost, ITooltipHostStyles } from "@fluentui/react";
|
import { TooltipHost, ITooltipHostStyles } from "@fluentui/react";
|
||||||
import { IAppSettings } from '../../../../models/applicationState';
|
import { IAppSettings } from '../../../../models/applicationState';
|
||||||
|
import { AutoLabelingStatus, PredictService } from "../../../../services/predictService";
|
||||||
|
import { AssetService } from "../../../../services/assetService";
|
||||||
import { strings } from "../../../../common/strings";
|
import { strings } from "../../../../common/strings";
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +57,7 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
||||||
onSelectedRegionsChanged?: (regions: IRegion[]) => void;
|
onSelectedRegionsChanged?: (regions: IRegion[]) => void;
|
||||||
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
||||||
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
||||||
|
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
||||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||||
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
||||||
onAssetDeleted?: () => void;
|
onAssetDeleted?: () => void;
|
||||||
|
@ -75,6 +78,7 @@ export interface ICanvasState {
|
||||||
errorTitle?: string;
|
errorTitle?: string;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
ocrStatus: OcrStatus;
|
ocrStatus: OcrStatus;
|
||||||
|
autoLableingStatus: AutoLabelingStatus;
|
||||||
layers: any;
|
layers: any;
|
||||||
tableIconTooltip: any;
|
tableIconTooltip: any;
|
||||||
hoveringFeature: string;
|
hoveringFeature: string;
|
||||||
|
@ -134,8 +138,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
isError: false,
|
isError: false,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
ocrStatus: OcrStatus.done,
|
ocrStatus: OcrStatus.done,
|
||||||
layers: {text: true, tables: true, checkboxes: true, label: true, drawnRegions: true},
|
autoLableingStatus: AutoLabelingStatus.none,
|
||||||
tableIconTooltip: { display: "none", width: 0, height: 0, top: 0, left: 0},
|
layers: { text: true, tables: true, checkboxes: true, label: true, drawnRegions: true },
|
||||||
|
tableIconTooltip: { display: "none", width: 0, height: 0, top: 0, left: 0 },
|
||||||
hoveringFeature: null,
|
hoveringFeature: null,
|
||||||
groupSelectMode: false,
|
groupSelectMode: false,
|
||||||
drawRegionMode: false,
|
drawRegionMode: false,
|
||||||
|
@ -188,7 +193,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
pdfFile: null,
|
pdfFile: null,
|
||||||
imageUri: null,
|
imageUri: null,
|
||||||
tiffImages: [],
|
tiffImages: [],
|
||||||
layers: {text: true, tables: true, checkboxes: true, label: true, drawnRegions: true},
|
layers: { text: true, tables: true, checkboxes: true, label: true, drawnRegions: true },
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const asset = this.state.currentAsset.asset;
|
const asset = this.state.currentAsset.asset;
|
||||||
await this.loadImage();
|
await this.loadImage();
|
||||||
|
@ -223,12 +228,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%", height: "100%" }}>
|
<div style={{ width: "100%", height: "100%" }}>
|
||||||
<KeyboardBinding
|
<KeyboardBinding
|
||||||
displayName={"Delete region"}
|
displayName={"Delete region"}
|
||||||
key={"Delete"}
|
key={"Delete"}
|
||||||
keyEventType={KeyEventType.KeyDown}
|
keyEventType={KeyEventType.KeyDown}
|
||||||
accelerators={["Escape", "Alt+Backspace", "Shift", "Delete", "Backspace", "<", ",", ">", ".",
|
accelerators={["Escape", "Alt+Backspace", "Shift", "Delete", "Backspace", "<", ",", ">", ".",
|
||||||
"{", "[", "}", "]", "+", "-", "/", "=", "_", "?"]}
|
"{", "[", "}", "]", "+", "-", "/", "=", "_", "?"]}
|
||||||
handler={this.handleKeyDown}
|
handler={this.handleKeyDown}
|
||||||
/>
|
/>
|
||||||
<KeyboardBinding
|
<KeyboardBinding
|
||||||
displayName={"Label Key Mode"}
|
displayName={"Label Key Mode"}
|
||||||
|
@ -246,9 +251,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
handleRunOcr={this.runOcr}
|
handleRunOcr={this.runOcr}
|
||||||
handleAssetDeleted={this.props.onAssetDeleted}
|
handleAssetDeleted={this.props.onAssetDeleted}
|
||||||
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
||||||
|
handleRunAutoLabelingOnCurrentDocument={this.runAutoLabelingOnCurrentDocument}
|
||||||
connectionType={this.props.project.sourceConnection.providerType}
|
connectionType={this.props.project.sourceConnection.providerType}
|
||||||
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
||||||
drawRegionMode={this.state.drawRegionMode}
|
drawRegionMode={this.state.drawRegionMode}
|
||||||
|
project={this.props.project}
|
||||||
parentPage={strings.editorPage.title}
|
parentPage={strings.editorPage.title}
|
||||||
/>
|
/>
|
||||||
<ImageMap
|
<ImageMap
|
||||||
|
@ -289,7 +296,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
/>
|
/>
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
content={"rows: " + this.state.tableIconTooltip.rows +
|
content={"rows: " + this.state.tableIconTooltip.rows +
|
||||||
" columns: " + this.state.tableIconTooltip.columns}
|
" columns: " + this.state.tableIconTooltip.columns}
|
||||||
id="tableInfo"
|
id="tableInfo"
|
||||||
styles={hostStyles}
|
styles={hostStyles}
|
||||||
>
|
>
|
||||||
|
@ -299,32 +306,40 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
onClick={this.handleTableIconFeatureSelect}
|
onClick={this.handleTableIconFeatureSelect}
|
||||||
/>
|
/>
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
{ this.shouldShowPreviousPageButton() &&
|
{this.shouldShowPreviousPageButton() &&
|
||||||
<IconButton
|
<IconButton
|
||||||
className="toolbar-btn prev"
|
className="toolbar-btn prev"
|
||||||
title="Previous"
|
title="Previous"
|
||||||
iconProps={{iconName: "ChevronLeft"}}
|
iconProps={{ iconName: "ChevronLeft" }}
|
||||||
onClick={this.prevPage}
|
onClick={this.prevPage}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.shouldShowNextPageButton() &&
|
{this.shouldShowNextPageButton() &&
|
||||||
<IconButton
|
<IconButton
|
||||||
className="toolbar-btn next"
|
className="toolbar-btn next"
|
||||||
title="Next"
|
title="Next"
|
||||||
onClick={this.nextPage}
|
onClick={this.nextPage}
|
||||||
iconProps={{iconName: "ChevronRight"}}
|
iconProps={{ iconName: "ChevronRight" }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.shouldShowMultiPageIndicator() &&
|
{this.shouldShowMultiPageIndicator() &&
|
||||||
<p className="page-number">
|
<p className="page-number">
|
||||||
Page {this.state.currentPage} of {this.state.numPages}
|
Page {this.state.currentPage} of {this.state.numPages}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
{ this.state.ocrStatus !== OcrStatus.done &&
|
{this.state.ocrStatus !== OcrStatus.done &&
|
||||||
<div className="canvas-ocr-loading">
|
<div className="canvas-ocr-loading">
|
||||||
<div className="canvas-ocr-loading-spinner">
|
<div className="canvas-ocr-loading-spinner">
|
||||||
<Label className="p-0" ></Label>
|
<Label className="p-0" ></Label>
|
||||||
<Spinner size={SpinnerSize.large} label="Running OCR..." ariaLive="assertive" labelPosition="right"/>
|
<Spinner size={SpinnerSize.large} label="Running OCR..." ariaLive="assertive" labelPosition="right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{this.state.autoLableingStatus === AutoLabelingStatus.running &&
|
||||||
|
<div className="canvas-ocr-loading">
|
||||||
|
<div className="canvas-ocr-loading-spinner">
|
||||||
|
<Label className="p-0" ></Label>
|
||||||
|
<Spinner size={SpinnerSize.large} label="Running Auto Labeling..." ariaLive="assertive" labelPosition="right" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -343,10 +358,28 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private runOcrForAllDocuments = () => {
|
private runOcrForAllDocuments = () => {
|
||||||
this.setState({ocrStatus: OcrStatus.runningOCR})
|
this.setState({ ocrStatus: OcrStatus.runningOCR })
|
||||||
this.props.runOcrForAllDocs(true);
|
this.props.runOcrForAllDocs(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private runAutoLabelingOnCurrentDocument = async () => {
|
||||||
|
try {
|
||||||
|
this.setAutoLabelingStatus(AutoLabelingStatus.running);
|
||||||
|
const asset = this.state.currentAsset.asset;
|
||||||
|
const assetPath = asset.path;
|
||||||
|
const predictService = new PredictService(this.props.project);
|
||||||
|
const result = await predictService.getPrediction(assetPath);
|
||||||
|
|
||||||
|
const assetService = new AssetService(this.props.project);
|
||||||
|
await assetService.uploadAssetPredictResult(asset, result);
|
||||||
|
const assetMetadata = await assetService.getAssetMetadata(asset);
|
||||||
|
await this.props.onAssetMetadataChanged(assetMetadata);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.setAutoLabelingStatus(AutoLabelingStatus.done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public updateSize() {
|
public updateSize() {
|
||||||
this.imageMap.updateSize();
|
this.imageMap.updateSize();
|
||||||
}
|
}
|
||||||
|
@ -405,8 +438,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const newTag = {
|
const newTag = {
|
||||||
...tag,
|
...tag,
|
||||||
documentCount: 1,
|
documentCount: 1,
|
||||||
type : fieldType,
|
type: fieldType,
|
||||||
format : FieldFormat.NotSpecified,
|
format: FieldFormat.NotSpecified,
|
||||||
} as ITag;
|
} as ITag;
|
||||||
this.props.onTagChanged(tag, newTag);
|
this.props.onTagChanged(tag, newTag);
|
||||||
}
|
}
|
||||||
|
@ -572,7 +605,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
this.imageMap.removeAllDrawnLabelFeatures();
|
this.imageMap.removeAllDrawnLabelFeatures();
|
||||||
this.addLabelledDataToLayer(regions.filter(
|
this.addLabelledDataToLayer(regions.filter(
|
||||||
(region) => region.tags[0] !== undefined &&
|
(region) => region.tags[0] !== undefined &&
|
||||||
region.pageNumber === this.state.currentPage));
|
region.pageNumber === this.state.currentPage));
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
currentAsset,
|
currentAsset,
|
||||||
|
@ -695,9 +728,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
state: "rest",
|
state: "rest",
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconTR = [coordinates[0][0] - 5, coordinates[0][1] ];
|
const iconTR = [coordinates[0][0] - 5, coordinates[0][1]];
|
||||||
const iconTL = [iconTR[0] - 31.5, iconTR[1]];
|
const iconTL = [iconTR[0] - 31.5, iconTR[1]];
|
||||||
const iconBL = [iconTR[0] , iconTR[1] - 29.5];
|
const iconBL = [iconTR[0], iconTR[1] - 29.5];
|
||||||
const iconBR = [iconTR[0] - 31.5, iconTR[1] - 29.5];
|
const iconBR = [iconTR[0] - 31.5, iconTR[1] - 29.5];
|
||||||
|
|
||||||
tableFeatures["iconBorder"] = new Feature({
|
tableFeatures["iconBorder"] = new Feature({
|
||||||
|
@ -883,7 +916,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
image: new Icon({
|
image: new Icon({
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
scale: this.imageMap && this.imageMap.getResolutionForZoom(3) ?
|
scale: this.imageMap && this.imageMap.getResolutionForZoom(3) ?
|
||||||
this.imageMap.getResolutionForZoom(3) / resolution : 1,
|
this.imageMap.getResolutionForZoom(3) / resolution : 1,
|
||||||
anchor: [.95, 0.15],
|
anchor: [.95, 0.15],
|
||||||
anchorXUnits: "fraction",
|
anchorXUnits: "fraction",
|
||||||
anchorYUnits: "fraction",
|
anchorYUnits: "fraction",
|
||||||
|
@ -895,7 +928,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
image: new Icon({
|
image: new Icon({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
scale: this.imageMap && this.imageMap.getResolutionForZoom(3) ?
|
scale: this.imageMap && this.imageMap.getResolutionForZoom(3) ?
|
||||||
this.imageMap.getResolutionForZoom(3) / resolution : 1,
|
this.imageMap.getResolutionForZoom(3) / resolution : 1,
|
||||||
anchor: [.95, 0.15],
|
anchor: [.95, 0.15],
|
||||||
anchorXUnits: "fraction",
|
anchorXUnits: "fraction",
|
||||||
anchorYUnits: "fraction",
|
anchorYUnits: "fraction",
|
||||||
|
@ -977,8 +1010,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
|
|
||||||
if (category === FeatureCategory.DrawnRegion ||
|
if (category === FeatureCategory.DrawnRegion ||
|
||||||
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
||||||
.find((r) => r.id === regionId).category === FeatureCategory.DrawnRegion))
|
.find((r) => r.id === regionId).category === FeatureCategory.DrawnRegion)) {
|
||||||
{
|
|
||||||
selectedRegions.forEach((region) => {
|
selectedRegions.forEach((region) => {
|
||||||
if (region?.category !== FeatureCategory.DrawnRegion) {
|
if (region?.category !== FeatureCategory.DrawnRegion) {
|
||||||
this.removeFromSelectedRegions(region.id)
|
this.removeFromSelectedRegions(region.id)
|
||||||
|
@ -987,14 +1019,14 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
else if (category === FeatureCategory.Checkbox ||
|
else if (category === FeatureCategory.Checkbox ||
|
||||||
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
||||||
.find((r) => r.id === regionId).category === FeatureCategory.Checkbox)) {
|
.find((r) => r.id === regionId).category === FeatureCategory.Checkbox)) {
|
||||||
selectedRegions.forEach((region) => this.removeFromSelectedRegions(region.id));
|
selectedRegions.forEach((region) => this.removeFromSelectedRegions(region.id));
|
||||||
} else if (category === FeatureCategory.Text ||
|
} else if (category === FeatureCategory.Text ||
|
||||||
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
(category === FeatureCategory.Label && this.state.currentAsset.regions
|
||||||
.find((r) => r.id === regionId).category === FeatureCategory.Text)) {
|
.find((r) => r.id === regionId).category === FeatureCategory.Text)) {
|
||||||
selectedRegions.filter((region) => region.category === FeatureCategory.Checkbox ||
|
selectedRegions.filter((region) => region.category === FeatureCategory.Checkbox ||
|
||||||
region.category === FeatureCategory.DrawnRegion)
|
region.category === FeatureCategory.DrawnRegion)
|
||||||
.forEach((region) => this.removeFromSelectedRegions(region.id));
|
.forEach((region) => this.removeFromSelectedRegions(region.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1014,7 +1046,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const iRegionId = this.getIndexOfSelectedRegionIndex(regionId);
|
const iRegionId = this.getIndexOfSelectedRegionIndex(regionId);
|
||||||
if (iRegionId >= 0) {
|
if (iRegionId >= 0) {
|
||||||
const region = this.getSelectedRegions().find((r) => r.id === regionId);
|
const region = this.getSelectedRegions().find((r) => r.id === regionId);
|
||||||
if (region && region.tags && region.tags.length === 0 ) {
|
if (region && region.tags && region.tags.length === 0) {
|
||||||
this.onRegionDelete(regionId);
|
this.onRegionDelete(regionId);
|
||||||
}
|
}
|
||||||
this.selectedRegionIds.splice(iRegionId, 1);
|
this.selectedRegionIds.splice(iRegionId, 1);
|
||||||
|
@ -1025,9 +1057,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private addToSelectedRegions = (regionId: string,
|
private addToSelectedRegions = (regionId: string,
|
||||||
text: string,
|
text: string,
|
||||||
polygon: number[],
|
polygon: number[],
|
||||||
regionCategory: FeatureCategory) => {
|
regionCategory: FeatureCategory) => {
|
||||||
let selectedRegion;
|
let selectedRegion;
|
||||||
if (this.isRegionSelected(regionId)) {
|
if (this.isRegionSelected(regionId)) {
|
||||||
// skip if it's already existed in selected regions
|
// skip if it's already existed in selected regions
|
||||||
|
@ -1041,7 +1073,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
if (this.selectedRegionIds.includes(regionId)) {
|
if (this.selectedRegionIds.includes(regionId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const regionBoundingBox = this.convertToRegionBoundingBox(polygon);
|
const regionBoundingBox = this.convertToRegionBoundingBox(polygon);
|
||||||
const regionPoints = this.convertToRegionPoints(polygon);
|
const regionPoints = this.convertToRegionPoints(polygon);
|
||||||
selectedRegion = {
|
selectedRegion = {
|
||||||
|
@ -1105,6 +1137,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
private setAutoLabelingStatus = (autoLableingStatus: AutoLabelingStatus) => {
|
||||||
|
this.setState({ autoLableingStatus }, () => {
|
||||||
|
if (this.props.onRunningAutoLabelingStatusChanged) {
|
||||||
|
this.props.onRunningAutoLabelingStatusChanged(autoLableingStatus === AutoLabelingStatus.running);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private runOcr = () => {
|
private runOcr = () => {
|
||||||
this.loadOcr(true);
|
this.loadOcr(true);
|
||||||
|
@ -1161,7 +1200,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
|
|
||||||
private loadPdfFile = async (assetId, url) => {
|
private loadPdfFile = async (assetId, url) => {
|
||||||
try {
|
try {
|
||||||
const pdf = await pdfjsLib.getDocument({url, cMapUrl, cMapPacked: true}).promise;
|
const pdf = await pdfjsLib.getDocument({ url, cMapUrl, cMapPacked: true }).promise;
|
||||||
// Fetch current page
|
// Fetch current page
|
||||||
if (assetId === this.state.currentAsset.asset.id) {
|
if (assetId === this.state.currentAsset.asset.id) {
|
||||||
await this.loadPdfPage(assetId, pdf, this.state.currentPage);
|
await this.loadPdfPage(assetId, pdf, this.state.currentPage);
|
||||||
|
@ -1247,7 +1286,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
private convertLabelDataToRegions = (labelData: ILabelData): IRegion[] => {
|
private convertLabelDataToRegions = (labelData: ILabelData): IRegion[] => {
|
||||||
const regions = [];
|
const regions = [];
|
||||||
|
|
||||||
if (labelData.labels) {
|
if (labelData && labelData.labels) {
|
||||||
labelData.labels.forEach((label) => {
|
labelData.labels.forEach((label) => {
|
||||||
if (label.value) {
|
if (label.value) {
|
||||||
label.value.forEach((formRegion) => {
|
label.value.forEach((formRegion) => {
|
||||||
|
@ -1339,13 +1378,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const top = Math.min(...yAxisValues);
|
const top = Math.min(...yAxisValues);
|
||||||
const right = Math.max(...xAxisValues);
|
const right = Math.max(...xAxisValues);
|
||||||
const bottom = Math.max(...yAxisValues);
|
const bottom = Math.max(...yAxisValues);
|
||||||
return([left, top, right, top, right, bottom, left, bottom]);
|
return ([left, top, right, top, right, bottom, left, bottom]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertToRegionPoints = (polygon: number[]) => {
|
private convertToRegionPoints = (polygon: number[]) => {
|
||||||
const points = [];
|
const points = [];
|
||||||
for (let i = 0; i < polygon.length; i += 2) {
|
for (let i = 0; i < polygon.length; i += 2) {
|
||||||
points.push({x: polygon[i], y: polygon[i + 1]});
|
points.push({ x: polygon[i], y: polygon[i + 1] });
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
@ -1555,7 +1594,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
|
|
||||||
private getBoundingBoxTextFromRegion = (formRegion: IFormRegion, boundingBoxIndex: number) => {
|
private getBoundingBoxTextFromRegion = (formRegion: IFormRegion, boundingBoxIndex: number) => {
|
||||||
// get value from formRegion.text
|
// get value from formRegion.text
|
||||||
const regionValues = formRegion.text.split(" ");
|
const regionValues = formRegion.text && formRegion.text.split(" ");
|
||||||
if (regionValues && regionValues.length > boundingBoxIndex) {
|
if (regionValues && regionValues.length > boundingBoxIndex) {
|
||||||
return regionValues[boundingBoxIndex];
|
return regionValues[boundingBoxIndex];
|
||||||
}
|
}
|
||||||
|
@ -1675,7 +1714,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
// 2. Avoid rebuilding order index when users switch back and forth between pages.
|
// 2. Avoid rebuilding order index when users switch back and forth between pages.
|
||||||
const ocrs = this.state.ocr;
|
const ocrs = this.state.ocr;
|
||||||
const ocrReadResults = (ocrs.recognitionResults || (ocrs.analyzeResult && ocrs.analyzeResult.readResults));
|
const ocrReadResults = (ocrs.recognitionResults || (ocrs.analyzeResult && ocrs.analyzeResult.readResults));
|
||||||
const ocrPageResults = (ocrs.recognitionResults || (ocrs.analyzeResult && ocrs.analyzeResult.pageResults));
|
const ocrPageResults = (ocrs.recognitionResults || (ocrs.analyzeResult && ocrs.analyzeResult.pageResults));
|
||||||
const imageExtent = this.imageMap.getImageExtent();
|
const imageExtent = this.imageMap.getImageExtent();
|
||||||
ocrReadResults.forEach((ocr) => {
|
ocrReadResults.forEach((ocr) => {
|
||||||
const ocrExtent = [0, 0, ocr.width, ocr.height];
|
const ocrExtent = [0, 0, ocr.width, ocr.height];
|
||||||
|
@ -1888,12 +1927,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const newLayers = Object.assign({}, this.state.layers);
|
const newLayers = Object.assign({}, this.state.layers);
|
||||||
newLayers[layer] = !newLayers[layer];
|
newLayers[layer] = !newLayers[layer];
|
||||||
this.setState({
|
this.setState({
|
||||||
layers : newLayers,
|
layers: newLayers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTableToolTipChange = async (display: string, width: number, height: number, top: number,
|
private handleTableToolTipChange = async (display: string, width: number, height: number, top: number,
|
||||||
left: number, rows: number, columns: number, featureID: string) => {
|
left: number, rows: number, columns: number, featureID: string) => {
|
||||||
if (!this.imageMap) {
|
if (!this.imageMap) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1902,23 +1941,23 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
this.imageMap.getTableBorderFeatureByID(featureID).set("state", "hovering");
|
this.imageMap.getTableBorderFeatureByID(featureID).set("state", "hovering");
|
||||||
this.imageMap.getTableIconFeatureByID(featureID).set("state", "hovering");
|
this.imageMap.getTableIconFeatureByID(featureID).set("state", "hovering");
|
||||||
} else if (featureID === null && this.state.hoveringFeature &&
|
} else if (featureID === null && this.state.hoveringFeature &&
|
||||||
this.imageMap.getTableBorderFeatureByID(this.state.hoveringFeature).get("state") !== "selected") {
|
this.imageMap.getTableBorderFeatureByID(this.state.hoveringFeature).get("state") !== "selected") {
|
||||||
this.imageMap.getTableBorderFeatureByID(this.state.hoveringFeature).set("state", "rest");
|
this.imageMap.getTableBorderFeatureByID(this.state.hoveringFeature).set("state", "rest");
|
||||||
this.imageMap.getTableIconFeatureByID(this.state.hoveringFeature).set("state", "rest");
|
this.imageMap.getTableIconFeatureByID(this.state.hoveringFeature).set("state", "rest");
|
||||||
}
|
}
|
||||||
const newTableIconTooltip = {
|
const newTableIconTooltip = {
|
||||||
display,
|
display,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
top,
|
top,
|
||||||
left,
|
left,
|
||||||
rows,
|
rows,
|
||||||
columns,
|
columns,
|
||||||
};
|
};
|
||||||
this.setState({
|
this.setState({
|
||||||
tableIconTooltip : newTableIconTooltip,
|
tableIconTooltip: newTableIconTooltip,
|
||||||
hoveringFeature: featureID,
|
hoveringFeature: featureID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private redrawAllFeatures = () => {
|
private redrawAllFeatures = () => {
|
||||||
|
@ -2028,7 +2067,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
|
|
||||||
private addDrawnRegionFeatureProps = (feature) => {
|
private addDrawnRegionFeatureProps = (feature) => {
|
||||||
const featureCoordinates = feature.getGeometry().getCoordinates()[0];
|
const featureCoordinates = feature.getGeometry().getCoordinates()[0];
|
||||||
const {featureId, boundingBox} = this.getFeatureIDAndBoundingBox(featureCoordinates);
|
const { featureId, boundingBox } = this.getFeatureIDAndBoundingBox(featureCoordinates);
|
||||||
feature.setProperties({
|
feature.setProperties({
|
||||||
id: featureId,
|
id: featureId,
|
||||||
text: "",
|
text: "",
|
||||||
|
@ -2093,7 +2132,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
polygonPoints.push(boundingBox[i + 1] / ocrHeight);
|
polygonPoints.push(boundingBox[i + 1] / ocrHeight);
|
||||||
}
|
}
|
||||||
const featureId = this.createRegionIdFromBoundingBox(polygonPoints, ocrPage);
|
const featureId = this.createRegionIdFromBoundingBox(polygonPoints, ocrPage);
|
||||||
return {featureId, boundingBox}
|
return { featureId, boundingBox }
|
||||||
}
|
}
|
||||||
|
|
||||||
private modifySelectedRegion = (existingRegionId, newRegionId) => {
|
private modifySelectedRegion = (existingRegionId, newRegionId) => {
|
||||||
|
@ -2124,7 +2163,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const originalFeatureId = feature.getId();
|
const originalFeatureId = feature.getId();
|
||||||
const featureCoordinates = feature.getGeometry().getCoordinates()[0];
|
const featureCoordinates = feature.getGeometry().getCoordinates()[0];
|
||||||
if (this.imageMap.modifyStartFeatureCoordinates[originalFeatureId] !== featureCoordinates.join(",")) {
|
if (this.imageMap.modifyStartFeatureCoordinates[originalFeatureId] !== featureCoordinates.join(",")) {
|
||||||
const {featureId, boundingBox} = this.getFeatureIDAndBoundingBox(featureCoordinates);
|
const { featureId, boundingBox } = this.getFeatureIDAndBoundingBox(featureCoordinates);
|
||||||
feature.setProperties({
|
feature.setProperties({
|
||||||
id: featureId,
|
id: featureId,
|
||||||
boundingbox: boundingBox,
|
boundingbox: boundingBox,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.ms-ContextualMenu-link.is-disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
|
@ -4,10 +4,14 @@ import { ICustomizations, Customizer } from "@fluentui/react/lib/Utilities";
|
||||||
import { getDarkGreyTheme } from "../../../../common/themes";
|
import { getDarkGreyTheme } from "../../../../common/themes";
|
||||||
import { strings } from '../../../../common/strings';
|
import { strings } from '../../../../common/strings';
|
||||||
import { ContextualMenuItemType } from "@fluentui/react";
|
import { ContextualMenuItemType } from "@fluentui/react";
|
||||||
|
import { IProject } from "../../../../models/applicationState";
|
||||||
|
import "./canvasCommandBar.scss";
|
||||||
|
|
||||||
interface ICanvasCommandBarProps {
|
interface ICanvasCommandBarProps {
|
||||||
handleZoomIn: () => void;
|
handleZoomIn: () => void;
|
||||||
handleZoomOut: () => void;
|
handleZoomOut: () => void;
|
||||||
|
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
||||||
|
project: IProject;
|
||||||
handleRotateImage: (degrees: number) => void;
|
handleRotateImage: (degrees: number) => void;
|
||||||
handleRunOcr?: () => void;
|
handleRunOcr?: () => void;
|
||||||
handleRunOcrForAllDocuments?: () => void;
|
handleRunOcrForAllDocuments?: () => void;
|
||||||
|
@ -60,16 +64,16 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
isChecked: props.layers["checkboxes"],
|
isChecked: props.layers["checkboxes"],
|
||||||
onClick: () => props.handleLayerChange("checkboxes"),
|
onClick: () => props.handleLayerChange("checkboxes"),
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// key: "DrawnRegions",
|
// key: "DrawnRegions",
|
||||||
// text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
// text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
||||||
// canCheck: true,
|
// canCheck: true,
|
||||||
// iconProps: { iconName: "AddField" },
|
// iconProps: { iconName: "AddField" },
|
||||||
// isChecked: props.layers["drawnRegions"],
|
// isChecked: props.layers["drawnRegions"],
|
||||||
// className: props.drawRegionMode ? "disabled" : "",
|
// className: props.drawRegionMode ? "disabled" : "",
|
||||||
// onClick: () => props.handleLayerChange("drawnRegions"),
|
// onClick: () => props.handleLayerChange("drawnRegions"),
|
||||||
// disabled: props.drawRegionMode
|
// disabled: props.drawRegionMode
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
key: "Label",
|
key: "Label",
|
||||||
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels,
|
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels,
|
||||||
|
@ -159,6 +163,17 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
iconProps: { iconName: "Documentation" },
|
iconProps: { iconName: "Documentation" },
|
||||||
onClick: () => props.handleRunOcrForAllDocuments(),
|
onClick: () => props.handleRunOcrForAllDocuments(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "runAutoLabelingCurrentDocument",
|
||||||
|
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingCurrentDocument,
|
||||||
|
iconProps: { iconName: "Tag" },
|
||||||
|
disabled: !props.project.predictModelId,
|
||||||
|
title: props.project.predictModelId ? "" :
|
||||||
|
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||||
|
onClick: () => {
|
||||||
|
props.handleRunAutoLabelingOnCurrentDocument();
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'divider_1',
|
key: 'divider_1',
|
||||||
itemType: ContextualMenuItemType.Divider,
|
itemType: ContextualMenuItemType.Divider,
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||||
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
||||||
import {AssetPreview, ContentSource} from "../../common/assetPreview/assetPreview";
|
import { AssetPreview, ContentSource } from "../../common/assetPreview/assetPreview";
|
||||||
import { KeyboardBinding } from "../../common/keyboardBinding/keyboardBinding";
|
import { KeyboardBinding } from "../../common/keyboardBinding/keyboardBinding";
|
||||||
import { KeyEventType } from "../../common/keyboardManager/keyboardManager";
|
import { KeyEventType } from "../../common/keyboardManager/keyboardManager";
|
||||||
import { TagInput } from "../../common/tagInput/tagInput";
|
import { TagInput } from "../../common/tagInput/tagInput";
|
||||||
|
@ -37,6 +37,8 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||||
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
|
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
|
||||||
import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
import { PredictService } from "../../../../services/predictService";
|
||||||
|
import { AssetService } from "../../../../services/assetService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties for Editor Page
|
* Properties for Editor Page
|
||||||
|
@ -87,6 +89,7 @@ export interface IEditorPageState {
|
||||||
isRunningOCRs?: boolean;
|
isRunningOCRs?: boolean;
|
||||||
/** Whether OCR is running in the main canvas */
|
/** Whether OCR is running in the main canvas */
|
||||||
isCanvasRunningOCR?: boolean;
|
isCanvasRunningOCR?: boolean;
|
||||||
|
isCanvasRunningAutoLabeling?: boolean;
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
errorTitle?: string;
|
errorTitle?: string;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
@ -178,7 +181,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { project } = this.props;
|
const { project } = this.props;
|
||||||
const { assets, selectedAsset, isRunningOCRs, isCanvasRunningOCR } = this.state;
|
const { assets, selectedAsset, isRunningOCRs, isCanvasRunningOCR, isCanvasRunningAutoLabeling } = this.state;
|
||||||
|
|
||||||
const labels = (selectedAsset &&
|
const labels = (selectedAsset &&
|
||||||
selectedAsset.labelData &&
|
selectedAsset.labelData &&
|
||||||
|
@ -222,7 +225,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
className="editor-page-sidebar-run-ocr"
|
className="editor-page-sidebar-run-ocr"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => this.loadOcrForNotVisited()}
|
onClick={() => this.loadOcrForNotVisited()}
|
||||||
disabled={this.state.isRunningOCRs}>
|
disabled={this.isBusy()}>
|
||||||
{this.state.isRunningOCRs ?
|
{this.state.isRunningOCRs ?
|
||||||
<div>
|
<div>
|
||||||
<Spinner
|
<Spinner
|
||||||
|
@ -245,16 +248,16 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="editor-page-content" onClick={this.onPageClick}>
|
<div className="editor-page-content" onClick={this.onPageClick}>
|
||||||
<SplitPane split = "vertical"
|
<SplitPane split="vertical"
|
||||||
primary = "second"
|
primary="second"
|
||||||
maxSize = {625}
|
maxSize={625}
|
||||||
minSize = {290}
|
minSize={290}
|
||||||
pane1Style = {{height: "100%"}}
|
pane1Style={{ height: "100%" }}
|
||||||
pane2Style = {{height: "auto"}}
|
pane2Style={{ height: "auto" }}
|
||||||
resizerStyle = {{width: "5px", margin: "0px", border: "2px", background: "transparent"}}
|
resizerStyle={{ width: "5px", margin: "0px", border: "2px", background: "transparent" }}
|
||||||
onChange = {() => this.resizeCanvas()}>
|
onChange={() => this.resizeCanvas()}>
|
||||||
<div className="editor-page-content-main" >
|
<div className="editor-page-content-main" >
|
||||||
<div className="editor-page-content-main-body" onClick = {this.onPageContainerClick}>
|
<div className="editor-page-content-main-body" onClick={this.onPageContainerClick}>
|
||||||
{selectedAsset &&
|
{selectedAsset &&
|
||||||
<Canvas
|
<Canvas
|
||||||
ref={this.canvas}
|
ref={this.canvas}
|
||||||
|
@ -263,6 +266,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
onCanvasRendered={this.onCanvasRendered}
|
onCanvasRendered={this.onCanvasRendered}
|
||||||
onSelectedRegionsChanged={this.onSelectedRegionsChanged}
|
onSelectedRegionsChanged={this.onSelectedRegionsChanged}
|
||||||
onRunningOCRStatusChanged={this.onCanvasRunningOCRStatusChanged}
|
onRunningOCRStatusChanged={this.onCanvasRunningOCRStatusChanged}
|
||||||
|
onRunningAutoLabelingStatusChanged={this.onCanvasRunningAutoLabelingStatusChanged}
|
||||||
onTagChanged={this.onTagChanged}
|
onTagChanged={this.onTagChanged}
|
||||||
onAssetDeleted={this.confirmDocumentDeleted}
|
onAssetDeleted={this.confirmDocumentDeleted}
|
||||||
editorMode={this.state.editorMode}
|
editorMode={this.state.editorMode}
|
||||||
|
@ -273,7 +277,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
closeTableView={this.closeTableView}
|
closeTableView={this.closeTableView}
|
||||||
runOcrForAllDocs={this.loadOcrForNotVisited}
|
runOcrForAllDocs={this.loadOcrForNotVisited}
|
||||||
appSettings={this.props.appSettings}
|
appSettings={this.props.appSettings}
|
||||||
>
|
>
|
||||||
<AssetPreview
|
<AssetPreview
|
||||||
controlsEnabled={this.state.isValid}
|
controlsEnabled={this.state.isValid}
|
||||||
onBeforeAssetChanged={this.onBeforeAssetSelected}
|
onBeforeAssetChanged={this.onBeforeAssetSelected}
|
||||||
|
@ -298,7 +302,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
onLabelEnter={this.onLabelEnter}
|
onLabelEnter={this.onLabelEnter}
|
||||||
onLabelLeave={this.onLabelLeave}
|
onLabelLeave={this.onLabelLeave}
|
||||||
onTagChanged={this.onTagChanged}
|
onTagChanged={this.onTagChanged}
|
||||||
ref = {this.tagInputRef}
|
ref={this.tagInputRef}
|
||||||
/>
|
/>
|
||||||
<Confirm
|
<Confirm
|
||||||
title={strings.editorPage.tags.rename.title}
|
title={strings.editorPage.tags.rename.title}
|
||||||
|
@ -320,14 +324,14 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
title={strings.editorPage.asset.delete.title}
|
title={strings.editorPage.asset.delete.title}
|
||||||
ref={this.deleteDocumentConfirm}
|
ref={this.deleteDocumentConfirm}
|
||||||
message={
|
message={
|
||||||
strings.editorPage.asset.delete.confirmation +
|
strings.editorPage.asset.delete.confirmation +
|
||||||
"\"" + this.state.selectedAsset.asset.name + "\"?"
|
"\"" + this.state.selectedAsset.asset.name + "\"?"
|
||||||
}
|
}
|
||||||
confirmButtonTheme={getPrimaryRedTheme()}
|
confirmButtonTheme={getPrimaryRedTheme()}
|
||||||
onConfirm={this.onAssetDeleted}
|
onConfirm={this.onAssetDeleted}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
</div>
|
</div>
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
@ -352,6 +356,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
when={isRunningOCRs || isCanvasRunningOCR}
|
when={isRunningOCRs || isCanvasRunningOCR}
|
||||||
message={"An OCR operation is currently in progress, are you sure you want to leave?"}
|
message={"An OCR operation is currently in progress, are you sure you want to leave?"}
|
||||||
/>
|
/>
|
||||||
|
<PreventLeaving
|
||||||
|
when={isCanvasRunningAutoLabeling}
|
||||||
|
message={"An AutoLabeling option is currently in progress, are you sure you want to leave?"} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -498,11 +505,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
|
|
||||||
if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||||
if (isTagLabelTypeDrawnRegion) {
|
if (isTagLabelTypeDrawnRegion) {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: category}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: category }));
|
||||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Checkbox}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox }));
|
||||||
} else {
|
} else {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCatagory: FeatureCategory.Text}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Text }));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
||||||
|
@ -513,7 +520,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
}
|
}
|
||||||
this.onTagClicked(tag);
|
this.onTagClicked(tag);
|
||||||
} else {
|
} else {
|
||||||
toast.warn(strings.tags.warnings.notCompatibleTagType, {autoClose: 7000});
|
toast.warn(strings.tags.warnings.notCompatibleTagType, { autoClose: 7000 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// do nothing if region was not selected
|
// do nothing if region was not selected
|
||||||
|
@ -554,7 +561,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
}
|
}
|
||||||
await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
||||||
if (this.props.project.lastVisitedAssetId === assetMetadata.asset.id) {
|
if (this.props.project.lastVisitedAssetId === assetMetadata.asset.id) {
|
||||||
this.setState({selectedAsset: assetMetadata});
|
this.setState({ selectedAsset: assetMetadata });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,10 +589,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
const assetIndex = assets.findIndex((item) => item.id === asset.id);
|
const assetIndex = assets.findIndex((item) => item.id === asset.id);
|
||||||
if (assetIndex > -1) {
|
if (assetIndex > -1) {
|
||||||
const assets = [...this.state.assets];
|
const assets = [...this.state.assets];
|
||||||
const item = {...assets[assetIndex]};
|
const item = { ...assets[assetIndex] };
|
||||||
item.cachedImage = (contentSource as HTMLImageElement).src;
|
item.cachedImage = (contentSource as HTMLImageElement).src;
|
||||||
assets[assetIndex] = item;
|
assets[assetIndex] = item;
|
||||||
this.setState({assets});
|
this.setState({ assets });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,6 +638,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
this.setState({ showInvalidRegionWarning: true });
|
this.setState({ showInvalidRegionWarning: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.state.isCanvasRunningAutoLabeling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
||||||
|
|
||||||
|
@ -687,9 +697,12 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
throw Error(error);
|
throw Error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private isBusy = (): boolean => {
|
||||||
|
return this.state.isRunningOCRs || this.state.isCanvasRunningOCR || this.state.isCanvasRunningAutoLabeling;
|
||||||
|
}
|
||||||
|
|
||||||
public loadOcrForNotVisited = async (runForAll?: boolean) => {
|
public loadOcrForNotVisited = async (runForAll?: boolean) => {
|
||||||
if (this.state.isRunningOCRs) {
|
if (this.isBusy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { project } = this.props;
|
const { project } = this.props;
|
||||||
|
@ -774,17 +787,19 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLabelEnter = (label: ILabel) => {
|
private onLabelEnter = (label: ILabel) => {
|
||||||
this.setState({hoveredLabel: label});
|
this.setState({ hoveredLabel: label });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLabelLeave = (label: ILabel) => {
|
private onLabelLeave = (label: ILabel) => {
|
||||||
this.setState({hoveredLabel: null});
|
this.setState({ hoveredLabel: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCanvasRunningOCRStatusChanged = (isCanvasRunningOCR: boolean) => {
|
private onCanvasRunningOCRStatusChanged = (isCanvasRunningOCR: boolean) => {
|
||||||
this.setState({ isCanvasRunningOCR });
|
this.setState({ isCanvasRunningOCR });
|
||||||
}
|
}
|
||||||
|
private onCanvasRunningAutoLabelingStatusChanged = (isCanvasRunningAutoLabeling: boolean) => {
|
||||||
|
this.setState({ isCanvasRunningAutoLabeling });
|
||||||
|
}
|
||||||
private onFocused = () => {
|
private onFocused = () => {
|
||||||
this.loadProjectAssets();
|
this.loadProjectAssets();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,46 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
import {
|
||||||
|
DefaultButton, Dropdown, FontIcon, IconButton, IDropdownOption,
|
||||||
|
ISelection, PrimaryButton, Selection,
|
||||||
|
SelectionMode, Separator, Spinner, SpinnerSize, TextField
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import axios from "axios";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { Feature } from "ol";
|
||||||
|
import Polygon from "ol/geom/Polygon";
|
||||||
|
import Fill from "ol/style/Fill";
|
||||||
|
import Stroke from "ol/style/Stroke";
|
||||||
|
import Style from "ol/style/Style";
|
||||||
|
import pdfjsLib from "pdfjs-dist";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { bindActionCreators } from "redux";
|
import { bindActionCreators } from "redux";
|
||||||
|
import url from "url";
|
||||||
|
import { constants } from "../../../../common/constants";
|
||||||
|
import HtmlFileReader from "../../../../common/htmlFileReader";
|
||||||
|
import { interpolate, strings } from "../../../../common/strings";
|
||||||
import {
|
import {
|
||||||
FontIcon, Selection, PrimaryButton, Spinner, SpinnerSize, IconButton, TextField, IDropdownOption,
|
getGreenWithWhiteBackgroundTheme, getPrimaryGreenTheme, getPrimaryWhiteTheme,
|
||||||
Dropdown, DefaultButton, Separator, ISelection, SelectionMode
|
getRightPaneDefaultButtonTheme
|
||||||
} from "@fluentui/react";
|
} from "../../../../common/themes";
|
||||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
import { loadImageToCanvas, parseTiffData, renderTiffToCanvas } from "../../../../common/utils";
|
||||||
|
import { AppError, ErrorCode, IApplicationState, IAppSettings, IConnection, ImageMapParent, IProject, IRecentModel } from "../../../../models/applicationState";
|
||||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||||
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
||||||
import "./predictPage.scss";
|
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||||
import {
|
|
||||||
IApplicationState, IConnection, IProject, IAppSettings, AppError, ErrorCode, IRecentModel, ImageMapParent,
|
|
||||||
} from "../../../../models/applicationState";
|
|
||||||
import { ImageMap } from "../../common/imageMap/imageMap";
|
|
||||||
import Style from "ol/style/Style";
|
|
||||||
import Stroke from "ol/style/Stroke";
|
|
||||||
import Fill from "ol/style/Fill";
|
|
||||||
import PredictResult from "./predictResult";
|
|
||||||
import _ from "lodash";
|
|
||||||
import pdfjsLib from "pdfjs-dist";
|
|
||||||
import Alert from "../../common/alert/alert";
|
|
||||||
import url from "url";
|
|
||||||
import HtmlFileReader from "../../../../common/htmlFileReader";
|
|
||||||
import { Feature } from "ol";
|
|
||||||
import Polygon from "ol/geom/Polygon";
|
|
||||||
import { strings, interpolate } from "../../../../common/strings";
|
|
||||||
import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
|
||||||
import ServiceHelper from "../../../../services/serviceHelper";
|
import ServiceHelper from "../../../../services/serviceHelper";
|
||||||
import { parseTiffData, renderTiffToCanvas, loadImageToCanvas } from "../../../../common/utils";
|
|
||||||
import { constants } from "../../../../common/constants";
|
|
||||||
import { getPrimaryGreenTheme, getPrimaryWhiteTheme,
|
|
||||||
getGreenWithWhiteBackgroundTheme,
|
|
||||||
getRightPaneDefaultButtonTheme} from "../../../../common/themes";
|
|
||||||
import axios from "axios";
|
|
||||||
import { IAnalyzeModelInfo } from './predictResult';
|
|
||||||
import RecentModelsView from "./recentModelsView";
|
|
||||||
import { getAppInsights } from '../../../../services/telemetryService';
|
import { getAppInsights } from '../../../../services/telemetryService';
|
||||||
|
import Alert from "../../common/alert/alert";
|
||||||
|
import Confirm from "../../common/confirm/confirm";
|
||||||
|
import { ImageMap } from "../../common/imageMap/imageMap";
|
||||||
|
import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||||
|
import "./predictPage.scss";
|
||||||
|
import PredictResult, { IAnalyzeModelInfo } from "./predictResult";
|
||||||
|
import RecentModelsView from "./recentModelsView";
|
||||||
|
import { UploadToTrainingSetView } from "./uploadToTrainingSetView";
|
||||||
import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
|
import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
|
||||||
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||||
|
@ -83,6 +84,7 @@ export interface IPredictPageState {
|
||||||
highlightedField: string;
|
highlightedField: string;
|
||||||
modelList: IModel[];
|
modelList: IModel[];
|
||||||
modelOption: string;
|
modelOption: string;
|
||||||
|
confirmDuplicatedAssetNameMessage?: string;
|
||||||
imageAngle: number;
|
imageAngle: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +151,8 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
private currPdf: any;
|
private currPdf: any;
|
||||||
private tiffImages: any[];
|
private tiffImages: any[];
|
||||||
private imageMap: ImageMap;
|
private imageMap: ImageMap;
|
||||||
|
private uploadToTrainingSetView: React.RefObject<UploadToTrainingSetView> = React.createRef();
|
||||||
|
private duplicateAssetNameConfirm: React.RefObject<Confirm> = React.createRef();
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
const projectId = this.props.match.params["projectId"];
|
const projectId = this.props.match.params["projectId"];
|
||||||
|
@ -180,7 +184,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
this.state.selectedRecentModelIndex === -1) {
|
this.state.selectedRecentModelIndex === -1) {
|
||||||
this.updateRecentModelsViewer(this.props.project);
|
this.updateRecentModelsViewer(this.props.project);
|
||||||
} else if (this.state.loadingRecentModel) {
|
} else if (this.state.loadingRecentModel) {
|
||||||
this.setState({loadingRecentModel: false});
|
this.setState({ loadingRecentModel: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.file) {
|
if (this.state.file) {
|
||||||
|
@ -288,29 +292,29 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
className="keep-button-80px"
|
className="keep-button-80px"
|
||||||
theme={getRightPaneDefaultButtonTheme()}
|
theme={getRightPaneDefaultButtonTheme()}
|
||||||
text="Change"
|
text="Change"
|
||||||
onClick={() => {this.setState({showRecentModelsView: true})}}
|
onClick={() => { this.setState({ showRecentModelsView: true }) }}
|
||||||
disabled={!mostRecentModel || browseFileDisabled}
|
disabled={!mostRecentModel || browseFileDisabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3" style={{marginTop: "8px"}}>
|
<div className="p-3" style={{ marginTop: "8px" }}>
|
||||||
<div style={{display: "flex", justifyContent: "space-between"}}>
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<h5>
|
<h5>
|
||||||
{strings.predict.downloadScript}
|
{strings.predict.downloadScript}
|
||||||
</h5>
|
</h5>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="keep-button-80px"
|
className="keep-button-80px"
|
||||||
theme={getPrimaryGreenTheme()}
|
theme={getPrimaryGreenTheme()}
|
||||||
text="Download"
|
text="Download"
|
||||||
allowDisabledFocus
|
allowDisabledFocus
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onClick={this.handleDownloadClick}
|
onClick={this.handleDownloadClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="separator-right-pane-main">or</Separator>
|
<Separator className="separator-right-pane-main">or</Separator>
|
||||||
<h5>
|
<h5>
|
||||||
{strings.predict.uploadFile}
|
{strings.predict.uploadFile}
|
||||||
</h5>
|
</h5>
|
||||||
<div style={{marginBottom: "3px"}}>Image source</div>
|
<div style={{ marginBottom: "3px" }}>Image source</div>
|
||||||
<div className="container-space-between">
|
<div className="container-space-between">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
className="sourceDropdown"
|
className="sourceDropdown"
|
||||||
|
@ -319,7 +323,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
disabled={this.state.isPredicting || this.state.isFetching}
|
disabled={this.state.isPredicting || this.state.isFetching}
|
||||||
onChange={this.selectSource}
|
onChange={this.selectSource}
|
||||||
/>
|
/>
|
||||||
{ this.state.sourceOption === "localFile" &&
|
{this.state.sourceOption === "localFile" &&
|
||||||
<input
|
<input
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
type="file"
|
type="file"
|
||||||
|
@ -330,11 +334,11 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
disabled={browseFileDisabled}
|
disabled={browseFileDisabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.state.sourceOption === "localFile" &&
|
{this.state.sourceOption === "localFile" &&
|
||||||
<TextField
|
<TextField
|
||||||
className="mr-2 ml-2"
|
className="mr-2 ml-2"
|
||||||
theme={getGreenWithWhiteBackgroundTheme()}
|
theme={getGreenWithWhiteBackgroundTheme()}
|
||||||
style={{cursor: (browseFileDisabled ? "default" : "pointer")}}
|
style={{ cursor: (browseFileDisabled ? "default" : "pointer") }}
|
||||||
onClick={this.handleDummyInputClick}
|
onClick={this.handleDummyInputClick}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
aria-label={strings.predict.uploadFile}
|
aria-label={strings.predict.uploadFile}
|
||||||
|
@ -342,7 +346,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
disabled={browseFileDisabled}
|
disabled={browseFileDisabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.state.sourceOption === "localFile" &&
|
{this.state.sourceOption === "localFile" &&
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="keep-button-80px"
|
className="keep-button-80px"
|
||||||
theme={getPrimaryGreenTheme()}
|
theme={getPrimaryGreenTheme()}
|
||||||
|
@ -353,7 +357,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
onClick={this.handleDummyInputClick}
|
onClick={this.handleDummyInputClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.state.sourceOption === "url" &&
|
{this.state.sourceOption === "url" &&
|
||||||
<TextField
|
<TextField
|
||||||
className="mr-2 ml-2"
|
className="mr-2 ml-2"
|
||||||
theme={getGreenWithWhiteBackgroundTheme()}
|
theme={getGreenWithWhiteBackgroundTheme()}
|
||||||
|
@ -364,7 +368,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
disabled={urlInputDisabled}
|
disabled={urlInputDisabled}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{ this.state.sourceOption === "url" &&
|
{this.state.sourceOption === "url" &&
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
theme={getPrimaryGreenTheme()}
|
theme={getPrimaryGreenTheme()}
|
||||||
className="keep-button-80px"
|
className="keep-button-80px"
|
||||||
|
@ -377,15 +381,15 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="container-items-end predict-button">
|
<div className="container-items-end predict-button">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
theme={getPrimaryWhiteTheme()}
|
theme={getPrimaryWhiteTheme()}
|
||||||
iconProps={{ iconName: "Insights" }}
|
iconProps={{ iconName: "Insights" }}
|
||||||
text="Run analysis"
|
text="Run analysis"
|
||||||
aria-label={!this.state.predictionLoaded ? strings.predict.inProgress : ""}
|
aria-label={!this.state.predictionLoaded ? strings.predict.inProgress : ""}
|
||||||
allowDisabledFocus
|
allowDisabledFocus
|
||||||
disabled={predictDisabled}
|
disabled={predictDisabled}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isFetching &&
|
{this.state.isFetching &&
|
||||||
<div className="loading-container">
|
<div className="loading-container">
|
||||||
|
@ -415,21 +419,33 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
page={this.state.currPage}
|
page={this.state.currPage}
|
||||||
tags={this.props.project.tags}
|
tags={this.props.project.tags}
|
||||||
downloadResultLabel={this.state.fileLabel}
|
downloadResultLabel={this.state.fileLabel}
|
||||||
|
onAddAssetToProject={this.onAddAssetToProjectClick}
|
||||||
onPredictionClick={this.onPredictionClick}
|
onPredictionClick={this.onPredictionClick}
|
||||||
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
||||||
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
<UploadToTrainingSetView
|
||||||
|
showOption={!this.props.appSettings.hideUploadingOption}
|
||||||
|
ref={this.uploadToTrainingSetView}
|
||||||
|
onConfirm={this.onAddAssetToProject} />
|
||||||
{
|
{
|
||||||
(Object.keys(predictions).length === 0 && this.state.predictRun) &&
|
(Object.keys(predictions).length === 0 && this.state.predictRun) &&
|
||||||
<div>
|
<div>
|
||||||
No field can be extracted.
|
No field can be extracted.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<Confirm
|
||||||
|
ref={this.duplicateAssetNameConfirm}
|
||||||
|
title={strings.predict.confirmDuplicatedAssetName.title}
|
||||||
|
message={this.state.confirmDuplicatedAssetNameMessage}
|
||||||
|
onConfirm={this.onAddAssetToProjectConfirm}
|
||||||
|
confirmButtonTheme={getPrimaryGreenTheme()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</> : <Spinner className="loading-tag" size={SpinnerSize.large}/>
|
</> : <Spinner className="loading-tag" size={SpinnerSize.large} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -467,76 +483,76 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
|
|
||||||
private removeDefaultInputedFileURL = () => {
|
private removeDefaultInputedFileURL = () => {
|
||||||
if (this.state.inputedFileURL === strings.predict.defaultURLInput) {
|
if (this.state.inputedFileURL === strings.predict.defaultURLInput) {
|
||||||
this.setState({inputedFileURL: ""});
|
this.setState({ inputedFileURL: "" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setInputedFileURL = (event) => {
|
private setInputedFileURL = (event) => {
|
||||||
this.setState({inputedFileURL: event.target.value});
|
this.setState({ inputedFileURL: event.target.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFileFromURL = () => {
|
private getFileFromURL = () => {
|
||||||
this.setState({isFetching: true});
|
this.setState({ isFetching: true });
|
||||||
fetch(this.state.inputedFileURL, { headers: {Accept: "application/pdf, image/jpeg, image/png, image/tiff"}})
|
fetch(this.state.inputedFileURL, { headers: { Accept: "application/pdf, image/jpeg, image/png, image/tiff" } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
this.setState({
|
||||||
|
isFetching: false,
|
||||||
|
shouldShowAlert: true,
|
||||||
|
alertTitle: "Failed to fetch",
|
||||||
|
alertMessage: response.status.toString() + " " + response.statusText,
|
||||||
|
isPredicting: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contentType = response.headers.get("Content-Type");
|
||||||
|
if (!["application/pdf", "image/jpeg", "image/png", "image/tiff"].includes(contentType)) {
|
||||||
|
this.setState({
|
||||||
|
isFetching: false,
|
||||||
|
shouldShowAlert: true,
|
||||||
|
alertTitle: "Content-Type not supported",
|
||||||
|
alertMessage: "Content-Type " + contentType + " not supported",
|
||||||
|
isPredicting: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.blob().then((blob) => {
|
||||||
|
const fileAsURL = new URL(this.state.inputedFileURL);
|
||||||
|
const fileName = fileAsURL.pathname.split("/").pop();
|
||||||
|
const file = new File([blob], fileName, { type: contentType });
|
||||||
|
this.setState({
|
||||||
|
fetchedFileURL: this.state.inputedFileURL,
|
||||||
|
isFetching: false,
|
||||||
|
fileLabel: fileName,
|
||||||
|
currPage: 1,
|
||||||
|
analyzeResult: {},
|
||||||
|
fileChanged: true,
|
||||||
|
file,
|
||||||
|
predictRun: false,
|
||||||
|
}, () => {
|
||||||
|
if (this.imageMap) {
|
||||||
|
this.imageMap.removeAllFeatures();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
this.setState({
|
||||||
|
isFetching: false,
|
||||||
|
shouldShowAlert: true,
|
||||||
|
alertTitle: "Invalid data",
|
||||||
|
alertMessage: error,
|
||||||
|
isPredicting: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}).catch(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
shouldShowAlert: true,
|
shouldShowAlert: true,
|
||||||
alertTitle: "Failed to fetch",
|
alertTitle: "Fetch failed",
|
||||||
alertMessage: response.status.toString() + " " + response.statusText,
|
alertMessage: "Network error or Cross-Origin Resource Sharing (CORS) is not configured server-side",
|
||||||
isPredicting: false,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const contentType = response.headers.get("Content-Type");
|
|
||||||
if (![ "application/pdf", "image/jpeg", "image/png", "image/tiff"].includes(contentType)) {
|
|
||||||
this.setState({
|
|
||||||
isFetching: false,
|
|
||||||
shouldShowAlert: true,
|
|
||||||
alertTitle: "Content-Type not supported",
|
|
||||||
alertMessage: "Content-Type " + contentType + " not supported",
|
|
||||||
isPredicting: false,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
response.blob().then((blob) => {
|
|
||||||
const fileAsURL = new URL(this.state.inputedFileURL);
|
|
||||||
const fileName = fileAsURL.pathname.split("/").pop();
|
|
||||||
const file = new File([blob], fileName, {type: contentType});
|
|
||||||
this.setState({
|
|
||||||
fetchedFileURL: this.state.inputedFileURL,
|
|
||||||
isFetching: false,
|
|
||||||
fileLabel: fileName,
|
|
||||||
currPage: 1,
|
|
||||||
analyzeResult: {},
|
|
||||||
fileChanged: true,
|
|
||||||
file,
|
|
||||||
predictRun: false,
|
|
||||||
}, () => {
|
|
||||||
if (this.imageMap) {
|
|
||||||
this.imageMap.removeAllFeatures();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
this.setState({
|
|
||||||
isFetching: false,
|
|
||||||
shouldShowAlert: true,
|
|
||||||
alertTitle: "Invalid data",
|
|
||||||
alertMessage: error,
|
|
||||||
isPredicting: false,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
|
||||||
this.setState({
|
|
||||||
isFetching: false,
|
|
||||||
shouldShowAlert: true,
|
|
||||||
alertTitle: "Fetch failed",
|
|
||||||
alertMessage: "Network error or Cross-Origin Resource Sharing (CORS) is not configured server-side",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectSource = (event, option) => {
|
private selectSource = (event, option) => {
|
||||||
|
@ -587,7 +603,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
<IconButton
|
<IconButton
|
||||||
className="toolbar-btn prev"
|
className="toolbar-btn prev"
|
||||||
title="Previous"
|
title="Previous"
|
||||||
iconProps={{iconName: "ChevronLeft"}}
|
iconProps={{ iconName: "ChevronLeft" }}
|
||||||
onClick={prevPage}
|
onClick={prevPage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -616,7 +632,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
className="toolbar-btn next"
|
className="toolbar-btn next"
|
||||||
title="Next"
|
title="Next"
|
||||||
onClick={nextPage}
|
onClick={nextPage}
|
||||||
iconProps={{iconName: "ChevronRight"}}
|
iconProps={{ iconName: "ChevronRight" }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -631,6 +647,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
handleZoomIn={this.handleCanvasZoomIn}
|
handleZoomIn={this.handleCanvasZoomIn}
|
||||||
handleZoomOut={this.handleCanvasZoomOut}
|
handleZoomOut={this.handleCanvasZoomOut}
|
||||||
handleRotateImage={this.handleRotateCanvas}
|
handleRotateImage={this.handleRotateCanvas}
|
||||||
|
project={this.props.project}
|
||||||
parentPage={"predict"}
|
parentPage={"predict"}
|
||||||
/>
|
/>
|
||||||
<ImageMap
|
<ImageMap
|
||||||
|
@ -742,17 +759,17 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
const apiKey = this.props.project.apiKey as string;
|
const apiKey = this.props.project.apiKey as string;
|
||||||
const analyzeScript = response.data.replace(/<endpoint>|<subscription_key>|<model_id>|<API_version>/gi,
|
const analyzeScript = response.data.replace(/<endpoint>|<subscription_key>|<model_id>|<API_version>/gi,
|
||||||
(matched: string) => {
|
(matched: string) => {
|
||||||
switch (matched) {
|
switch (matched) {
|
||||||
case "<endpoint>":
|
case "<endpoint>":
|
||||||
return endpointURL;
|
return endpointURL;
|
||||||
case "<subscription_key>":
|
case "<subscription_key>":
|
||||||
return apiKey;
|
return apiKey;
|
||||||
case "<model_id>":
|
case "<model_id>":
|
||||||
return modelID;
|
return modelID;
|
||||||
case "<API_version>":
|
case "<API_version>":
|
||||||
return constants.apiVersion;
|
return constants.apiVersion;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const fileURL = window.URL.createObjectURL(
|
const fileURL = window.URL.createObjectURL(
|
||||||
new Blob([analyzeScript]));
|
new Blob([analyzeScript]));
|
||||||
const fileLink = document.createElement("a");
|
const fileLink = document.createElement("a");
|
||||||
|
@ -813,7 +830,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
if (err.response.status === 404) {
|
if (err.response.status === 404) {
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
ErrorCode.ModelNotFound,
|
ErrorCode.ModelNotFound,
|
||||||
interpolate(strings.errors.modelNotFound.message, {modelID})
|
interpolate(strings.errors.modelNotFound.message, { modelID })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ServiceHelper.handleServiceError(err);
|
ServiceHelper.handleServiceError(err);
|
||||||
|
@ -895,7 +912,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
|
|
||||||
fileReader.onload = (e: any) => {
|
fileReader.onload = (e: any) => {
|
||||||
const typedArray = new Uint8Array(e.target.result);
|
const typedArray = new Uint8Array(e.target.result);
|
||||||
const loadingTask = pdfjsLib.getDocument({data: typedArray, cMapUrl, cMapPacked: true});
|
const loadingTask = pdfjsLib.getDocument({ data: typedArray, cMapUrl, cMapPacked: true });
|
||||||
loadingTask.promise.then((pdf) => {
|
loadingTask.promise.then((pdf) => {
|
||||||
this.currPdf = pdf;
|
this.currPdf = pdf;
|
||||||
this.loadPdfPage(pdf, this.state.currPage);
|
this.loadPdfPage(pdf, this.state.currPage);
|
||||||
|
@ -1056,7 +1073,41 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
private noOp = () => {
|
private noOp = () => {
|
||||||
// no operation
|
// no operation
|
||||||
}
|
}
|
||||||
|
private onAddAssetToProjectClick = async () => {
|
||||||
|
if (this.state.file) {
|
||||||
|
// this.props.project.assets
|
||||||
|
const fileName = `${this.props.project.folderPath}/${decodeURIComponent(this.state.file.name)}`;
|
||||||
|
const asset = Object.values(this.props.project.assets).find(asset => asset.name === fileName);
|
||||||
|
if (asset) {
|
||||||
|
const confirmDuplicatedAssetNameMessage = interpolate(strings.predict.confirmDuplicatedAssetName.message, { name: decodeURI(this.state.file.name) });
|
||||||
|
this.setState({
|
||||||
|
confirmDuplicatedAssetNameMessage
|
||||||
|
});
|
||||||
|
this.duplicateAssetNameConfirm.current.open();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.onAddAssetToProjectConfirm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private onAddAssetToProjectConfirm = async () => {
|
||||||
|
if (this.props.appSettings.hideUploadingOption) {
|
||||||
|
this.uploadToTrainingSetView.current.open();
|
||||||
|
await this.onAddAssetToProject();
|
||||||
|
this.uploadToTrainingSetView.current.close();
|
||||||
|
} else {
|
||||||
|
this.uploadToTrainingSetView.current.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private onAddAssetToProject = async () => {
|
||||||
|
if (this.state.file) {
|
||||||
|
const fileData = new Buffer(await this.state.file.arrayBuffer());
|
||||||
|
const readResults: any = this.state.analyzeResult;
|
||||||
|
const fileName = decodeURIComponent(this.state.file.name).split("/").pop();
|
||||||
|
await this.props.actions.addAssetToProject(this.props.project, fileName, fileData, readResults);
|
||||||
|
this.props.history.push(`/projects/${this.props.project.id}/edit`);
|
||||||
|
}
|
||||||
|
}
|
||||||
private onPredictionClick = (predictedItem: any) => {
|
private onPredictionClick = (predictedItem: any) => {
|
||||||
const targetPage = predictedItem.page;
|
const targetPage = predictedItem.page;
|
||||||
if (Number.isInteger(targetPage) && targetPage !== this.state.currPage) {
|
if (Number.isInteger(targetPage) && targetPage !== this.state.currPage) {
|
||||||
|
@ -1092,12 +1143,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
private handleModelSelection = () => {
|
private handleModelSelection = () => {
|
||||||
const selectedIndex = this.getSelectedIndex();
|
const selectedIndex = this.getSelectedIndex();
|
||||||
if (selectedIndex !== this.state.selectionIndexTracker) {
|
if (selectedIndex !== this.state.selectionIndexTracker) {
|
||||||
this.setState({selectionIndexTracker: selectedIndex})
|
this.setState({ selectionIndexTracker: selectedIndex })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRecentModelsViewClose = () => {
|
private handleRecentModelsViewClose = () => {
|
||||||
this.setState({showRecentModelsView: false});
|
this.setState({ showRecentModelsView: false });
|
||||||
const selectedIndex = this.getSelectedIndex();
|
const selectedIndex = this.getSelectedIndex();
|
||||||
if (selectedIndex !== this.state.selectedRecentModelIndex) {
|
if (selectedIndex !== this.state.selectedRecentModelIndex) {
|
||||||
this.selectionHandler.setIndexSelected(this.state.selectedRecentModelIndex, true, true);
|
this.selectionHandler.setIndexSelected(this.state.selectedRecentModelIndex, true, true);
|
||||||
|
@ -1121,22 +1172,22 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await axios.get(endpointURL,
|
response = await axios.get(endpointURL,
|
||||||
{headers: { [constants.apiKeyHeader]: this.props.project.apiKey as string}})
|
{ headers: { [constants.apiKeyHeader]: this.props.project.apiKey as string } })
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const status = err.response.status;
|
const status = err.response.status;
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
this.setState({
|
this.setState({
|
||||||
couldNotGetRecentModel: true,
|
couldNotGetRecentModel: true,
|
||||||
shouldShowAlert: true,
|
shouldShowAlert: true,
|
||||||
alertTitle: "Failed to get recent model",
|
alertTitle: "Failed to get recent model",
|
||||||
alertMessage: "Permission denied. Check API key",
|
alertMessage: "Permission denied. Check API key",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
couldNotGetRecentModel: true,
|
couldNotGetRecentModel: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
this.setState({
|
this.setState({
|
||||||
couldNotGetRecentModel: true,
|
couldNotGetRecentModel: true,
|
||||||
|
@ -1175,7 +1226,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
if (model.modelInfo.modelId === project.predictModelId) {
|
if (model.modelInfo.modelId === project.predictModelId) {
|
||||||
predictModelIndex = index
|
predictModelIndex = index
|
||||||
}
|
}
|
||||||
recentModelRecordsWithKey[index] = Object.assign({key: index}, model);
|
recentModelRecordsWithKey[index] = Object.assign({ key: index }, model);
|
||||||
})
|
})
|
||||||
this.selectionHandler.setItems(recentModelRecordsWithKey, false);
|
this.selectionHandler.setItems(recentModelRecordsWithKey, false);
|
||||||
this.selectionHandler.setIndexSelected(predictModelIndex, true, false);
|
this.selectionHandler.setIndexSelected(predictModelIndex, true, false);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import "./predictResult.scss";
|
||||||
import { getPrimaryGreenTheme } from "../../../../common/themes";
|
import { getPrimaryGreenTheme } from "../../../../common/themes";
|
||||||
import { PrimaryButton } from "@fluentui/react";
|
import { PrimaryButton } from "@fluentui/react";
|
||||||
import PredictModelInfo from './predictModelInfo';
|
import PredictModelInfo from './predictModelInfo';
|
||||||
|
import { strings } from "../../../../common/strings";
|
||||||
|
|
||||||
export interface IAnalyzeModelInfo {
|
export interface IAnalyzeModelInfo {
|
||||||
docType: string,
|
docType: string,
|
||||||
|
@ -21,6 +22,7 @@ export interface IPredictResultProps {
|
||||||
page: number;
|
page: number;
|
||||||
tags: ITag[];
|
tags: ITag[];
|
||||||
downloadResultLabel: string;
|
downloadResultLabel: string;
|
||||||
|
onAddAssetToProject?: () => void;
|
||||||
onPredictionClick?: (item: any) => void;
|
onPredictionClick?: (item: any) => void;
|
||||||
onPredictionMouseEnter?: (item: any) => void;
|
onPredictionMouseEnter?: (item: any) => void;
|
||||||
onPredictionMouseLeave?: (item: any) => void;
|
onPredictionMouseLeave?: (item: any) => void;
|
||||||
|
@ -46,6 +48,13 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
||||||
<div>
|
<div>
|
||||||
<div className="container-items-center container-space-between results-container">
|
<div className="container-items-center container-space-between results-container">
|
||||||
<h5 className="results-header">Prediction results</h5>
|
<h5 className="results-header">Prediction results</h5>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="container-items-center container-space-between">
|
||||||
|
<PrimaryButton
|
||||||
|
theme={getPrimaryGreenTheme()}
|
||||||
|
onClick={this.onAddAssetToProject}
|
||||||
|
text={strings.predict.editAndUploadToTrainingSet} />
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="align-self-end keep-button-80px"
|
className="align-self-end keep-button-80px"
|
||||||
theme={getPrimaryGreenTheme()}
|
theme={getPrimaryGreenTheme()}
|
||||||
|
@ -142,6 +151,11 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onAddAssetToProject = async () => {
|
||||||
|
if (this.props.onAddAssetToProject) {
|
||||||
|
this.props.onAddAssetToProject();
|
||||||
|
}
|
||||||
|
}
|
||||||
private triggerDownload = (): void => {
|
private triggerDownload = (): void => {
|
||||||
const { analyzeResult } = this.props;
|
const { analyzeResult } = this.props;
|
||||||
const predictionData = JSON.stringify(this.sanitizeData(analyzeResult));
|
const predictionData = JSON.stringify(this.sanitizeData(analyzeResult));
|
||||||
|
@ -189,23 +203,23 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
||||||
switch (predictionType) {
|
switch (predictionType) {
|
||||||
case "string":
|
case "string":
|
||||||
valueType = "valueString";
|
valueType = "valueString";
|
||||||
postProcessedValue = prediction.valueString;
|
postProcessedValue = prediction.valueString;
|
||||||
break;
|
break;
|
||||||
case "date":
|
case "date":
|
||||||
valueType = "valueDate";
|
valueType = "valueDate";
|
||||||
postProcessedValue = prediction.valueDate;
|
postProcessedValue = prediction.valueDate;
|
||||||
break;
|
break;
|
||||||
case "number":
|
case "number":
|
||||||
valueType = "valueNumber";
|
valueType = "valueNumber";
|
||||||
postProcessedValue = prediction.valueNumber?.toString();
|
postProcessedValue = prediction.valueNumber?.toString();
|
||||||
break;
|
break;
|
||||||
case "integer":
|
case "integer":
|
||||||
valueType = "valueInteger";
|
valueType = "valueInteger";
|
||||||
postProcessedValue = prediction.valueInteger?.toString();
|
postProcessedValue = prediction.valueInteger?.toString();
|
||||||
break;
|
break;
|
||||||
case "time":
|
case "time":
|
||||||
valueType = "valueTime";
|
valueType = "valueTime";
|
||||||
postProcessedValue = prediction.valueTime;
|
postProcessedValue = prediction.valueTime;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.upload-to-training-set-modal {
|
||||||
|
max-width: 34em;
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { Customizer, ICustomizations, Modal, PrimaryButton, Spinner, SpinnerSize } from '@fluentui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { strings } from "../../../../common/strings";
|
||||||
|
import { getDarkGreyTheme, getDefaultDarkTheme, getPrimaryGreenTheme, getPrimaryGreyTheme } from '../../../../common/themes';
|
||||||
|
import './uploadToTrainingSetView.scss';
|
||||||
|
|
||||||
|
interface IUploadToTrainingSetViewProp {
|
||||||
|
onConfirm?: () => Promise<void>;
|
||||||
|
showOption: boolean;
|
||||||
|
}
|
||||||
|
interface IUploadToTrainingSetViewState {
|
||||||
|
hideModal: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
export class UploadToTrainingSetView extends React.Component<IUploadToTrainingSetViewProp, IUploadToTrainingSetViewState>{
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hideModal: true,
|
||||||
|
isLoading: !props.showOption,
|
||||||
|
};
|
||||||
|
this.open = this.open.bind(this);
|
||||||
|
this.close = this.close.bind(this);
|
||||||
|
this.onConfirm = this.onConfirm.bind(this);
|
||||||
|
}
|
||||||
|
open() {
|
||||||
|
this.setState({
|
||||||
|
hideModal: false,
|
||||||
|
isLoading: !this.props.showOption
|
||||||
|
});
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
this.setState({ hideModal: true });
|
||||||
|
}
|
||||||
|
async onConfirm() {
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
if (this.props.onConfirm) {
|
||||||
|
await this.props.onConfirm();
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const dark: ICustomizations = {
|
||||||
|
settings: {
|
||||||
|
theme: getDarkGreyTheme(),
|
||||||
|
},
|
||||||
|
scopedSettings: {},
|
||||||
|
};
|
||||||
|
const notifyMessage = this.props.showOption ? strings.predict.editAndUploadToTrainingSetNotify : strings.predict.editAndUploadToTrainingSetNotify2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Customizer {...dark}>
|
||||||
|
<Modal
|
||||||
|
isOpen={!this.state.hideModal}
|
||||||
|
isModeless={false}
|
||||||
|
containerClassName="modal-container upload-to-training-set-modal"
|
||||||
|
scrollableContentClassName="scrollable-content"
|
||||||
|
>
|
||||||
|
<h4>Notice: <small>{notifyMessage}</small></h4>
|
||||||
|
<div className="modal-buttons-container mt-4">
|
||||||
|
{this.state.isLoading ?
|
||||||
|
<div>
|
||||||
|
<Spinner
|
||||||
|
label={strings.predict.uploadInPrgoress}
|
||||||
|
ariaLive="assertive"
|
||||||
|
labelPosition="right"
|
||||||
|
theme={getDefaultDarkTheme()}
|
||||||
|
size={SpinnerSize.large} />
|
||||||
|
</div> :
|
||||||
|
<div>
|
||||||
|
<PrimaryButton
|
||||||
|
className="mr-3"
|
||||||
|
text={strings.predict.editAndUploadToTrainingSet}
|
||||||
|
theme={getPrimaryGreenTheme()}
|
||||||
|
onClick={this.onConfirm} />
|
||||||
|
<PrimaryButton
|
||||||
|
className="modal-cancel"
|
||||||
|
theme={getPrimaryGreyTheme()}
|
||||||
|
onClick={this.close}
|
||||||
|
text="Cancel" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Customizer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import {
|
||||||
IUpdateProjectTagAction,
|
IUpdateProjectTagAction,
|
||||||
IUpdateProjectTagsFromFilesAction,
|
IUpdateProjectTagsFromFilesAction,
|
||||||
IUpdateTagDocumentCount,
|
IUpdateTagDocumentCount,
|
||||||
|
IAddAssetToProjectAction,
|
||||||
} from "./projectActions";
|
} from "./projectActions";
|
||||||
import {
|
import {
|
||||||
IShowAppErrorAction,
|
IShowAppErrorAction,
|
||||||
|
@ -83,6 +84,7 @@ export type AnyAction = IOtherAction |
|
||||||
IDeleteConnectionAction |
|
IDeleteConnectionAction |
|
||||||
ILoadConnectionAction |
|
ILoadConnectionAction |
|
||||||
ISaveConnectionAction |
|
ISaveConnectionAction |
|
||||||
|
IAddAssetToProjectAction|
|
||||||
IDeleteConnectionAction |
|
IDeleteConnectionAction |
|
||||||
ILoadProjectAction |
|
ILoadProjectAction |
|
||||||
ICloseProjectAction |
|
ICloseProjectAction |
|
||||||
|
|
|
@ -12,6 +12,7 @@ export enum ActionTypes {
|
||||||
// Projects
|
// Projects
|
||||||
LOAD_PROJECT_SUCCESS = "LOAD_PROJECT_SUCCESS",
|
LOAD_PROJECT_SUCCESS = "LOAD_PROJECT_SUCCESS",
|
||||||
SAVE_PROJECT_SUCCESS = "SAVE_PROJECT_SUCCESS",
|
SAVE_PROJECT_SUCCESS = "SAVE_PROJECT_SUCCESS",
|
||||||
|
ADD_ASSET_TO_PROJECT_SUCCESS = "ADD_ASSET_TO_PROJECT_SUCCESS",
|
||||||
DELETE_PROJECT_SUCCESS = "DELETE_PROJECT_SUCCESS",
|
DELETE_PROJECT_SUCCESS = "DELETE_PROJECT_SUCCESS",
|
||||||
CLOSE_PROJECT_SUCCESS = "CLOSE_PROJECT_SUCCESS",
|
CLOSE_PROJECT_SUCCESS = "CLOSE_PROJECT_SUCCESS",
|
||||||
LOAD_PROJECT_ASSETS_SUCCESS = "LOAD_PROJECT_ASSETS_SUCCESS",
|
LOAD_PROJECT_ASSETS_SUCCESS = "LOAD_PROJECT_ASSETS_SUCCESS",
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default interface IProjectActions {
|
||||||
saveProject(project: IProject, saveTags?: boolean, updateTagsFromFiles?: boolean): Promise<IProject>;
|
saveProject(project: IProject, saveTags?: boolean, updateTagsFromFiles?: boolean): Promise<IProject>;
|
||||||
deleteProject(project: IProject): Promise<void>;
|
deleteProject(project: IProject): Promise<void>;
|
||||||
closeProject(): void;
|
closeProject(): void;
|
||||||
|
addAssetToProject(project: IProject, fileName: string, buffer: Buffer, analyzeResult: any): Promise<IAsset>;
|
||||||
deleteAsset(project: IProject, assetMetadata: IAssetMetadata): Promise<void>;
|
deleteAsset(project: IProject, assetMetadata: IAssetMetadata): Promise<void>;
|
||||||
loadAssets(project: IProject): Promise<IAsset[]>;
|
loadAssets(project: IProject): Promise<IAsset[]>;
|
||||||
loadAssetMetadata(project: IProject, asset: IAsset): Promise<IAssetMetadata>;
|
loadAssetMetadata(project: IProject, asset: IAsset): Promise<IAssetMetadata>;
|
||||||
|
@ -64,7 +65,7 @@ export function loadProject(project: IProject, sharedToken?: ISecurityToken):
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
} else if (existingToken.key !== sharedToken.key) {
|
} else if (existingToken.key !== sharedToken.key) {
|
||||||
const reason = interpolate(strings.shareProject.errors.tokenNameExist, {sharedTokenName: sharedToken.name})
|
const reason = interpolate(strings.shareProject.errors.tokenNameExist, { sharedTokenName: sharedToken.name })
|
||||||
toast.error(reason, { autoClose: false, closeOnClick: false });
|
toast.error(reason, { autoClose: false, closeOnClick: false });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +127,7 @@ export function updateProjectTagsFromFiles(project: IProject, asset?: string): (
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updatedAssetMetadata(project: IProject,
|
export function updatedAssetMetadata(project: IProject,
|
||||||
assetDocumentCountDifference: any): (dispatch: Dispatch) => Promise<void> {
|
assetDocumentCountDifference: any): (dispatch: Dispatch) => Promise<void> {
|
||||||
return async (dispatch: Dispatch) => {
|
return async (dispatch: Dispatch) => {
|
||||||
const projectService = new ProjectService();
|
const projectService = new ProjectService();
|
||||||
const updatedProject = await projectService.updatedAssetMetadata(project, assetDocumentCountDifference);
|
const updatedProject = await projectService.updatedAssetMetadata(project, assetDocumentCountDifference);
|
||||||
|
@ -169,7 +170,22 @@ export function closeProject(): (dispatch: Dispatch) => void {
|
||||||
dispatch({ type: ActionTypes.CLOSE_PROJECT_SUCCESS });
|
dispatch({ type: ActionTypes.CLOSE_PROJECT_SUCCESS });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* add asset, ocr data, labels to project storage.
|
||||||
|
*/
|
||||||
|
export function addAssetToProject(project: IProject, fileName: string, buffer: Buffer, analyzeResult: any): (dispatch: Dispatch) => Promise<IAsset> {
|
||||||
|
return async (dispatch: Dispatch) => {
|
||||||
|
const assetService = new AssetService(project);
|
||||||
|
await assetService.uploadBuffer(fileName, buffer);
|
||||||
|
const assets = await assetService.getAssets();
|
||||||
|
const assetName = project.folderPath ? `${project.folderPath}/${fileName}` : fileName;
|
||||||
|
const asset = assets.find(a => a.name === assetName);
|
||||||
|
|
||||||
|
await assetService.uploadAssetPredictResult(asset, analyzeResult);
|
||||||
|
dispatch(addAssetToProjectAction(asset));
|
||||||
|
return asset;
|
||||||
|
};
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Dispatches Delete Asset action
|
* Dispatches Delete Asset action
|
||||||
*/
|
*/
|
||||||
|
@ -351,6 +367,12 @@ export interface IDeleteProjectAction extends IPayloadAction<string, IProject> {
|
||||||
type: ActionTypes.DELETE_PROJECT_SUCCESS;
|
type: ActionTypes.DELETE_PROJECT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add asset to project action type
|
||||||
|
*/
|
||||||
|
export interface IAddAssetToProjectAction extends IPayloadAction<string, IAsset> {
|
||||||
|
type: ActionTypes.ADD_ASSET_TO_PROJECT_SUCCESS;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Load project assets action type
|
* Load project assets action type
|
||||||
*/
|
*/
|
||||||
|
@ -409,6 +431,10 @@ export const saveProjectAction = createPayloadAction<ISaveProjectAction>(ActionT
|
||||||
* Instance of Delete Project action
|
* Instance of Delete Project action
|
||||||
*/
|
*/
|
||||||
export const deleteProjectAction = createPayloadAction<IDeleteProjectAction>(ActionTypes.DELETE_PROJECT_SUCCESS);
|
export const deleteProjectAction = createPayloadAction<IDeleteProjectAction>(ActionTypes.DELETE_PROJECT_SUCCESS);
|
||||||
|
/**
|
||||||
|
* Instance of Add Asset to Project action
|
||||||
|
*/
|
||||||
|
export const addAssetToProjectAction = createPayloadAction<IAddAssetToProjectAction>(ActionTypes.ADD_ASSET_TO_PROJECT_SUCCESS);
|
||||||
/**
|
/**
|
||||||
* Instance of Load Project Assets action
|
* Instance of Load Project Assets action
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,6 +18,8 @@ export const reducer = (state: IAppSettings = null, action: AnyAction): IAppSett
|
||||||
return { ...action.payload };
|
return { ...action.payload };
|
||||||
case ActionTypes.ENSURE_SECURITY_TOKEN_SUCCESS:
|
case ActionTypes.ENSURE_SECURITY_TOKEN_SUCCESS:
|
||||||
return { ...action.payload };
|
return { ...action.payload };
|
||||||
|
case ActionTypes.ADD_ASSET_TO_PROJECT_SUCCESS:
|
||||||
|
return { ...state, hideUploadingOption: true };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ export const reducer = (state: IProject = null, action: AnyAction): IProject =>
|
||||||
return null;
|
return null;
|
||||||
case ActionTypes.LOAD_PROJECT_SUCCESS:
|
case ActionTypes.LOAD_PROJECT_SUCCESS:
|
||||||
return { ...action.payload };
|
return { ...action.payload };
|
||||||
|
case ActionTypes.ADD_ASSET_TO_PROJECT_SUCCESS:
|
||||||
|
return { ...state, lastVisitedAssetId: action.payload.id };
|
||||||
case ActionTypes.LOAD_ASSET_METADATA_SUCCESS:
|
case ActionTypes.LOAD_ASSET_METADATA_SUCCESS:
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -23,7 +23,7 @@ const supportedImageFormats = {
|
||||||
|
|
||||||
interface IMime {
|
interface IMime {
|
||||||
types: string[];
|
types: string[];
|
||||||
pattern: (number|undefined)[];
|
pattern: (number | undefined)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable number-literal-format
|
// tslint:disable number-literal-format
|
||||||
|
@ -62,6 +62,91 @@ const mimeBytesNeeded: number = (Math.max(...imageMimes.map((m) => m.pattern.len
|
||||||
* @description - Functions for dealing with project assets
|
* @description - Functions for dealing with project assets
|
||||||
*/
|
*/
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
|
private getOcrFromAnalyzeResult(analyzeResult: any) {
|
||||||
|
return _.get(analyzeResult, "analyzeResult.readResults", []);
|
||||||
|
}
|
||||||
|
async uploadAssetPredictResult(asset: IAsset, readResults: any): Promise<void> {
|
||||||
|
const getBoundingBox = (pageIndex, arr: number[]) => {
|
||||||
|
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(readResults)[pageIndex - 1];
|
||||||
|
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||||
|
const ocrWidth = ocrExtent[2] - ocrExtent[0];
|
||||||
|
const ocrHeight = ocrExtent[3] - ocrExtent[1];
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < arr.length; i += 2) {
|
||||||
|
result.push([
|
||||||
|
(arr[i] / ocrWidth),
|
||||||
|
(arr[i + 1] / ocrHeight),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const getLabelValues = (field: any) => {
|
||||||
|
return field.elements.map((path: string) => {
|
||||||
|
const pathArr = path.split('/').slice(1);
|
||||||
|
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...readResults.analyzeResult });
|
||||||
|
return {
|
||||||
|
page: field.page,
|
||||||
|
text: word.text || word.state,
|
||||||
|
confidence: word.confidence,
|
||||||
|
boundingBoxes: [getBoundingBox(field.page, word.boundingBox)]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const labels = [];
|
||||||
|
readResults.analyzeResult.documentResults
|
||||||
|
.map(result => Object.keys(result.fields)
|
||||||
|
.filter(key => result.fields[key])
|
||||||
|
.map<ILabel>(key => (
|
||||||
|
{
|
||||||
|
label: key,
|
||||||
|
key: null,
|
||||||
|
value: getLabelValues(result.fields[key])
|
||||||
|
}))).forEach(items => {
|
||||||
|
labels.push(...items);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (labels.length > 0) {
|
||||||
|
const fileName = decodeURIComponent(asset.name).split('/').pop();
|
||||||
|
const labelData: ILabelData = {
|
||||||
|
document: fileName,
|
||||||
|
labels
|
||||||
|
};
|
||||||
|
const metadata = {
|
||||||
|
...await this.getAssetMetadata(asset),
|
||||||
|
labelData
|
||||||
|
};
|
||||||
|
metadata.asset.state = AssetState.Tagged;
|
||||||
|
|
||||||
|
const ocrData = JSON.parse(JSON.stringify(readResults));
|
||||||
|
delete ocrData.analyzeResult.documentResults;
|
||||||
|
if (ocrData.analyzeResult.errors) {
|
||||||
|
delete ocrData.analyzeResult.errors;
|
||||||
|
}
|
||||||
|
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
||||||
|
await Promise.all([
|
||||||
|
this.save(metadata),
|
||||||
|
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const ocrData = { ...readResults };
|
||||||
|
delete ocrData.analyzeResult.documentResults;
|
||||||
|
if (ocrData.analyzeResult.errors) {
|
||||||
|
delete ocrData.analyzeResult.errors;
|
||||||
|
}
|
||||||
|
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||||
|
const ocrFileName = decodeURIComponent(`${asset.name}${constants.ocrFileExtension}`);
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
this.storageProvider.deleteFile(labelFileName, true, true),
|
||||||
|
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create IAsset from filePath
|
* Create IAsset from filePath
|
||||||
* @param filePath - filepath of asset
|
* @param filePath - filepath of asset
|
||||||
|
@ -230,13 +315,20 @@ export class AssetService {
|
||||||
return asset;
|
return asset;
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return assets.map((asset) => {
|
return assets.map((asset) => {
|
||||||
asset.name = decodeURIComponent(asset.name);
|
asset.name = decodeURIComponent(asset.name);
|
||||||
return asset;
|
return asset;
|
||||||
}).filter((asset) => this.isInExactFolderPath(asset.name, folderPath));
|
}).filter((asset) => this.isInExactFolderPath(asset.name, folderPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async uploadBuffer(name: string, buffer: Buffer) {
|
||||||
|
const path = this.project.folderPath ? `${this.project.folderPath}/${name}` : name;
|
||||||
|
await this.storageProvider.writeBinary(path, buffer);
|
||||||
|
}
|
||||||
|
public async uploadText(name: string, contents: string) {
|
||||||
|
const path = this.project.folderPath ? `${this.project.folderPath}/${name}` : name;
|
||||||
|
await this.storageProvider.writeText(path, contents);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Delete asset
|
* Delete asset
|
||||||
* @param metadata - Metadata for asset
|
* @param metadata - Metadata for asset
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import _ from "lodash";
|
||||||
|
import url from 'url';
|
||||||
|
import { constants } from "../common/constants";
|
||||||
|
import { interpolate, strings } from "../common/strings";
|
||||||
|
import { AppError, ErrorCode, IProject } from "../models/applicationState";
|
||||||
|
import ServiceHelper from "./serviceHelper";
|
||||||
|
|
||||||
|
export enum AutoLabelingStatus {
|
||||||
|
none,
|
||||||
|
running,
|
||||||
|
done
|
||||||
|
}
|
||||||
|
export class PredictService {
|
||||||
|
|
||||||
|
constructor(private project: IProject) {
|
||||||
|
}
|
||||||
|
public async getPrediction(fileUrl: string): Promise<any> {
|
||||||
|
const modelID = this.project.predictModelId;
|
||||||
|
if (!modelID) {
|
||||||
|
throw new AppError(
|
||||||
|
ErrorCode.PredictWithoutTrainForbidden,
|
||||||
|
strings.errors.predictWithoutTrainForbidden.message,
|
||||||
|
strings.errors.predictWithoutTrainForbidden.title);
|
||||||
|
}
|
||||||
|
const endpointURL = url.resolve(
|
||||||
|
this.project.apiUriBase,
|
||||||
|
`${constants.apiModelsPath}/${modelID}/analyze?includeTextDetails=true`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = { "Content-Type": "application/json", "cache-control": "no-cache" };
|
||||||
|
const body = { source: fileUrl };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await ServiceHelper.postWithAutoRetry(endpointURL, body, { headers }, this.project.apiKey as string);
|
||||||
|
const operationLocation = response.headers["operation-location"];
|
||||||
|
|
||||||
|
return this.poll(() =>
|
||||||
|
ServiceHelper.getWithAutoRetry(
|
||||||
|
operationLocation, { headers }, this.project.apiKey as string), 120000, 500);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
if (err.response.status === 404) {
|
||||||
|
throw new AppError(
|
||||||
|
ErrorCode.ModelNotFound,
|
||||||
|
interpolate(strings.errors.modelNotFound.message, { modelID })
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ServiceHelper.handleServiceError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private poll = (func, timeout, interval): Promise<any> => {
|
||||||
|
const endTime = Number(new Date()) + (timeout || 10000);
|
||||||
|
interval = interval || 100;
|
||||||
|
|
||||||
|
const checkSucceeded = (resolve, reject) => {
|
||||||
|
const ajax = func();
|
||||||
|
ajax.then((response) => {
|
||||||
|
if (response.data.status.toLowerCase() === constants.statusCodeSucceeded) {
|
||||||
|
resolve(response.data);
|
||||||
|
// prediction response from API
|
||||||
|
console.log("raw data", JSON.parse(response.request.response));
|
||||||
|
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||||
|
reject(_.get(
|
||||||
|
response,
|
||||||
|
"data.analyzeResult.errors[0].errorMessage",
|
||||||
|
"Generic error during prediction"));
|
||||||
|
} else if (Number(new Date()) < endTime) {
|
||||||
|
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
||||||
|
setTimeout(checkSucceeded, interval, resolve, reject);
|
||||||
|
} else {
|
||||||
|
// Didn't succeeded after too much time, reject
|
||||||
|
reject("Timed out, please try other file.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(checkSucceeded);
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче