feat: enable re-run OCR (#297)
* feat: enable re-run OCR (draft) * add rerun OCR for single file functionality * added tooltip * add icon and changed position * added - rerun OCR for all documents in project * fix: remove idents * feat: enable re-run OCR (draft) * add rerun OCR for single file functionality * added tooltip * add icon and changed position * added - rerun OCR for all documents in project * refactor: delete redundant local state and rename * fix: indent * style: change submenu text * docs: added manual test runbook * style: idents * refactor: rename dropdown class name * renaming: re-run.. to run, files to documents.. * fix: style and grammar * fix: run on not visited * docs: new Test Runbook revision * style: fix wording and spacing * refactor: use anonymous wrapper function for onclick Co-authored-by: kunzheng <58841788+kunzms@users.noreply.github.com> Co-authored-by: Robert Stewart (eXcell <v-stewro@microsoft.com>
This commit is contained in:
Родитель
7c57de9656
Коммит
cbe9b0ed1c
|
@ -0,0 +1,24 @@
|
|||
# Test Runbook
|
||||
|
||||
## **Enable rerun OCR for current or all documents**
|
||||
|
||||
> ### Feature description ###
|
||||
Add the following buttons to the canvas command bar:
|
||||
- "Run OCR on current document"
|
||||
- "Run OCR on all documents"
|
||||
|
||||
> ### Use Case ###
|
||||
**`As`** a user
|
||||
**`I want`** to rerun OCR on documents
|
||||
**`So`** I can update OCR results
|
||||
|
||||
> ### Acceptance criteria ###
|
||||
#### Scenario One ####
|
||||
**`Given`** I've opened a project containing documents and I'm on the Tag Editor page.
|
||||
**`When`** I click "Run OCR on current document" in the canvas command bar.
|
||||
**`Then`** I should see "Running OCR..." for the current docucment. When running OCR finishes, I should be able to view the document's updated OCR JSON file.
|
||||
|
||||
#### **Scenario Two** ####
|
||||
**`Given`** I've opened a project containing documents and I'm on the Tag Editor page.
|
||||
**`When`** I click "Run OCR on all documents" in the canvas command bar.
|
||||
**`Then`** I should see "Running OCR..." for all documents. When running OCR finishes for each document, I should be ale to view each document's updated OCR JSON file.
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,190 +1,202 @@
|
|||
{
|
||||
"fontName": "fabric-icons",
|
||||
"fontFamilyName": "FabricMDL2Icons",
|
||||
"excludeGlyphs": false,
|
||||
"excludeThirdPartyIcons": false,
|
||||
"chunkSubsets": false,
|
||||
"hashFontFileName": true,
|
||||
"glyphs": [
|
||||
{
|
||||
"name": "Insights",
|
||||
"unicode": "E3AF"
|
||||
},
|
||||
{
|
||||
"name": "MachineLearning",
|
||||
"unicode": "E3B8"
|
||||
},
|
||||
{
|
||||
"name": "TagGroup",
|
||||
"unicode": "E3F6"
|
||||
},
|
||||
{
|
||||
"name": "ChevronDown",
|
||||
"unicode": "E70D"
|
||||
},
|
||||
{
|
||||
"name": "ChevronUp",
|
||||
"unicode": "E70E"
|
||||
},
|
||||
{
|
||||
"name": "Edit",
|
||||
"unicode": "E70F"
|
||||
},
|
||||
{
|
||||
"name": "Add",
|
||||
"unicode": "E710"
|
||||
},
|
||||
{
|
||||
"name": "Cancel",
|
||||
"unicode": "E711"
|
||||
},
|
||||
{
|
||||
"name": "Settings",
|
||||
"unicode": "E713"
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"unicode": "E71B"
|
||||
},
|
||||
{
|
||||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "ZoomOut",
|
||||
"unicode": "E71F"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"unicode": "E721"
|
||||
},
|
||||
{
|
||||
"name": "CheckboxComposite",
|
||||
"unicode": "E73A"
|
||||
},
|
||||
{
|
||||
"name": "CheckMark",
|
||||
"unicode": "E73E"
|
||||
},
|
||||
{
|
||||
"name": "Up",
|
||||
"unicode": "E74A"
|
||||
},
|
||||
{
|
||||
"name": "Down",
|
||||
"unicode": "E74B"
|
||||
},
|
||||
{
|
||||
"name": "Delete",
|
||||
"unicode": "E74D"
|
||||
},
|
||||
{
|
||||
"name": "Cloud",
|
||||
"unicode": "E753"
|
||||
},
|
||||
{
|
||||
"name": "ChevronLeft",
|
||||
"unicode": "E76B"
|
||||
},
|
||||
{
|
||||
"name": "ChevronRight",
|
||||
"unicode": "E76C"
|
||||
},
|
||||
{
|
||||
"name": "Home",
|
||||
"unicode": "E80F"
|
||||
},
|
||||
{
|
||||
"name": "MapLayers",
|
||||
"unicode": "E81E"
|
||||
},
|
||||
{
|
||||
"name": "View",
|
||||
"unicode": "E890"
|
||||
},
|
||||
{
|
||||
"name": "Download",
|
||||
"unicode": "E896"
|
||||
},
|
||||
{
|
||||
"name": "Help",
|
||||
"unicode": "E897"
|
||||
},
|
||||
{
|
||||
"name": "ZoomIn",
|
||||
"unicode": "E8A3"
|
||||
},
|
||||
{
|
||||
"name": "Rename",
|
||||
"unicode": "E8AC"
|
||||
},
|
||||
{
|
||||
"name": "Copy",
|
||||
"unicode": "E8C8"
|
||||
},
|
||||
{
|
||||
"name": "Tag",
|
||||
"unicode": "E8EC"
|
||||
},
|
||||
{
|
||||
"name": "Label",
|
||||
"unicode": "E932"
|
||||
},
|
||||
{
|
||||
"name": "Info",
|
||||
"unicode": "E946"
|
||||
},
|
||||
{
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
},
|
||||
{
|
||||
"name": "OpenFolderHorizontal",
|
||||
"unicode": "ED25"
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"unicode": "ED86"
|
||||
},
|
||||
{
|
||||
"name": "TextField",
|
||||
"unicode": "EDC3"
|
||||
},
|
||||
{
|
||||
"name": "DocumentManagement",
|
||||
"unicode": "EFFC"
|
||||
},
|
||||
{
|
||||
"name": "TextDocument",
|
||||
"unicode": "F029"
|
||||
},
|
||||
{
|
||||
"name": "BranchMerge",
|
||||
"unicode": "F295"
|
||||
},
|
||||
{
|
||||
"name": "Plug",
|
||||
"unicode": "F300"
|
||||
},
|
||||
{
|
||||
"name": "PlugConnected",
|
||||
"unicode": "F302"
|
||||
},
|
||||
{
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
},
|
||||
{
|
||||
"name": "Hide3",
|
||||
"unicode": "F6AC"
|
||||
},
|
||||
{
|
||||
"name": "WarningSolid",
|
||||
"unicode": "F736"
|
||||
},
|
||||
{
|
||||
"name": "BookAnswers",
|
||||
"unicode": "F8A4"
|
||||
}
|
||||
]
|
||||
}
|
||||
"fontName": "fabric-icons",
|
||||
"fontFamilyName": "FabricMDL2Icons",
|
||||
"excludeGlyphs": false,
|
||||
"excludeThirdPartyIcons": false,
|
||||
"chunkSubsets": false,
|
||||
"hashFontFileName": true,
|
||||
"glyphs": [
|
||||
{
|
||||
"name": "Table",
|
||||
"unicode": "ED86"
|
||||
},
|
||||
{
|
||||
"name": "TextField",
|
||||
"unicode": "EDC3"
|
||||
},
|
||||
{
|
||||
"name": "TextDocument",
|
||||
"unicode": "F029"
|
||||
},
|
||||
{
|
||||
"name": "DocumentManagement",
|
||||
"unicode": "EFFC"
|
||||
},
|
||||
{
|
||||
"name": "OpenFolderHorizontal",
|
||||
"unicode": "ED25"
|
||||
},
|
||||
{
|
||||
"name": "Info",
|
||||
"unicode": "E946"
|
||||
},
|
||||
{
|
||||
"name": "Label",
|
||||
"unicode": "E932"
|
||||
},
|
||||
{
|
||||
"name": "Documentation",
|
||||
"unicode": "EC17"
|
||||
},
|
||||
{
|
||||
"name": "AddTo",
|
||||
"unicode": "ECC8"
|
||||
},
|
||||
{
|
||||
"name": "Hide3",
|
||||
"unicode": "F6AC"
|
||||
},
|
||||
{
|
||||
"name": "WarningSolid",
|
||||
"unicode": "F736"
|
||||
},
|
||||
{
|
||||
"name": "BranchMerge",
|
||||
"unicode": "F295"
|
||||
},
|
||||
{
|
||||
"name": "PlugConnected",
|
||||
"unicode": "F302"
|
||||
},
|
||||
{
|
||||
"name": "Plug",
|
||||
"unicode": "F300"
|
||||
},
|
||||
{
|
||||
"name": "AlertSolid",
|
||||
"unicode": "F331"
|
||||
},
|
||||
{
|
||||
"name": "Refresh",
|
||||
"unicode": "E72C"
|
||||
},
|
||||
{
|
||||
"name": "CheckboxComposite",
|
||||
"unicode": "E73A"
|
||||
},
|
||||
{
|
||||
"name": "Cancel",
|
||||
"unicode": "E711"
|
||||
},
|
||||
{
|
||||
"name": "More",
|
||||
"unicode": "E712"
|
||||
},
|
||||
{
|
||||
"name": "Settings",
|
||||
"unicode": "E713"
|
||||
},
|
||||
{
|
||||
"name": "Link",
|
||||
"unicode": "E71B"
|
||||
},
|
||||
{
|
||||
"name": "Filter",
|
||||
"unicode": "E71C"
|
||||
},
|
||||
{
|
||||
"name": "ZoomOut",
|
||||
"unicode": "E71F"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"unicode": "E721"
|
||||
},
|
||||
{
|
||||
"name": "Add",
|
||||
"unicode": "E710"
|
||||
},
|
||||
{
|
||||
"name": "CheckMark",
|
||||
"unicode": "E73E"
|
||||
},
|
||||
{
|
||||
"name": "ChevronLeft",
|
||||
"unicode": "E76B"
|
||||
},
|
||||
{
|
||||
"name": "ChevronRight",
|
||||
"unicode": "E76C"
|
||||
},
|
||||
{
|
||||
"name": "Up",
|
||||
"unicode": "E74A"
|
||||
},
|
||||
{
|
||||
"name": "Down",
|
||||
"unicode": "E74B"
|
||||
},
|
||||
{
|
||||
"name": "Delete",
|
||||
"unicode": "E74D"
|
||||
},
|
||||
{
|
||||
"name": "Cloud",
|
||||
"unicode": "E753"
|
||||
},
|
||||
{
|
||||
"name": "Edit",
|
||||
"unicode": "E70F"
|
||||
},
|
||||
{
|
||||
"name": "ChevronUp",
|
||||
"unicode": "E70E"
|
||||
},
|
||||
{
|
||||
"name": "ChevronDown",
|
||||
"unicode": "E70D"
|
||||
},
|
||||
{
|
||||
"name": "Copy",
|
||||
"unicode": "E8C8"
|
||||
},
|
||||
{
|
||||
"name": "ZoomIn",
|
||||
"unicode": "E8A3"
|
||||
},
|
||||
{
|
||||
"name": "Rename",
|
||||
"unicode": "E8AC"
|
||||
},
|
||||
{
|
||||
"name": "Tag",
|
||||
"unicode": "E8EC"
|
||||
},
|
||||
{
|
||||
"name": "Download",
|
||||
"unicode": "E896"
|
||||
},
|
||||
{
|
||||
"name": "View",
|
||||
"unicode": "E890"
|
||||
},
|
||||
{
|
||||
"name": "Help",
|
||||
"unicode": "E897"
|
||||
},
|
||||
{
|
||||
"name": "Home",
|
||||
"unicode": "E80F"
|
||||
},
|
||||
{
|
||||
"name": "MapLayers",
|
||||
"unicode": "E81E"
|
||||
},
|
||||
{
|
||||
"name": "Insights",
|
||||
"unicode": "E3AF"
|
||||
},
|
||||
{
|
||||
"name": "MachineLearning",
|
||||
"unicode": "E3B8"
|
||||
},
|
||||
{
|
||||
"name": "TagGroup",
|
||||
"unicode": "E3F6"
|
||||
},
|
||||
{
|
||||
"name": "BookAnswers",
|
||||
"unicode": "F8A4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ export class AssetPreview extends React.Component<IAssetPreviewProps, IAssetPrev
|
|||
|
||||
public render() {
|
||||
const { loaded, hasError } = this.state;
|
||||
const size = this.props.asset.size;
|
||||
const { size } = this.props.asset;
|
||||
const classNames = ["asset-preview"];
|
||||
if (size) {
|
||||
if (size.width > size.height) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
height: 100%;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
.map {
|
||||
background-color: Gainsboro;
|
||||
width: 100%;
|
||||
|
@ -57,7 +57,7 @@
|
|||
color: white;
|
||||
text-shadow:
|
||||
1px 1px 0 #000,
|
||||
-1px -1px 0 #000,
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
|
@ -86,3 +86,8 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.additional-action-dropdown {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: -1rem;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
|||
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
||||
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||
runOcrForAllDocs?: (runForAllDocs:boolean) => void;
|
||||
}
|
||||
|
||||
export interface ICanvasState {
|
||||
|
@ -212,6 +213,8 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
handleZoomIn={this.handleCanvasZoomIn}
|
||||
handleZoomOut={this.handleCanvasZoomOut}
|
||||
layers={this.state.layers}
|
||||
handleRunOcr={this.runOcr}
|
||||
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
||||
/>
|
||||
<ImageMap
|
||||
ref={(ref) => this.imageMap = ref}
|
||||
|
@ -285,6 +288,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
);
|
||||
}
|
||||
|
||||
private runOcrForAllDocuments = () => {
|
||||
this.setState({ocrStatus: OcrStatus.runningOCR})
|
||||
this.props.runOcrForAllDocs(true);
|
||||
}
|
||||
|
||||
public updateSize() {
|
||||
this.imageMap.updateSize();
|
||||
}
|
||||
|
@ -938,14 +946,19 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
});
|
||||
}
|
||||
|
||||
private loadOcr = async () => {
|
||||
private runOcr = () => {
|
||||
this.loadOcr(true);
|
||||
}
|
||||
|
||||
private loadOcr = async (force?: boolean) => {
|
||||
const asset = this.state.currentAsset.asset;
|
||||
|
||||
if (asset.isRunningOCR) {
|
||||
// Skip loading OCR this time since it's running. This will be triggered again once it's finished.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus);
|
||||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus, force);
|
||||
if (asset.id === this.state.currentAsset.asset.id) {
|
||||
// since get OCR is async, we only set currentAsset's OCR
|
||||
this.setState({
|
||||
|
|
|
@ -6,6 +6,8 @@ import { getDarkGreyTheme } from "../../../../common/themes";
|
|||
interface ICanvasCommandBarProps {
|
||||
handleZoomIn: () => void;
|
||||
handleZoomOut: () => void;
|
||||
handleRunOcr: () => void;
|
||||
handleRunOcrForAllDocuments: () => void;
|
||||
handleLayerChange: (layer: string) => void;
|
||||
layers: any;
|
||||
}
|
||||
|
@ -81,6 +83,29 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
|||
iconProps: { iconName: "ZoomIn" },
|
||||
onClick: () => props.handleZoomIn(),
|
||||
},
|
||||
{
|
||||
key: "additionalActions",
|
||||
title: "Additional actions",
|
||||
ariaLabel: "Additional actions",
|
||||
className: "additional-action-dropdown",
|
||||
iconProps: { iconName: "More" },
|
||||
subMenuProps: {
|
||||
items: [
|
||||
{
|
||||
key: "runOcrForCurrentDocument",
|
||||
text: "Run OCR on current document",
|
||||
iconProps: { iconName: "TextDocument" },
|
||||
onClick: () => props.handleRunOcr(),
|
||||
},
|
||||
{
|
||||
key: "runOcrForAllDocuments",
|
||||
text: "Run OCR for all documents",
|
||||
iconProps: { iconName: "Documentation" },
|
||||
onClick: () => props.handleRunOcrForAllDocuments(),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -37,7 +37,6 @@ import { constants } from "../../../../common/constants";
|
|||
import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||
import { Spinner, SpinnerSize } from "@fluentui/react/lib/Spinner";
|
||||
import { getPrimaryGreenTheme, getPrimaryRedTheme } from "../../../../common/themes";
|
||||
import { SkipButton } from "../../shell/skipButton";
|
||||
|
||||
/**
|
||||
* Properties for Editor Page
|
||||
|
@ -221,7 +220,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
theme={getPrimaryGreenTheme()}
|
||||
className="editor-page-sidebar-run-ocr"
|
||||
type="button"
|
||||
onClick={this.loadAllOCRs}
|
||||
onClick={() => this.loadOcrForNotVisited()}
|
||||
disabled={this.state.isRunningOCRs}>
|
||||
{this.state.isRunningOCRs ?
|
||||
<div>
|
||||
|
@ -231,7 +230,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
ariaLive="off"
|
||||
labelPosition="right"
|
||||
/>
|
||||
</div> : "Run OCR on all files"
|
||||
</div> : "Run OCR on unvisited documents"
|
||||
}
|
||||
</PrimaryButton>
|
||||
</div>}
|
||||
|
@ -269,7 +268,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
lockedTags={this.state.lockedTags}
|
||||
hoveredLabel={this.state.hoveredLabel}
|
||||
setTableToView={this.setTableToView}
|
||||
closeTableView={this.closeTableView}>
|
||||
closeTableView={this.closeTableView}
|
||||
runOcrForAllDocs={this.loadOcrForNotVisited}>
|
||||
<AssetPreview
|
||||
controlsEnabled={this.state.isValid}
|
||||
onBeforeAssetChanged={this.onBeforeAssetSelected}
|
||||
|
@ -629,11 +629,10 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
});
|
||||
}
|
||||
|
||||
private loadAllOCRs = async () => {
|
||||
public loadOcrForNotVisited = async (runForAll?: boolean) => {
|
||||
if (this.state.isRunningOCRs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { project } = this.props;
|
||||
const ocrService = new OCRService(project);
|
||||
if (this.state.assets) {
|
||||
|
@ -641,14 +640,16 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
try {
|
||||
await throttle(
|
||||
constants.maxConcurrentServiceRequests,
|
||||
this.state.assets.filter((asset) => asset.state === AssetState.NotVisited).map((asset) => asset.id),
|
||||
this.state.assets
|
||||
.filter((asset) => runForAll ? asset : asset.state === AssetState.NotVisited)
|
||||
.map((asset) => asset.id),
|
||||
async (assetId) => {
|
||||
// Get the latest version of asset.
|
||||
const asset = this.state.assets.find((asset) => asset.id === assetId);
|
||||
if (asset && asset.state === AssetState.NotVisited) {
|
||||
if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
|
||||
try {
|
||||
this.updateAssetState(asset.id, true);
|
||||
await ocrService.getRecognizedText(asset.path, asset.name);
|
||||
await ocrService.getRecognizedText(asset.path, asset.name, undefined, runForAll);
|
||||
this.updateAssetState(asset.id, false, AssetState.Visited);
|
||||
} catch (err) {
|
||||
this.updateAssetState(asset.id, false);
|
||||
|
@ -659,7 +660,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
|||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
this.setState({ isRunningOCRs: false });
|
||||
}
|
||||
|
|
|
@ -54,7 +54,9 @@ export function registerIcons() {
|
|||
MapLayers: "\uE81E",
|
||||
BookAnswers: "\uF8A4",
|
||||
Cancel: "\uE711",
|
||||
|
||||
Refresh: "\uE72C",
|
||||
Documentation: "\uEC17",
|
||||
More: "\uE712",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,7 +32,9 @@ export class OCRService {
|
|||
public async getRecognizedText(
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
onStatusChanged?: (ocrStatus: OcrStatus) => void): Promise<any> {
|
||||
onStatusChanged?: (ocrStatus: OcrStatus) => void,
|
||||
rewrite?: boolean
|
||||
): Promise<any> {
|
||||
Guard.empty(filePath);
|
||||
Guard.empty(this.project.apiUriBase);
|
||||
|
||||
|
@ -43,7 +45,7 @@ export class OCRService {
|
|||
try {
|
||||
notifyStatusChanged(OcrStatus.loadingFromAzureBlob);
|
||||
ocrJson = await this.readOcrFile(ocrFileName);
|
||||
if (!this.isValidOcrFormat(ocrJson)) {
|
||||
if (!this.isValidOcrFormat(ocrJson) || rewrite) {
|
||||
ocrJson = await this.fetchOcrUriResult(filePath, ocrFileName);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -52,7 +54,6 @@ export class OCRService {
|
|||
} finally {
|
||||
notifyStatusChanged(OcrStatus.done);
|
||||
}
|
||||
|
||||
return ocrJson;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче