Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLAHE to opencv image tool #8730

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- Added contrast limited adaptive histogram equalization tool
(<https://github.com/cvat-ai/cvat/pull/8730>)
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import React from 'react';
import { connect } from 'react-redux';
import { Row, Col } from 'antd/lib/grid';
import Popover from 'antd/lib/popover';
import Icon, { AreaChartOutlined, ScissorOutlined } from '@ant-design/icons';
import Icon, { AreaChartOutlined, BarChartOutlined, ScissorOutlined } from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
import Tabs from 'antd/lib/tabs';
import Button from 'antd/lib/button';
import InputNumber from 'antd/lib/input-number';
import Progress from 'antd/lib/progress';
import Select from 'antd/lib/select';
import notification from 'antd/lib/notification';
Expand Down Expand Up @@ -38,7 +39,7 @@ import ApproximationAccuracy, {
} from 'components/annotation-page/standard-workspace/controls-side-bar/approximation-accuracy';
import { OpenCVTracker, TrackerModel } from 'utils/opencv-wrapper/opencv-interfaces';
import { enableImageFilter as enableImageFilterAction, disableImageFilter as disableImageFilterAction, switchToolsBlockerState } from 'actions/settings-actions';
import { ImageFilter, ImageFilterAlias, hasFilter } from 'utils/image-processing';
import { ImageFilter, ImageFilterAlias, ImageFilterSettings } from 'utils/image-processing';
import withVisibilityHandling from './handle-popover-visibility';

interface Props {
Expand Down Expand Up @@ -84,6 +85,7 @@ interface State {
activeTracker: OpenCVTracker | null;
trackers: OpenCVTracker[];
lastTrackedFrame: number | null;
imageFilterSettings: ImageFilterSettings;
}

const core = getCore();
Expand Down Expand Up @@ -156,6 +158,7 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps
trackers: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking) : [],
activeTracker: openCVWrapper.isInitialized ? Object.values(openCVWrapper.tracking)[0] : null,
lastTrackedFrame: null,
imageFilterSettings: new ImageFilterSettings(),
};
}

Expand Down Expand Up @@ -579,35 +582,123 @@ class OpenCVControlComponent extends React.PureComponent<Props & DispatchToProps
);
}

private renderImageContent():JSX.Element {
private renderImageContent(): JSX.Element {
const { enableImageFilter, disableImageFilter, filters } = this.props;
return (
<Row justify='start'>
<Col>
<CVATTooltip title='Histogram equalization' className='cvat-opencv-image-tool'>
<Button
className={
hasFilter(filters, ImageFilterAlias.HISTOGRAM_EQUALIZATION) ?
'cvat-opencv-histogram-tool-button cvat-opencv-image-tool-active' : 'cvat-opencv-histogram-tool-button'
}
onClick={(e: React.MouseEvent<HTMLElement>) => {
if (!hasFilter(filters, ImageFilterAlias.HISTOGRAM_EQUALIZATION)) {
const { imageFilterSettings } = this.state;

const claheContent = (
<div>
<Row align='middle' gutter={8}>
<Col span={12}>Clip Limit:</Col>
<Col span={12}>
<InputNumber
min={1}
max={255}
step={1}
value={imageFilterSettings.claheClipLimit}
onChange={(value) => {
if (value !== null) {
imageFilterSettings.claheClipLimit = value;
this.setState({
imageFilterSettings,
});
enableImageFilter({
modifier: openCVWrapper.imgproc.hist(),
alias: ImageFilterAlias.HISTOGRAM_EQUALIZATION,
modifier: openCVWrapper.imgproc.clahe(),
alias: ImageFilterAlias.CLAHE,
}, imageFilterSettings);
}
}}
/>
</Col>
</Row>
<Row align='middle' gutter={8}>
<Col span={12}>Tile Columns:</Col>
<Col span={12}>
<InputNumber
min={1}
max={128}
value={imageFilterSettings.claheTileGridSize.columns}
onChange={(value) => {
if (value !== null) {
imageFilterSettings.claheTileGridSize.columns = value;
this.setState({
imageFilterSettings,
});
} else {
const button = e.target as HTMLElement;
button.blur();
disableImageFilter(ImageFilterAlias.HISTOGRAM_EQUALIZATION);
enableImageFilter({
modifier: openCVWrapper.imgproc.clahe(),
alias: ImageFilterAlias.CLAHE,
}, imageFilterSettings);
}
}}
>
<AreaChartOutlined />
</Button>
</CVATTooltip>
</Col>
</Row>
/>
</Col>
</Row>
<Row align='middle' gutter={8}>
<Col span={12}>Tile Rows:</Col>
<Col span={12}>
<InputNumber
min={1}
max={128}
value={imageFilterSettings.claheTileGridSize.rows}
onChange={(value) => {
if (value !== null) {
imageFilterSettings.claheTileGridSize.rows = value;
this.setState({
imageFilterSettings,
});
enableImageFilter({
modifier: openCVWrapper.imgproc.clahe(),
alias: ImageFilterAlias.CLAHE,
}, imageFilterSettings);
}
}}
/>
</Col>
</Row>
</div>
);

return (
<Tabs
defaultActiveKey='none'
onChange={(key) => {
filters.values().forEach((filter: ImageFilter) => {
if (key !== filter.alias) {
disableImageFilter(filter.alias);
}
});
if (key === 'none') {
filters.values().forEach((filter: ImageFilter) => disableImageFilter(filter.alias));
} else if (key === ImageFilterAlias.HISTOGRAM_EQUALIZATION) {
enableImageFilter({
modifier: openCVWrapper.imgproc.hist(),
alias: ImageFilterAlias.HISTOGRAM_EQUALIZATION,
}, imageFilterSettings);
} else if (key === ImageFilterAlias.CLAHE) {
enableImageFilter({
modifier: openCVWrapper.imgproc.clahe(),
alias: ImageFilterAlias.CLAHE,
}, imageFilterSettings);
}
}}
items={[
{
label: 'None',
key: 'none',
},
{
label: 'Histogram Eq',
key: ImageFilterAlias.HISTOGRAM_EQUALIZATION,
icon: <AreaChartOutlined />,
},
{
label: 'CLAHE',
key: ImageFilterAlias.CLAHE,
icon: <BarChartOutlined />,
children: claheContent,
},
]}
/>
);
}

Expand Down
3 changes: 2 additions & 1 deletion cvat-ui/src/reducers/settings-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
const { alias } = filter;
const filters = [...state.imageFilters];
const index = filters.findIndex((imageFilter) => imageFilter.alias === alias);
if (options && index !== -1) {
if (index !== -1) {
const enabledFilter = filters[index];
enabledFilter.modifier.currentProcessedImage = null;
enabledFilter.modifier.configure(options);
Expand All @@ -407,6 +407,7 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
imageFilters: filters,
};
}
filter.modifier.configure(options);
return {
...state,
imageFilters: [
Expand Down
16 changes: 14 additions & 2 deletions cvat-ui/src/utils/image-processing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface ImageProcessing {
currentProcessedImage: number | null;

processImage: (src: ImageData, frameNumber: number) => ImageData;
configure: (options: object) => void;
configure: (options: ImageFilterSettings) => void;
}

/* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
Expand All @@ -22,7 +22,7 @@ export class BaseImageFilter implements ImageProcessing {
throw new Error('Process image is not implemented');
}

configure(_options: object): void {}
configure(_options: ImageFilterSettings): void {}
}

export interface ImageFilter {
Expand All @@ -32,6 +32,7 @@ export interface ImageFilter {

export enum ImageFilterAlias {
HISTOGRAM_EQUALIZATION = 'opencv.histogramEqualizaton',
CLAHE = 'opencv.CLAHE',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Based on the search results, I can now provide a final response:

The CLAHE implementation is properly integrated but lacks test coverage

The verification shows:

  • CLAHE is correctly implemented in cvat-ui/src/utils/opencv-wrapper/clahe.ts
  • It's properly integrated in the UI controls (opencv-control.tsx)
  • The enum is used consistently with the implementation
  • However, no test files were found for this feature

Action items:

  • Add unit tests for the CLAHE implementation in cvat-ui/src/utils/opencv-wrapper/clahe.ts
  • Add integration tests for the OpenCV controls component
  • Consider adding visual regression tests for the CLAHE filter effects
🔗 Analysis chain

LGTM! Verify consistent usage across the codebase.

The new CLAHE enum value follows the established naming and value format conventions.

Let's verify the consistent usage of this new filter alias:


Let me gather more information about the CLAHE implementation to verify its usage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of the CLAHE filter alias
# Expected: Should find references in OpenCV control component and tests

# Search for CLAHE usage
rg -l "opencv\.CLAHE"

# Search for potential test files
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts

Length of output: 113


Script:

#!/bin/bash
# Search for CLAHE-related code in the codebase
rg -g '!*.test.*' -g '!*.spec.*' "CLAHE" -A 5 -B 5

# Look for image processing related components
fd -e tsx -e ts | rg -i "image|filter|opencv"

# Check for any test files containing image processing related terms
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts | rg -i "image|filter|opencv"

Length of output: 19892

GAMMA_CORRECTION = 'fabric.gammaCorrection',
}

Expand All @@ -42,3 +43,14 @@ export function hasFilter(filters: ImageFilter[], alias: ImageFilterAlias): Imag
}
return null;
}

export class ImageFilterSettings {
public claheClipLimit: number;
public claheTileGridSize: { rows: number, columns: number };

constructor() {
// Default values, same as default values in OpenCV
this.claheClipLimit = 40.0;
this.claheTileGridSize = { rows: 8, columns: 8 };
}
}
63 changes: 0 additions & 63 deletions cvat-ui/src/utils/opencv-wrapper/histogram-equalization.ts

This file was deleted.

Loading