Add thresholding

This commit is contained in:
2026-09-29 12:35:26 +02:00
parent 2d417990ec
commit b8544b8f46
11 changed files with 647 additions and 2 deletions

View File

@@ -17,4 +17,5 @@ public interface INavigator {
void showComplexMorphology();
void showLinearOutline();
void showNonLinearOutline();
void showThresholding();
}

View File

@@ -13,6 +13,7 @@ import ui.implementation.views.morphology.BasicMorphologyView;
import ui.implementation.views.morphology.ComplexMorphologyView;
import ui.implementation.views.outline.LinearOutlineView;
import ui.implementation.views.outline.NonLinearOutlineView;
import ui.implementation.views.threshold.ThresholdingView;
/**
* Concrete implement of the INavigator interface.
@@ -29,6 +30,7 @@ public class SwingNavigator implements INavigator {
private final Provider<ComplexMorphologyView> complexMorphologyProvider;
private final Provider<LinearOutlineView> linearOutlineProvider;
private final Provider<NonLinearOutlineView> nonLinearOutlineProvider;
private final Provider<ThresholdingView> thresholdingProvider;
@Inject
public SwingNavigator(
@@ -39,7 +41,8 @@ public class SwingNavigator implements INavigator {
Provider<BasicMorphologyView> basicMorphologyProvider,
Provider<ComplexMorphologyView> complexMorphologyProvider,
Provider<LinearOutlineView> linearOutlineProvider,
Provider<NonLinearOutlineView> nonLinearOutlineProvider
Provider<NonLinearOutlineView> nonLinearOutlineProvider,
Provider<ThresholdingView> thresholdingProvider
) {
this.doubleMatrixProvider = doubleMatrixProvider;
this.filteringProvider = filteringProvider;
@@ -49,6 +52,7 @@ public class SwingNavigator implements INavigator {
this.complexMorphologyProvider = complexMorphologyProvider;
this.linearOutlineProvider = linearOutlineProvider;
this.nonLinearOutlineProvider = nonLinearOutlineProvider;
this.thresholdingProvider = thresholdingProvider;
}
@Override
@@ -99,6 +103,12 @@ public class SwingNavigator implements INavigator {
runView(view);
}
@Override
public void showThresholding() {
ThresholdingView view = thresholdingProvider.get();
runView(view);
}
private void runView(AbstractView view) {
ViewInitializer.Initialize(view);
view.setVisible(true);

View File

@@ -0,0 +1,65 @@
package app.state;
import com.google.inject.Inject;
import domain.image.Image;
import presenters.threshold.ThresholdingListener;
import java.util.ArrayList;
import java.util.List;
public class ThresholdingState extends State {
private Image simpleThreshold;
private Image doubleThreshold;
private Image automaticThreshold;
private final List<ThresholdingListener> listeners = new ArrayList<>();
@Inject
public ThresholdingState() {
}
public void subscribe(ThresholdingListener listener) {
listeners.add(listener);
}
public void unsubscribe(ThresholdingListener listener) {
listeners.remove(listener);
}
public void setSimpleThreshold(Image image) {
simpleThreshold = image;
listeners.forEach(listener ->
listener.onSimpleThresholdChanged(image)
);
}
public void setDoubleThreshold(Image image) {
doubleThreshold = image;
listeners.forEach(listener ->
listener.onDoubleThresholdChanged(image)
);
}
public void setAutomaticThreshold(Image image) {
automaticThreshold = image;
listeners.forEach(listener ->
listener.onAutomaticThresholdChanged(image)
);
}
public Image getSimpleThreshold() {
return simpleThreshold;
}
public Image getDoubleThreshold() {
return doubleThreshold;
}
public Image getAutomaticThreshold() {
return automaticThreshold;
}
}

View File

@@ -0,0 +1,116 @@
package domain.image.processing.threshold;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
public class Thresholding {
public static GrayScaleMatrix simpleThreshold(
Image image,
int threshold
) {
GrayScaleMatrix gray = image.toGrayScale();
int height = gray.getHeight();
int width = gray.getWidth();
double[][] data = gray.getRawData();
double[][] result = new double[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
result[y][x] = data[y][x] >= threshold
? 255
: 0;
}
}
return new GrayScaleMatrix(result);
}
public static GrayScaleMatrix doubleThreshold(
Image image,
int threshold1,
int threshold2
) {
if (threshold1 > threshold2) {
int temp = threshold1;
threshold1 = threshold2;
threshold2 = temp;
}
GrayScaleMatrix gray = image.toGrayScale();
int height = gray.getHeight();
int width = gray.getWidth();
double[][] data = gray.getRawData();
double[][] result = new double[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double value = data[y][x];
if (value < threshold1) {
result[y][x] = 0;
} else if (value < threshold2) {
result[y][x] = 127;
} else {
result[y][x] = 255;
}
}
}
return new GrayScaleMatrix(result);
}
public static GrayScaleMatrix automaticThreshold(Image image) {
GrayScaleMatrix gray = image.toGrayScale();
double[][] data = gray.getRawData();
double threshold = (gray.getMinValue() + gray.getMaxValue()) / 2.0;
while (true) {
double lowerSum = 0;
double upperSum = 0;
int lowerCount = 0;
int upperCount = 0;
for (int y = 0; y < gray.getHeight(); y++) {
for (int x = 0; x < gray.getWidth(); x++) {
double value = data[y][x];
if (value <= threshold) {
lowerSum += value;
lowerCount++;
} else {
upperSum += value;
upperCount++;
}
}
}
if (lowerCount == 0 || upperCount == 0) {
break;
}
double lowerMean = lowerSum / lowerCount;
double upperMean = upperSum / upperCount;
double newThreshold = (lowerMean + upperMean) / 2.0;
if (Math.abs(newThreshold - threshold) < 0.5) {
threshold = newThreshold;
break;
}
threshold = newThreshold;
}
return simpleThreshold(image, (int) threshold);
}
}

View File

@@ -58,6 +58,8 @@ public class NavPresenter {
public void goToNonLinearOutline() { navigator.showNonLinearOutline(); }
public void goToThresholding() { navigator.showThresholding(); }
public GrayScaleMatrix getImageGrayMatrix() { return appState.getImage().toGrayScale(); }
public void setMode(Mode mode) { modeService.setMode(mode); }

View File

@@ -0,0 +1,12 @@
package presenters.threshold;
import domain.image.Image;
public interface ThresholdingListener {
void onSimpleThresholdChanged(Image image);
void onDoubleThresholdChanged(Image image);
void onAutomaticThresholdChanged(Image image);
}

View File

@@ -0,0 +1,94 @@
package presenters.threshold;
import app.state.AppState;
import app.state.ThresholdingState;
import domain.image.Image;
import jakarta.inject.Inject;
import services.ThresholdingService;
import ui.abstractions.IThresholdingView;
public class ThresholdingPresenter implements ThresholdingListener {
private final AppState appState;
private final ThresholdingState thresholdingState;
private final ThresholdingService thresholdingService;
private IThresholdingView view;
@Inject
public ThresholdingPresenter(
AppState appState,
ThresholdingState thresholdingState,
ThresholdingService thresholdingService
) {
this.appState = appState;
this.thresholdingState = thresholdingState;
this.thresholdingService = thresholdingService;
thresholdingState.subscribe(this);
}
public void setView(IThresholdingView view) {
this.view = view;
}
public void dispose() {
view = null;
thresholdingState.unsubscribe(this);
}
public void computeSimpleThreshold(int threshold) {
Image image = appState.getImage();
Image result = thresholdingService.simpleThreshold(
image,
threshold
);
thresholdingState.setSimpleThreshold(result);
}
public void computeDoubleThreshold(
int threshold1,
int threshold2
) {
Image image = appState.getImage();
Image result = thresholdingService.doubleThreshold(
image,
threshold1,
threshold2
);
thresholdingState.setDoubleThreshold(result);
}
public void computeAutomaticThreshold() {
Image image = appState.getImage();
Image result = thresholdingService.automaticThreshold(image);
thresholdingState.setAutomaticThreshold(result);
}
@Override
public void onSimpleThresholdChanged(Image image) {
if (view != null) {
view.updateSimpleThreshold(image);
}
}
@Override
public void onDoubleThresholdChanged(Image image) {
if (view != null) {
view.updateDoubleThreshold(image);
}
}
@Override
public void onAutomaticThresholdChanged(Image image) {
if (view != null) {
view.updateAutomaticThreshold(image);
}
}
}

View File

@@ -0,0 +1,29 @@
package services;
import domain.image.Image;
import domain.image.processing.threshold.Thresholding;
public class ThresholdingService {
public Image simpleThreshold(Image image, int threshold) {
return Thresholding
.simpleThreshold(image, threshold)
.toImage();
}
public Image doubleThreshold(
Image image,
int threshold1,
int threshold2
) {
return Thresholding
.doubleThreshold(image, threshold1, threshold2)
.toImage();
}
public Image automaticThreshold(Image image) {
return Thresholding
.automaticThreshold(image)
.toImage();
}
}

View File

@@ -0,0 +1,12 @@
package ui.abstractions;
import domain.image.Image;
public interface IThresholdingView {
void updateSimpleThreshold(Image image);
void updateDoubleThreshold(Image image);
void updateAutomaticThreshold(Image image);
}

View File

@@ -33,9 +33,10 @@ public class NavBar extends JMenuBar implements INavBar {
private JMenu drawingMenu;
private JMenu fourierMenu;
private JMenu linearMenu;
private JMenu histogramMenu;
private JMenu morphologyMenu;
private Menu outlineMenu;
private Menu thresholdingMenu;
private JMenu histogramMenu;
private NavPresenter presenter;
@@ -103,6 +104,10 @@ public class NavBar extends JMenuBar implements INavBar {
new MenuItem("Non-linéaire", this::displayNonLinearOutline)
);
thresholdingMenu = new Menu("Seuillage","/cp_51_p1.jpg",
new MenuItem("Afficher", this::displayThresholding)
);
histogramMenu = new Menu("Histogramme","/report_48_hot.jpg",
new MenuItem("Afficher", "/report_32_hot.jpg",this::displayHistogram)
);
@@ -113,6 +118,7 @@ public class NavBar extends JMenuBar implements INavBar {
this.add(linearMenu);
this.add(morphologyMenu);
this.add(outlineMenu);
this.add(thresholdingMenu);
this.add(histogramMenu);
drawingMenu.setEnabled(false);
@@ -120,9 +126,12 @@ public class NavBar extends JMenuBar implements INavBar {
linearMenu.setEnabled(false);
morphologyMenu.setEnabled(false);
outlineMenu.setEnabled(false);
thresholdingMenu.setEnabled(false);
histogramMenu.setEnabled(false);
}
//####################################################
private File chooseFile(){
@@ -190,6 +199,7 @@ public class NavBar extends JMenuBar implements INavBar {
linearMenu.setEnabled(true);
morphologyMenu.setEnabled(true);
outlineMenu.setEnabled(true);
thresholdingMenu.setEnabled(true);
histogramMenu.setEnabled(true);
}
@@ -200,6 +210,7 @@ public class NavBar extends JMenuBar implements INavBar {
linearMenu.setEnabled(false);
morphologyMenu.setEnabled(false);
outlineMenu.setEnabled(false);
thresholdingMenu.setEnabled(false);
histogramMenu.setEnabled(false);
}
@@ -261,6 +272,7 @@ public class NavBar extends JMenuBar implements INavBar {
private void displayNonLinearOutline(ActionEvent actionEvent) { presenter.goToNonLinearOutline();}
private void displayThresholding(ActionEvent actionEvent) { presenter.goToThresholding(); }
private void displayErrorMessage(String title, String message) {
JOptionPane.showMessageDialog(

View File

@@ -0,0 +1,292 @@
package ui.implementation.views.threshold;
import domain.image.Image;
import jakarta.inject.Inject;
import presenters.threshold.ThresholdingPresenter;
import ui.abstractions.AbstractView;
import ui.abstractions.IThresholdingView;
import ui.implementation.components.controls.SaveButton;
import ui.implementation.components.image.TabbedImagePanel;
import javax.swing.*;
public class ThresholdingView extends AbstractView implements IThresholdingView {
private static final String SIMPLE_TAB_TITLE = "Simple";
private static final String DOUBLE_TAB_TITLE = "Double";
private static final String AUTOMATIC_TAB_TITLE = "Automatique";
private final ThresholdingPresenter presenter;
private TabbedImagePanel modeTab;
private JSlider thresholdSlider;
private JSlider threshold1Slider;
private JSlider threshold2Slider;
private JLabel thresholdValueLabel;
private JLabel threshold1ValueLabel;
private JLabel threshold2ValueLabel;
@Inject
public ThresholdingView(ThresholdingPresenter presenter) {
this.presenter = presenter;
}
@Override
public void initComponents() {
modeTab = new TabbedImagePanel();
modeTab.addPreviewTab(SIMPLE_TAB_TITLE);
modeTab.addPreviewTab(DOUBLE_TAB_TITLE);
modeTab.addPreviewTab(AUTOMATIC_TAB_TITLE);
thresholdSlider = createThresholdSlider(128);
threshold1Slider = createThresholdSlider(85);
threshold2Slider = createThresholdSlider(170);
thresholdValueLabel = new JLabel();
threshold1ValueLabel = new JLabel();
threshold2ValueLabel = new JLabel();
updateValueLabels();
thresholdSlider.addChangeListener(e -> {
updateValueLabels();
presenter.computeSimpleThreshold(
thresholdSlider.getValue()
);
});
threshold1Slider.addChangeListener(e -> {
if (threshold1Slider.getValue() > threshold2Slider.getValue()) {
threshold2Slider.setValue(threshold1Slider.getValue());
}
updateValueLabels();
presenter.computeDoubleThreshold(
threshold1Slider.getValue(),
threshold2Slider.getValue()
);
});
threshold2Slider.addChangeListener(e -> {
if (threshold2Slider.getValue() < threshold1Slider.getValue()) {
threshold1Slider.setValue(threshold2Slider.getValue());
}
updateValueLabels();
presenter.computeDoubleThreshold(
threshold1Slider.getValue(),
threshold2Slider.getValue()
);
});
JButton saveButton = new SaveButton();
JPanel controlPanel = createControlPanel();
JPanel rightPanel = new JPanel();
GroupLayout rightLayout = new GroupLayout(rightPanel);
rightPanel.setLayout(rightLayout);
rightLayout.setAutoCreateGaps(true);
rightLayout.setAutoCreateContainerGaps(true);
rightLayout.setHorizontalGroup(
rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(controlPanel)
.addComponent(saveButton)
);
rightLayout.setVerticalGroup(
rightLayout.createSequentialGroup()
.addComponent(controlPanel)
.addComponent(saveButton)
);
JPanel mainPanel = new JPanel();
GroupLayout mainLayout = new GroupLayout(mainPanel);
mainPanel.setLayout(mainLayout);
mainLayout.setAutoCreateGaps(true);
mainLayout.setAutoCreateContainerGaps(true);
mainLayout.setHorizontalGroup(
mainLayout.createSequentialGroup()
.addComponent(modeTab)
.addComponent(rightPanel)
);
mainLayout.setVerticalGroup(
mainLayout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(modeTab)
.addComponent(rightPanel)
);
setContentPane(mainPanel);
setTitle("Seuillage");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
pack();
}
private JSlider createThresholdSlider(int value) {
JSlider slider = new JSlider(
SwingConstants.VERTICAL,
0,
255,
value
);
slider.setMajorTickSpacing(50);
slider.setMinorTickSpacing(10);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.setLabelTable(slider.createStandardLabels(50));
return slider;
}
private JPanel createControlPanel() {
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
JPanel simplePanel = createSliderPanel(
thresholdSlider,
thresholdValueLabel
);
JPanel double1Panel = createSliderPanel(
threshold1Slider,
threshold1ValueLabel
);
JPanel double2Panel = createSliderPanel(
threshold2Slider,
threshold2ValueLabel
);
layout.setHorizontalGroup(
layout.createSequentialGroup()
.addComponent(simplePanel)
.addComponent(double1Panel)
.addComponent(double2Panel)
);
layout.setVerticalGroup(
layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(simplePanel)
.addComponent(double1Panel)
.addComponent(double2Panel)
);
return panel;
}
private JPanel createSliderPanel(JSlider slider, JLabel valueLabel) {
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(slider)
.addComponent(valueLabel)
);
layout.setVerticalGroup(
layout.createSequentialGroup()
.addComponent(slider)
.addComponent(valueLabel)
);
return panel;
}
private void updateValueLabels() {
thresholdValueLabel.setText(
String.valueOf(thresholdSlider.getValue())
);
threshold1ValueLabel.setText(
String.valueOf(threshold1Slider.getValue())
);
threshold2ValueLabel.setText(
String.valueOf(threshold2Slider.getValue())
);
}
@Override
public void initValues() {
modeTab.addChangeListener(e -> {
updateControls();
applyThreshold();
});
presenter.setView(this);
updateControls();
applyThreshold();
}
private void updateControls() {
int operation = modeTab.getSelectedIndex();
thresholdSlider.setVisible(operation == 0);
threshold1Slider.setVisible(operation == 1);
threshold2Slider.setVisible(operation == 1);
thresholdValueLabel.setVisible(operation == 0);
threshold1ValueLabel.setVisible(operation == 1);
threshold2ValueLabel.setVisible(operation == 1);
}
private void applyThreshold() {
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeSimpleThreshold(
thresholdSlider.getValue()
);
case 1 -> presenter.computeDoubleThreshold(
threshold1Slider.getValue(),
threshold2Slider.getValue()
);
case 2 -> presenter.computeAutomaticThreshold();
}
}
@Override
public void updateSimpleThreshold(Image image) {
modeTab.loadImage(SIMPLE_TAB_TITLE, image);
}
@Override
public void updateDoubleThreshold(Image image) {
modeTab.loadImage(DOUBLE_TAB_TITLE, image);
}
@Override
public void updateAutomaticThreshold(Image image) {
modeTab.loadImage(AUTOMATIC_TAB_TITLE, image);
}
@Override
public void disposeCore() {
presenter.setView(null);
presenter.dispose();
}
}