Farmbot-Web-App/frontend/farmware/weed_detector/image_workspace.tsx

174 lines
5.3 KiB
TypeScript

import * as React from "react";
import { FarmbotColorPicker } from "./farmbot_picker";
import { BlurableInput, Row, Col } from "../../ui/index";
import { ImageFlipper } from "../images/image_flipper";
import { HSV } from "./interfaces";
import { WeedDetectorSlider } from "./slider";
import { TaggedImage } from "farmbot";
import { PhotoFooter } from "../images/photos";
import { parseIntInput } from "../../util";
import { t } from "../../i18next_wrapper";
import { TimeSettings } from "../../interfaces";
const RANGES = {
H: { LOWEST: 0, HIGHEST: 179 },
S: { LOWEST: 0, HIGHEST: 255 },
V: { LOWEST: 0, HIGHEST: 255 },
BLUR: { LOWEST: 0, HIGHEST: 100 },
MORPH: { LOWEST: 0, HIGHEST: 100 },
ITERATION: { LOWEST: 0, HIGHEST: 100 },
};
/** Number values that the <ImageWorkspace/> panel deals with. */
export interface NumericValues {
iteration: number;
morph: number;
blur: number;
H_LO: number;
H_HI: number;
S_LO: number;
S_HI: number;
V_LO: number;
V_HI: number;
}
type NumericKeyName = keyof NumericValues;
export interface ImageWorkspaceProps extends NumericValues {
onFlip(uuid: string | undefined): void;
onProcessPhoto(image_id: number): void;
currentImage: TaggedImage | undefined;
images: TaggedImage[];
onChange(key: NumericKeyName, value: number): void;
invertHue?: boolean;
botOnline: boolean;
timeSettings: TimeSettings;
}
/** Mapping of HSV values to FBOS Env variables. */
const CHANGE_MAP: Record<HSV, [NumericKeyName, NumericKeyName]> = {
H: ["H_LO", "H_HI"],
S: ["S_LO", "S_HI"],
V: ["V_LO", "V_HI"]
};
export class ImageWorkspace extends React.Component<ImageWorkspaceProps, {}> {
/** Generates a function to handle changes to blur/morph/iteration. */
numericChange = (key: NumericKeyName) =>
(e: React.SyntheticEvent<HTMLInputElement>) => {
this.props.onChange(key, parseIntInput(e.currentTarget.value) || 0);
};
maybeProcessPhoto = () => {
const img = this.props.currentImage || this.props.images[0];
if (img?.body.id) {
this.props.onProcessPhoto(img.body.id);
}
};
/** This will trigger onChange callback only when necessary, at most twice.
* (H|S|L)_HI and (H|S|L)_LO */
onHslChange = (key: keyof typeof CHANGE_MAP) =>
(values: [number, number]) => {
const keys = CHANGE_MAP[key];
[0, 1].map(i => {
if (values[i] !== this.props[keys[i]]) {
this.props.onChange(keys[i], values[i]);
}
});
};
render() {
const { H_LO, H_HI, S_LO, S_HI, V_LO, V_HI } = this.props;
return <div className="widget-content">
<Row>
<Col xs={12} md={6}>
<h4>
<i>{t("Color Range")}</i>
</h4>
<label htmlFor="hue">{t("HUE")}</label>
<WeedDetectorSlider
onRelease={this.onHslChange("H")}
lowest={RANGES.H.LOWEST}
highest={RANGES.H.HIGHEST}
lowValue={Math.min(H_LO, H_HI)}
highValue={Math.max(H_LO, H_HI)} />
<label htmlFor="saturation">{t("SATURATION")}</label>
<WeedDetectorSlider
onRelease={this.onHslChange("S")}
lowest={RANGES.S.LOWEST}
highest={RANGES.S.HIGHEST}
lowValue={S_LO}
highValue={S_HI} />
<label htmlFor="value">{t("VALUE")}</label>
<WeedDetectorSlider
onRelease={this.onHslChange("V")}
lowest={RANGES.V.LOWEST}
highest={RANGES.V.HIGHEST}
lowValue={V_LO}
highValue={V_HI} />
</Col>
<Col xs={12} md={6}>
<FarmbotColorPicker
h={[H_LO, H_HI]}
s={[S_LO, S_HI]}
v={[V_LO, V_HI]}
invertHue={this.props.invertHue} />
</Col>
</Row>
<Row>
<Col xs={12}>
<h4>
<i>{t("Processing Parameters")}</i>
</h4>
</Col>
<Col xs={4}>
<label>{t("BLUR")}</label>
<BlurableInput type="number"
min={RANGES.BLUR.LOWEST}
max={RANGES.BLUR.HIGHEST}
onCommit={this.numericChange("blur")}
value={"" + this.props.blur} />
</Col>
<Col xs={4}>
<label>{t("MORPH")}</label>
<BlurableInput type="number"
min={RANGES.MORPH.LOWEST}
max={RANGES.MORPH.HIGHEST}
onCommit={this.numericChange("morph")}
value={"" + this.props.morph} />
</Col>
<Col xs={4}>
<label>{t("ITERATION")}</label>
<BlurableInput type="number"
min={RANGES.ITERATION.LOWEST}
max={RANGES.ITERATION.HIGHEST}
onCommit={this.numericChange("iteration")}
value={"" + this.props.iteration} />
</Col>
</Row>
<Row>
<Col xs={12}>
<button
className="green fb-button"
title={t("Scan this image")}
onClick={this.maybeProcessPhoto}
disabled={!this.props.botOnline || !this.props.images.length}>
{t("Scan image")}
</button>
</Col>
</Row>
<ImageFlipper
onFlip={this.props.onFlip}
images={this.props.images}
currentImage={this.props.currentImage} />
<PhotoFooter
image={this.props.currentImage}
timeSettings={this.props.timeSettings} />
</div>;
}
}