restore cropper functionality

This commit is contained in:
Ali Abid 2021-10-13 21:06:47 +00:00
parent 5ff983f7a2
commit b1ee6f2ae5
8 changed files with 289 additions and 92 deletions

View File

@ -8,15 +8,13 @@ def image_mod(image):
iface = gr.Interface(image_mod,
gr.inputs.Image(type="pil", optional=True),
gr.inputs.Image(type="pil", optional=True, tool="select"),
"image",
examples=[
["images/cheetah1.jpg"],
["images/cheetah2.jpg"],
["images/lion.jpg"],
])
iface.test_launch()
], live=True)
if __name__ == "__main__":
iface.launch()

View File

@ -5005,6 +5005,11 @@
"sha.js": "^2.4.8"
}
},
"cropperjs": {
"version": "1.5.12",
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.12.tgz",
"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -15446,6 +15451,14 @@
"lodash.curry": "^4.1.1"
}
},
"react-cropper": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/react-cropper/-/react-cropper-2.1.8.tgz",
"integrity": "sha512-QEj6CE9et/gMRqpaKMgZQdBgtzLjjq+zj1pmHwtoWG6GqscDl4QpTwoEElWN2pieYxkwFaZa0lPiD2b9nwqLKQ==",
"requires": {
"cropperjs": "^1.5.12"
}
},
"react-dev-utils": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",

View File

@ -20,6 +20,7 @@
"prettier-eslint": "^13.0.0",
"prettier-eslint-cli": "^5.0.1",
"react": "^17.0.2",
"react-cropper": "^2.1.8",
"react-dom": "^17.0.2",
"react-json-tree": "^0.15.0",
"react-plotly.js": "^2.5.1",

View File

@ -61,42 +61,169 @@ import {
} from "./components/output/timeseries";
import { VideoOutput, VideoOutputExample } from "./components/output/video";
let input_component_map = {
audio: [AudioInput, AudioInputExample],
checkboxgroup: [CheckboxGroupInput, CheckboxGroupInputExample],
checkbox: [CheckboxInput, CheckboxInputExample],
dataframe: [DataframeInput, DataframeInputExample],
dropdown: [DropdownInput, DropdownInputExample],
file: [FileInput, FileInputExample],
image: [ImageInput, ImageInputExample],
number: [NumberInput, NumberInputExample],
radio: [RadioInput, RadioInputExample],
slider: [SliderInput, SliderInputExample],
textbox: [TextboxInput, TextboxInputExample],
timeseries: [TimeseriesInput, TimeseriesInputExample],
video: [VideoInput, VideoInputExample]
};
let output_component_map = {
audio: [AudioOutput, AudioOutputExample],
carousel: [CarouselOutput, CarouselOutputExample],
dataframe: [DataframeOutput, DataframeOutputExample],
file: [FileOutput, FileOutputExample],
highlightedtext: [HighlightedTextOutput, HighlightedTextOutputExample],
html: [HTMLOutput, HTMLOutputExample],
image: [ImageOutput, ImageOutputExample],
json: [JSONOutput, JSONOutputExample],
keyvalues: [KeyValuesOutput, KeyValuesOutputExample],
label: [LabelOutput, LabelOutputExample],
textbox: [TextboxOutput, TextboxOutputExample],
timeseries: [TimeseriesOutput, TimeseriesOutputExample],
video: [VideoOutput, VideoOutputExample]
};
let input_component_set = [
{
name: "audio",
component: AudioInput,
memoized_component: null,
example_component: AudioInputExample
},
{
name: "checkboxgroup",
component: CheckboxGroupInput,
memoized_component: null,
example_component: CheckboxGroupInputExample
},
{
name: "checkbox",
component: CheckboxInput,
memoized_component: null,
example_component: CheckboxInputExample
},
{
name: "dataframe",
component: DataframeInput,
memoized_component: null,
example_component: DataframeInputExample
},
{
name: "dropdown",
component: DropdownInput,
memoized_component: null,
example_component: DropdownInputExample
},
{
name: "file",
component: FileInput,
memoized_component: null,
example_component: FileInputExample
},
{
name: "image",
component: ImageInput,
memoized_component: null,
example_component: ImageInputExample
},
{
name: "number",
component: NumberInput,
memoized_component: null,
example_component: NumberInputExample
},
{
name: "radio",
component: RadioInput,
memoized_component: null,
example_component: RadioInputExample
},
{
name: "slider",
component: SliderInput,
memoized_component: null,
example_component: SliderInputExample
},
{
name: "textbox",
component: TextboxInput,
memoized_component: null,
example_component: TextboxInputExample
},
{
name: "timeseries",
component: TimeseriesInput,
memoized_component: null,
example_component: TimeseriesInputExample
},
{ name: "video", component: VideoInput, example_component: VideoInputExample }
];
let output_component_set = [
{
name: "audio",
component: AudioOutput,
memoized_component: null,
example_component: AudioOutputExample
},
{
name: "carousel",
component: CarouselOutput,
memoized_component: null,
example_component: CarouselOutputExample
},
{
name: "dataframe",
component: DataframeOutput,
memoized_component: null,
example_component: DataframeOutputExample
},
{
name: "file",
component: FileOutput,
memoized_component: null,
example_component: FileOutputExample
},
{
name: "highlightedtext",
component: HighlightedTextOutput,
memoized_component: null,
example_component: HighlightedTextOutputExample
},
{
name: "html",
component: HTMLOutput,
memoized_component: null,
example_component: HTMLOutputExample
},
{
name: "image",
component: ImageOutput,
memoized_component: null,
example_component: ImageOutputExample
},
{
name: "json",
component: JSONOutput,
memoized_component: null,
example_component: JSONOutputExample
},
{
name: "keyvalues",
component: KeyValuesOutput,
memoized_component: null,
example_component: KeyValuesOutputExample
},
{
name: "label",
component: LabelOutput,
memoized_component: null,
example_component: LabelOutputExample
},
{
name: "textbox",
component: TextboxOutput,
memoized_component: null,
example_component: TextboxOutputExample
},
{
name: "timeseries",
component: TimeseriesOutput,
memoized_component: null,
example_component: TimeseriesOutputExample
},
{
name: "video",
component: VideoOutput,
memoized_component: null,
example_component: VideoOutputExample
}
];
for (let component_map of [input_component_map, output_component_map]) {
for (let [key, components] of Object.entries(component_map)) {
let component = components[0];
component_map[key][0] = React.memo(component, component.memo);
for (let component_set of [input_component_set, output_component_set]) {
for (let component_data of component_set) {
component_data.memoized_component = React.memo(
component_data.component,
component_data.component.memo
);
}
}
export { input_component_map, output_component_map };
export { input_component_set, output_component_set };

View File

@ -4,4 +4,7 @@ export default class BaseComponent extends React.Component {
static memo = (a, b) => {
return a.value === b.value && a.interpretation === b.interpretation;
};
static postprocess = (y) => {
return y;
};
}

View File

@ -6,6 +6,8 @@ import { SketchField, Tools } from "../../vendor/ReactSketch";
import { getObjectFitSize, paintSaliency } from "../../utils";
import "tui-image-editor/dist/tui-image-editor.css";
import ImageEditor from "@toast-ui/react-image-editor";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
class ImageInput extends BaseComponent {
constructor(props) {
@ -26,9 +28,21 @@ class ImageInput extends BaseComponent {
this.webcamRef = React.createRef();
this.sketchRef = React.createRef();
this.editorRef = React.createRef();
this.cropperRef = React.createRef();
this.sketchKey = 0;
this.state = { editorMode: false };
}
static memo = (a, b) => {
if (a.interpretation != b.interpretation) {
return false;
} else if (a.value === null && b.value === null) {
return true;
} else if (a.value === null || b.value === null) {
return false;
} else {
return a.value.src === b.value.src;
}
};
handleChange(data) {
this.props.handleChange(data);
}
@ -37,18 +51,18 @@ class ImageInput extends BaseComponent {
}
snapshot() {
let imageSrc = this.webcamRef.current.getScreenshot();
this.handleChange(imageSrc);
this.handleChange({ src: imageSrc, crop: null });
}
getSketch() {
let imageSrc = this.sketchRef.current.toDataURL();
this.handleChange(imageSrc);
this.handleChange({ src: imageSrc, crop: null });
}
cancelEditor() {
this.setState({ editorMode: false });
}
saveEditor() {
const editorInstance = this.editorRef.current.getInstance();
this.handleChange(editorInstance.toDataURL());
this.handleChange({ src: editorInstance.toDataURL(), crop: null });
this.setState({ editorMode: false });
}
onImgLoad({ target: img }) {
@ -62,6 +76,10 @@ class ImageInput extends BaseComponent {
openEditor() {
this.setState({ editorMode: true });
}
onCrop = () => {
const crop = this.cropperRef.current.cropper.getCroppedCanvas().toDataURL();
this.handleChange({ src: this.props.value.src, crop: crop });
};
render() {
let no_action = (evt) => {
evt.preventDefault();
@ -107,39 +125,53 @@ class ImageInput extends BaseComponent {
return (
<div className="input_image">
<div className="image_preview_holder">
{this.state.editorMode ? (
<div className="image_editor">
<div className="image_editor_buttons">
<button onClick={this.saveEditor}>Save</button>
<button onClick={this.cancelEditor}>Cancel</button>
{this.props.tool === "editor" ? (
this.state.editorMode ? (
<div className="image_editor">
<div className="image_editor_buttons">
<button onClick={this.saveEditor}>Save</button>
<button onClick={this.cancelEditor}>Cancel</button>
</div>
<ImageEditor
ref={this.editorRef}
includeUI={{
loadImage: { path: this.props.value.src, name: "value" },
uiSize: {
width: "800px",
height: "600px"
},
menuBarPosition: "left"
}}
cssMaxHeight={500}
cssMaxWidth={700}
usageStatistics={false}
/>
</div>
<ImageEditor
ref={this.editorRef}
includeUI={{
loadImage: { path: this.props.value, name: "value" },
uiSize: {
width: "800px",
height: "600px"
},
menuBarPosition: "left"
}}
cssMaxHeight={500}
cssMaxWidth={700}
usageStatistics={false}
/>
</div>
) : (
<button className="edit_button" onClick={this.openEditor}>
Edit
</button>
)
) : (
<button className="edit_button" onClick={this.openEditor}>
Edit
</button>
false
)}
{this.props.tool === "select" ? (
<Cropper
src={this.props.value.src}
style={{ height: "100%", width: "100%" }}
ref={this.cropperRef}
crop={this.onCrop}
autoCropArea={1}
/>
) : (
<img
ref={this.imgRef}
onLoad={this.onImgLoad}
className="image_preview"
alt=""
src={this.props.value.src}
/>
)}
<img
ref={this.imgRef}
onLoad={this.onImgLoad}
className="image_preview"
alt=""
src={this.props.value}
/>
</div>
{interpretation}
</div>
@ -229,12 +261,19 @@ class ImageInput extends BaseComponent {
var ReaderObj = new FileReader();
ReaderObj.readAsDataURL(files[0]);
ReaderObj.onloadend = function () {
component.props.handleChange(this.result);
component.props.handleChange({ src: this.result, crop: null });
};
}
static postprocess = (y) => {
return y.crop === null ? y.src : y.crop;
};
}
class ImageInputExample extends DataURLComponentExample {
static async preprocess(x, examples_dir) {
let src = await DataURLComponentExample.preprocess(x, examples_dir);
return { src: src, crop: null };
}
render() {
return (
<img

View File

@ -1,7 +1,7 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import { output_component_map } from "../../components";
import { output_component_set } from "../../components";
import arrowRight from "../../static/img/arrow-right.svg";
import arrowLeft from "../../static/img/arrow-left.svg";
@ -36,7 +36,9 @@ class CarouselOutput extends BaseComponent {
return (
<div className="output_carousel">
{this.props.components.map((config, index) => {
let Component = output_component_map[config.name][0];
let Component = output_component_set.find(
(c) => c.name === config.name
).memoized_component;
return (
<div className="component" key={index}>
{config.label ? (

View File

@ -1,6 +1,6 @@
import React from "react";
import html2canvas from "html2canvas-objectfit-fix";
import { input_component_map, output_component_map } from "./components";
import { input_component_set, output_component_set } from "./components";
import { saveAs } from "./utils";
import ReactDOM from "react-dom";
import classNames from "classnames";
@ -104,14 +104,17 @@ export class GradioInterface extends React.Component {
}
this.pending_response = true;
let input_state = [];
for (let i = 0; i < this.props.input_components.length; i++) {
for (let [i, input_component] of this.props.input_components.entries()) {
if (
this.state[i] === null &&
this.props.input_components[i].optional !== true
) {
return;
}
input_state[i] = this.state[i];
let InputComponentClass = input_component_set.find(
(c) => c.name === input_component.name
).component;
input_state[i] = InputComponentClass.postprocess(this.state[i]);
}
this.setState({
submitting: true,
@ -237,14 +240,22 @@ export class GradioInterface extends React.Component {
handleExampleChange = (example_id) => {
this.setState({ example_id: example_id });
for (let [i, item] of this.props.examples[example_id].entries()) {
let ExampleComponent =
i < this.props.input_components.length
? input_component_map[this.props.input_components[i].name][1]
: output_component_map[
this.props.output_components[
i - this.props.input_components.length
].name
][1];
let ExampleComponent;
if (i < this.props.input_components.length) {
let component_name = this.props.input_components[i].name;
let component_data = input_component_set.find(
(c) => c.name === component_name
);
ExampleComponent = component_data.example_component;
} else {
let component_name =
this.props.output_components[i - this.props.input_components.length]
.name;
let component_data = output_component_set.find(
(c) => c.name === component_name
);
ExampleComponent = component_data.example_component;
}
ExampleComponent.preprocess(item, this.examples_dir).then((data) => {
this.handleChange(i, data);
});
@ -290,7 +301,9 @@ export class GradioInterface extends React.Component {
>
<div className="component_set">
{this.props.input_components.map((component, index) => {
const Component = input_component_map[component.name][0];
const Component = input_component_set.find(
(c) => c.name === component.name
).memoized_component;
return (
<div className="component" key={index}>
<div className="panel_header">{component.label}</div>
@ -335,7 +348,9 @@ export class GradioInterface extends React.Component {
>
{status}
{this.props.output_components.map((component, index) => {
const Component = output_component_map[component.name][0];
const Component = output_component_set.find(
(c) => c.name === component.name
).memoized_component;
const key = this.props.input_components.length + index;
return this.state[key] === null ? (
false
@ -499,10 +514,9 @@ class GradioInterfaceExamples extends React.Component {
onClick={() => this.props.handleExampleChange(i)}
>
{example_row.map((example_data, j) => {
let ExampleComponent =
input_component_map[
this.props.input_components[j].name
][1];
let ExampleComponent = input_component_set.find(
(c) => c.name === this.props.input_components[j].name
).example_component;
return (
<td>
<ExampleComponent