Fix complex morphology

This commit is contained in:
2026-09-29 03:53:27 +02:00
parent 9fcc48ecf5
commit ee7c1cab2f
17 changed files with 543 additions and 111 deletions

View File

@@ -1,7 +1,6 @@
package app; package app;
import app.state.AppState; import app.state.*;
import app.state.DoubleMatrixState;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Scopes; import com.google.inject.Scopes;
@@ -10,13 +9,18 @@ import presenters.filtering.global.GlobalFilteringPresenter;
import presenters.MainPresenter; import presenters.MainPresenter;
import presenters.NavPresenter; import presenters.NavPresenter;
import presenters.filtering.local.LocalFilteringPresenter; import presenters.filtering.local.LocalFilteringPresenter;
import presenters.morphology.BasicMorphologyPresenter;
import services.GlobalFilteringService;
import services.ImageService; import services.ImageService;
import services.MorphologyService;
import ui.abstractions.*; import ui.abstractions.*;
import ui.implementation.components.nav.NavBar; import ui.implementation.components.nav.NavBar;
import ui.implementation.views.DoubleMatrix; import ui.implementation.views.DoubleMatrix;
import ui.implementation.views.fitering.global.IdealFilteringView; import ui.implementation.views.fitering.global.IdealFilteringView;
import ui.implementation.views.MainView; import ui.implementation.views.MainView;
import ui.implementation.views.fitering.local.LocalFilteringView; import ui.implementation.views.fitering.local.LocalFilteringView;
import ui.implementation.views.morphology.BasicMorphologyView;
import ui.implementation.views.morphology.ComplexMorphologyView;
/** /**
* Gathers some configurations in order to set the dependency injection container up * Gathers some configurations in order to set the dependency injection container up
@@ -30,6 +34,10 @@ public class AppModule extends AbstractModule {
//--- States --- //--- States ---
bind(AppState.class).in(Scopes.SINGLETON); bind(AppState.class).in(Scopes.SINGLETON);
bind(DoubleMatrixState.class); bind(DoubleMatrixState.class);
bind(LocalFilteringState.class);
bind(GlobalFilteringState.class);
bind(BasicMorphologyState.class);
bind(ComplexMorphologyState.class);
//--- Application layer --- //--- Application layer ---
bind(EventBus.class).in(Scopes.SINGLETON); bind(EventBus.class).in(Scopes.SINGLETON);
@@ -41,6 +49,8 @@ public class AppModule extends AbstractModule {
bind(IDoubleMatrix.class).to(DoubleMatrix.class); bind(IDoubleMatrix.class).to(DoubleMatrix.class);
bind(IGlobalFilteringView.class).to(IdealFilteringView.class); bind(IGlobalFilteringView.class).to(IdealFilteringView.class);
bind(ILocalFilteringView.class).to(LocalFilteringView.class); bind(ILocalFilteringView.class).to(LocalFilteringView.class);
bind(IBasicMorphologyView.class).to(BasicMorphologyView.class);
bind(IComplexMorphologyView.class).to(ComplexMorphologyView.class);
//--- Presentation layer --- //--- Presentation layer ---
bind(MainPresenter.class); bind(MainPresenter.class);
@@ -48,9 +58,14 @@ public class AppModule extends AbstractModule {
bind(DoubleMatrixPresenter.class); bind(DoubleMatrixPresenter.class);
bind(GlobalFilteringPresenter.class); bind(GlobalFilteringPresenter.class);
bind(LocalFilteringPresenter.class); bind(LocalFilteringPresenter.class);
bind(BasicMorphologyPresenter.class);
bind(ComplexMorphologyView.class);
//--- Service layer layer --- //--- Service layer layer ---
bind(ImageService.class); bind(ImageService.class);
bind(LocalFilteringView.class);
bind(GlobalFilteringService.class);
bind(MorphologyService.class);
} }
} }

View File

@@ -14,4 +14,5 @@ public interface INavigator {
void showButterworth(); void showButterworth();
void showConvolutionFiltering(); void showConvolutionFiltering();
void showBasicMorphology(); void showBasicMorphology();
void showComplexMorphology();
} }

View File

@@ -2,6 +2,7 @@ package app;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import domain.image.processing.complex.ComplexMatrix;
import ui.abstractions.AbstractView; import ui.abstractions.AbstractView;
import ui.implementation.views.DoubleMatrix; import ui.implementation.views.DoubleMatrix;
import ui.implementation.views.fitering.global.IdealFilteringView; import ui.implementation.views.fitering.global.IdealFilteringView;
@@ -9,6 +10,7 @@ import ui.implementation.views.fitering.global.NonIdealFilteringView;
import ui.implementation.views.ViewInitializer; import ui.implementation.views.ViewInitializer;
import ui.implementation.views.fitering.local.LocalFilteringView; import ui.implementation.views.fitering.local.LocalFilteringView;
import ui.implementation.views.morphology.BasicMorphologyView; import ui.implementation.views.morphology.BasicMorphologyView;
import ui.implementation.views.morphology.ComplexMorphologyView;
/** /**
* Concrete implement of the INavigator interface. * Concrete implement of the INavigator interface.
@@ -22,6 +24,7 @@ public class SwingNavigator implements INavigator {
private final Provider<NonIdealFilteringView> nonIdealFilteringProvider; private final Provider<NonIdealFilteringView> nonIdealFilteringProvider;
private final Provider<LocalFilteringView> convolutionFilteringProvider; private final Provider<LocalFilteringView> convolutionFilteringProvider;
private final Provider<BasicMorphologyView> basicMorphologyProvider; private final Provider<BasicMorphologyView> basicMorphologyProvider;
private final Provider<ComplexMorphologyView> complexMorphologyProvider;
@Inject @Inject
public SwingNavigator( public SwingNavigator(
@@ -29,13 +32,15 @@ public class SwingNavigator implements INavigator {
Provider<IdealFilteringView> filteringProvider, Provider<IdealFilteringView> filteringProvider,
Provider<NonIdealFilteringView> nonIdealFilteringProvider, Provider<NonIdealFilteringView> nonIdealFilteringProvider,
Provider<LocalFilteringView> convolutionFilterProvider, Provider<LocalFilteringView> convolutionFilterProvider,
Provider<BasicMorphologyView> basicMorphologyProvider Provider<BasicMorphologyView> basicMorphologyProvider,
Provider<ComplexMorphologyView> complexMorphologyProvider
) { ) {
this.doubleMatrixProvider = doubleMatrixProvider; this.doubleMatrixProvider = doubleMatrixProvider;
this.filteringProvider = filteringProvider; this.filteringProvider = filteringProvider;
this.nonIdealFilteringProvider = nonIdealFilteringProvider; this.nonIdealFilteringProvider = nonIdealFilteringProvider;
this.convolutionFilteringProvider = convolutionFilterProvider; this.convolutionFilteringProvider = convolutionFilterProvider;
this.basicMorphologyProvider = basicMorphologyProvider; this.basicMorphologyProvider = basicMorphologyProvider;
this.complexMorphologyProvider = complexMorphologyProvider;
} }
@Override @Override
@@ -68,6 +73,12 @@ public class SwingNavigator implements INavigator {
runView(view); runView(view);
} }
@Override
public void showComplexMorphology() {
ComplexMorphologyView view = complexMorphologyProvider.get();
runView(view);
}
private void runView(AbstractView view) { private void runView(AbstractView view) {
ViewInitializer.Initialize(view); ViewInitializer.Initialize(view);
view.setVisible(true); view.setVisible(true);

View File

@@ -2,28 +2,28 @@ package app.state;
import com.google.inject.Inject; import com.google.inject.Inject;
import domain.image.Image; import domain.image.Image;
import presenters.morphology.MorphologyListener; import presenters.morphology.BasicMorphologyListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class MorphologyState extends State { public class BasicMorphologyState extends State {
private Image erosion; private Image erosion;
private Image dilation; private Image dilation;
private Image opening; private Image opening;
private Image closing; private Image closing;
private final List<MorphologyListener> listeners = new ArrayList<>(); private final List<BasicMorphologyListener> listeners = new ArrayList<>();
@Inject @Inject
public MorphologyState() {} public BasicMorphologyState() {}
public void subscribe(MorphologyListener listener) { public void subscribe(BasicMorphologyListener listener) {
listeners.add(listener); listeners.add(listener);
} }
public void unsubscribe(MorphologyListener listener) { public void unsubscribe(BasicMorphologyListener listener) {
listeners.remove(listener); listeners.remove(listener);
} }

View File

@@ -0,0 +1,64 @@
package app.state;
import com.google.inject.Inject;
import domain.image.Image;
import presenters.morphology.ComplexMorphologyListener;
import java.util.ArrayList;
import java.util.List;
public class ComplexMorphologyState extends State {
private Image mask;
private Image dilation;
private Image reconstruction;
private Image mean;
private final List<ComplexMorphologyListener> listeners = new ArrayList<>();
@Inject
public ComplexMorphologyState() {}
public void subscribe(ComplexMorphologyListener listener) {
listeners.add(listener);
}
public void unsubscribe(ComplexMorphologyListener listener) {
listeners.remove(listener);
}
public void setMask(Image mask) {
this.mask = mask;
listeners.forEach(listeners -> listeners.onMaskChanged(mask));
}
public void setDilation(Image image) {
dilation = image;
listeners.forEach(listener -> listener.onGeodesicDilationChanged(image));
}
public void setReconstruction(Image image) {
reconstruction = image;
listeners.forEach(listener -> listener.onGeodesicReconstructionChanged(image));
}
public void setMedian(Image image) {
mean = image;
listeners.forEach(listener -> listener.onMedianChanged(image));
}
public Image getMask() { return mask; }
public Image getDilation() {
return dilation;
}
public Image getReconstruction() {
return reconstruction;
}
public Image getMean() {
return mean;
}
}

View File

@@ -10,26 +10,30 @@ import org.opencv.imgproc.Imgproc;
public class ComplexMorphology { public class ComplexMorphology {
public static GrayScaleMatrix geodesicDilation(Image image, Image mask, int size) { public static GrayScaleMatrix geodesicDilation(Image image, Image mask, int nbIter) {
Mat input = toMat(image);
Mat maskMat = toMat(mask); Mat maskMat = toMat(mask);
Mat kernel = createKernel(size); Mat current = toMat(image);
Mat kernel = createKernel(3);
for (int i = 0; i < nbIter; i++) {
Mat dilated = new Mat(); Mat dilated = new Mat();
Imgproc.dilate(input, dilated, kernel);
Imgproc.dilate(current, dilated, kernel);
Mat result = new Mat(); Mat result = new Mat();
Core.min(dilated, maskMat, result); Core.min(dilated, maskMat, result);
return toGrayScaleMatrix(result); current = result;
} }
public static GrayScaleMatrix geodesicReconstruction(Image marker, Image mask, int size) { return toGrayScaleMatrix(current);
Mat markerMat = toMat(marker); }
Mat maskMat = toMat(mask);
Mat kernel = createKernel(size);
Mat current = markerMat.clone(); public static GrayScaleMatrix geodesicReconstruction(Image image, Image mask) {
Mat maskMat = toMat(mask);
Mat current = toMat(image);
Mat kernel = createKernel(3);
while (true) { while (true) {
Mat dilated = new Mat(); Mat dilated = new Mat();
@@ -38,7 +42,9 @@ public class ComplexMorphology {
Mat next = new Mat(); Mat next = new Mat();
Core.min(dilated, maskMat, next); Core.min(dilated, maskMat, next);
if (Core.norm(next, current, Core.NORM_INF) == 0) { double difference = Core.norm(next, current, Core.NORM_INF);
if (difference == 0) {
return toGrayScaleMatrix(next); return toGrayScaleMatrix(next);
} }
@@ -46,11 +52,11 @@ public class ComplexMorphology {
} }
} }
public static GrayScaleMatrix meanFilter(Image image, int size) { public static GrayScaleMatrix medianFilter(Image image, int tailleMasque) {
Mat input = toMat(image); Mat input = toMat(image);
Mat result = new Mat(); Mat result = new Mat();
Imgproc.blur(input, result, new Size(size, size)); Imgproc.medianBlur(input, result, tailleMasque);
return toGrayScaleMatrix(result); return toGrayScaleMatrix(result);
} }
@@ -73,10 +79,7 @@ public class ComplexMorphology {
} }
private static Mat createKernel(int size) { private static Mat createKernel(int size) {
return Imgproc.getStructuringElement( return Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(size, size));
Imgproc.MORPH_RECT,
new Size(size, size)
);
} }
private static GrayScaleMatrix toGrayScaleMatrix(Mat mat) { private static GrayScaleMatrix toGrayScaleMatrix(Mat mat) {

View File

@@ -52,6 +52,8 @@ public class NavPresenter {
public void goToBasicMorphology(){ navigator.showBasicMorphology(); } public void goToBasicMorphology(){ navigator.showBasicMorphology(); }
public void goToComplexMorphology(){ navigator.showComplexMorphology(); }
public GrayScaleMatrix getImageGrayMatrix() { return appState.getImage().toGrayScale(); } public GrayScaleMatrix getImageGrayMatrix() { return appState.getImage().toGrayScale(); }
public void setMode(Mode mode) { modeService.setMode(mode); } public void setMode(Mode mode) { modeService.setMode(mode); }

View File

@@ -2,7 +2,7 @@ package presenters.morphology;
import domain.image.Image; import domain.image.Image;
public interface MorphologyListener { public interface BasicMorphologyListener {
void onErosionChanged(Image image); void onErosionChanged(Image image);
void onDilationChanged(Image image); void onDilationChanged(Image image);
@@ -10,4 +10,6 @@ public interface MorphologyListener {
void onOpeningChanged(Image image); void onOpeningChanged(Image image);
void onClosingChanged(Image image); void onClosingChanged(Image image);
} }

View File

@@ -1,23 +1,23 @@
package presenters.morphology; package presenters.morphology;
import app.state.AppState; import app.state.AppState;
import app.state.MorphologyState; import app.state.BasicMorphologyState;
import com.google.inject.Inject; import com.google.inject.Inject;
import domain.image.Image; import domain.image.Image;
import services.MorphologyService; import services.MorphologyService;
import ui.abstractions.IBasicMorphologyView; import ui.abstractions.IBasicMorphologyView;
public class MorphologyPresenter implements MorphologyListener { public class BasicMorphologyPresenter implements BasicMorphologyListener {
private final AppState appState; private final AppState appState;
private final MorphologyState morphologyState; private final BasicMorphologyState morphologyState;
private final MorphologyService morphologyService; private final MorphologyService morphologyService;
private IBasicMorphologyView view; private IBasicMorphologyView view;
@Inject @Inject
public MorphologyPresenter( public BasicMorphologyPresenter(
AppState appState, AppState appState,
MorphologyState morphologyState, BasicMorphologyState morphologyState,
MorphologyService morphologyService MorphologyService morphologyService
) { ) {
this.appState = appState; this.appState = appState;

View File

@@ -0,0 +1,10 @@
package presenters.morphology;
import domain.image.Image;
public interface ComplexMorphologyListener {
void onMaskChanged(Image mask);
void onGeodesicDilationChanged(Image image);
void onGeodesicReconstructionChanged(Image image);
void onMedianChanged(Image image);
}

View File

@@ -0,0 +1,126 @@
package presenters.morphology;
import app.state.AppState;
import app.state.ComplexMorphologyState;
import com.google.inject.Inject;
import domain.image.Image;
import services.MorphologyService;
import ui.abstractions.IComplexMorphologyView;
public class ComplexMorphologyPresenter implements ComplexMorphologyListener {
private final AppState appState;
private final ComplexMorphologyState morphologyState;
private final MorphologyService morphologyService;
private IComplexMorphologyView view;
@Inject
public ComplexMorphologyPresenter(
AppState appState,
ComplexMorphologyState morphologyState,
MorphologyService morphologyService
) {
this.appState = appState;
this.morphologyState = morphologyState;
this.morphologyService = morphologyService;
morphologyState.subscribe(this);
}
public void setView(IComplexMorphologyView view) {
this.view = view;
}
public void setMask(Image mask) {
this.morphologyState.setMask(mask);
}
public void computeGeodesicDilation(int iterations) {
Image image = appState.getImage();
Image mask = morphologyState.getMask();
Image result = morphologyService.geodesicDilation(image, mask, iterations);
morphologyState.setDilation(result);
}
public void computeGeodesicReconstruction() {
Image image = appState.getImage();
Image mask = morphologyState.getMask();
Image result = morphologyService.geodesicReconstruction(image, mask);
System.out.println("IMAGE: "
+ image.getWidth() + "x" + image.getHeight());
System.out.println("MASK: "
+ mask.getWidth() + "x" + mask.getHeight());
System.out.println(
"Same object? " + (image == mask)
);
System.out.println("Image : " + image.getPixel(0, 0).grayValue());
System.out.println("Mask : " + mask.getPixel(0, 0).grayValue());
System.out.println("Result : " + result.getPixel(0, 0).grayValue());
double[][] imageData = image.toGrayScale().getRawData();
double[][] maskData = mask.toGrayScale().getRawData();
int violations = 0;
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
if (imageData[y][x] > maskData[y][x]) {
violations++;
}
}
}
System.out.println("Marker > mask pixels: " + violations);
morphologyState.setReconstruction(result);
}
public void computeMedianFilter(int size) {
Image image = appState.getImage();
Image result = morphologyService.medianFilter(image, size);
morphologyState.setMedian(result);
}
public void dispose() {
view = null;
morphologyState.unsubscribe(this);
}
@Override
public void onMaskChanged(Image mask) {
}
@Override
public void onGeodesicDilationChanged(Image image) {
if (view != null) {
view.updateDilation(image);
}
}
@Override
public void onGeodesicReconstructionChanged(Image image) {
if (view != null) {
view.updateReconstruction(image);
}
}
@Override
public void onMedianChanged(Image image) {
if (view != null) {
view.updateMedian(image);
}
}
}

View File

@@ -22,22 +22,21 @@ public class MorphologyService {
return BasicMorphology.closing(image, size).toImage(); return BasicMorphology.closing(image, size).toImage();
} }
public Image geodesicDilation(Image image, Image mask, int size) { public Image geodesicDilation(Image image, Image mask, int iterations) {
return ComplexMorphology return ComplexMorphology
.geodesicDilation(image, mask, size) .geodesicDilation(image, mask, iterations)
.toImage(); .toImage();
} }
public Image geodesicReconstruction(Image marker, Image mask, int size) { public Image geodesicReconstruction(Image image, Image mask) {
return ComplexMorphology return ComplexMorphology
.geodesicReconstruction(marker, mask, size) .geodesicReconstruction(image, mask)
.toImage(); .toImage();
} }
public Image meanFilter(Image image, int size) { public Image medianFilter(Image image, int size) {
return ComplexMorphology return ComplexMorphology
.meanFilter(image, size) .medianFilter(image, size)
.toImage(); .toImage();
} }
} }

View File

@@ -0,0 +1,10 @@
package ui.abstractions;
import domain.image.Image;
public interface IComplexMorphologyView {
void updateMask(Image mask);
void updateDilation(Image image);
void updateReconstruction(Image image);
void updateMedian(Image image);
}

View File

@@ -254,6 +254,5 @@ public class NavBar extends JMenuBar implements INavBar {
private void displayBasicMorphology(ActionEvent actionEvent) { presenter.goToBasicMorphology(); } private void displayBasicMorphology(ActionEvent actionEvent) { presenter.goToBasicMorphology(); }
private void displayComplexeMorphology(ActionEvent actionEvent) { private void displayComplexeMorphology(ActionEvent actionEvent) { presenter.goToComplexMorphology(); }
}
} }

View File

@@ -2,17 +2,13 @@ package ui.implementation.views.morphology;
import domain.image.Image; import domain.image.Image;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import presenters.morphology.MorphologyPresenter; import presenters.morphology.BasicMorphologyPresenter;
import ui.abstractions.AbstractView; import ui.abstractions.AbstractView;
import ui.abstractions.IBasicMorphologyView; import ui.abstractions.IBasicMorphologyView;
import ui.implementation.components.controls.SaveButton; import ui.implementation.components.controls.SaveButton;
import ui.implementation.components.image.TabbedImagePanel; import ui.implementation.components.image.TabbedImagePanel;
import javax.swing.*; import javax.swing.*;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import java.awt.*; import java.awt.*;
public class BasicMorphologyView extends AbstractView implements IBasicMorphologyView { public class BasicMorphologyView extends AbstractView implements IBasicMorphologyView {
@@ -22,13 +18,13 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
private static final String OPENING_TAB_TITLE = "Ouverture"; private static final String OPENING_TAB_TITLE = "Ouverture";
private static final String CLOSING_TAB_TITLE = "Fermeture"; private static final String CLOSING_TAB_TITLE = "Fermeture";
private final MorphologyPresenter presenter; private final BasicMorphologyPresenter presenter;
private TabbedImagePanel modeTab; private TabbedImagePanel modeTab;
private JTextField maskSizeField; private JSpinner maskSizeSpinner;
@Inject @Inject
public BasicMorphologyView(MorphologyPresenter presenter) { public BasicMorphologyView(BasicMorphologyPresenter presenter) {
this.presenter = presenter; this.presenter = presenter;
} }
@@ -41,7 +37,9 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
modeTab.addPreviewTab(OPENING_TAB_TITLE); modeTab.addPreviewTab(OPENING_TAB_TITLE);
modeTab.addPreviewTab(CLOSING_TAB_TITLE); modeTab.addPreviewTab(CLOSING_TAB_TITLE);
maskSizeField = createPositiveIntegerField(); maskSizeSpinner = new JSpinner(
new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 2)
);
JButton applyButton = new JButton("Appliquer"); JButton applyButton = new JButton("Appliquer");
applyButton.addActionListener(e -> applyMorphology()); applyButton.addActionListener(e -> applyMorphology());
@@ -49,7 +47,7 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
JButton saveButton = new SaveButton(); JButton saveButton = new SaveButton();
JPanel controlPanel = createControlPanel( JPanel controlPanel = createControlPanel(
maskSizeField, maskSizeSpinner,
applyButton applyButton
); );
@@ -100,12 +98,17 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
@Override @Override
public void initValues() { public void initValues() {
this.modeTab.addChangeListener(e -> applyMorphology()); modeTab.addChangeListener(e -> applyMorphology());
presenter.setView(this); presenter.setView(this);
applyMorphology(); applyMorphology();
} }
private JPanel createControlPanel(JTextField sizeField, JButton applyButton) { private JPanel createControlPanel(
JSpinner sizeSpinner,
JButton applyButton
) {
JPanel panel = new JPanel(); JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel); GroupLayout layout = new GroupLayout(panel);
@@ -119,9 +122,9 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
layout.createParallelGroup(GroupLayout.Alignment.CENTER) layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(label) .addComponent(label)
.addComponent( .addComponent(
sizeField, sizeSpinner,
GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,
60, 70,
GroupLayout.PREFERRED_SIZE GroupLayout.PREFERRED_SIZE
) )
.addComponent(applyButton) .addComponent(applyButton)
@@ -131,7 +134,7 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
layout.createSequentialGroup() layout.createSequentialGroup()
.addComponent(label) .addComponent(label)
.addComponent( .addComponent(
sizeField, sizeSpinner,
GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE GroupLayout.PREFERRED_SIZE
@@ -142,18 +145,8 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
return panel; return panel;
} }
private JTextField createPositiveIntegerField() {
JTextField field = new JTextField("1", 5);
((AbstractDocument) field.getDocument()).setDocumentFilter(new PositiveIntegerFilter());
return field;
}
private void applyMorphology() { private void applyMorphology() {
Integer size = getMaskSize(); int size = (Integer) maskSizeSpinner.getValue();
if (size == null || size % 2 == 0) {
return;
}
switch (modeTab.getSelectedIndex()) { switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeErosion(size); case 0 -> presenter.computeErosion(size);
@@ -163,32 +156,22 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
} }
} }
private Integer getMaskSize() { @Override
try {
int size = Integer.parseInt(maskSizeField.getText());
if (size <= 0) {
return null;
}
return size;
} catch (NumberFormatException e) {
return null;
}
}
public void updateErosion(Image image) { public void updateErosion(Image image) {
modeTab.loadImage(EROSION_TAB_TITLE, image); modeTab.loadImage(EROSION_TAB_TITLE, image);
} }
@Override
public void updateDilation(Image image) { public void updateDilation(Image image) {
modeTab.loadImage(DILATION_TAB_TITLE, image); modeTab.loadImage(DILATION_TAB_TITLE, image);
} }
@Override
public void updateOpening(Image image) { public void updateOpening(Image image) {
modeTab.loadImage(OPENING_TAB_TITLE, image); modeTab.loadImage(OPENING_TAB_TITLE, image);
} }
@Override
public void updateClosing(Image image) { public void updateClosing(Image image) {
modeTab.loadImage(CLOSING_TAB_TITLE, image); modeTab.loadImage(CLOSING_TAB_TITLE, image);
} }
@@ -198,31 +181,4 @@ public class BasicMorphologyView extends AbstractView implements IBasicMorpholog
presenter.setView(null); presenter.setView(null);
presenter.dispose(); presenter.dispose();
} }
private static class PositiveIntegerFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr
) throws BadLocationException {
if (isValid(fb.getDocument().getText(0, fb.getDocument().getLength()), offset, 0, string)) {
super.insertString(fb, offset, string, attr);
}
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
if (isValid(fb.getDocument().getText(0, fb.getDocument().getLength()), offset, length, text)) {
super.replace(fb, offset, length, text, attrs);
}
}
private boolean isValid(String current, int offset, int length, String replacement) {
String result = current.substring(0, offset) + replacement + current.substring(offset + length);
if (result.isEmpty()) {
return true;
}
return result.matches("[1-9][0-9]*");
}
}
} }

View File

@@ -0,0 +1,234 @@
package ui.implementation.views.morphology;
import domain.image.Image;
import infrastructure.image.io.ImageLoader;
import infrastructure.ui.ImageMapper;
import jakarta.inject.Inject;
import presenters.morphology.ComplexMorphologyPresenter;
import ui.abstractions.AbstractView;
import ui.abstractions.IComplexMorphologyView;
import ui.implementation.components.controls.SaveButton;
import ui.implementation.components.image.TabbedImagePanel;
import ui.implementation.components.intent.FileChooser;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
public class ComplexMorphologyView extends AbstractView implements IComplexMorphologyView {
private static final String DILATION_TAB_TITLE = "Dilatation géodésique";
private static final String RECONSTRUCTION_TAB_TITLE = "Reconstruction géodésique";
private static final String MEDIAN_TAB_TITLE = "Filtre médian";
private final ComplexMorphologyPresenter presenter;
private TabbedImagePanel modeTab;
private JButton maskButton;
private JLabel maskLabel;
private JSpinner iterationsSpinner;
@Inject
public ComplexMorphologyView(ComplexMorphologyPresenter presenter) {
this.presenter = presenter;
}
@Override
public void initComponents() {
modeTab = new TabbedImagePanel();
modeTab.addPreviewTab(DILATION_TAB_TITLE);
modeTab.addPreviewTab(RECONSTRUCTION_TAB_TITLE);
modeTab.addPreviewTab(MEDIAN_TAB_TITLE);
iterationsSpinner = new JSpinner(
new SpinnerNumberModel(1, 1, Integer.MAX_VALUE, 1)
);
maskButton = new JButton("Choisir...");
maskLabel = new JLabel("Aucun masque");
maskButton.addActionListener(e -> chooseGeodesicMask());
JButton applyButton = new JButton("Appliquer");
applyButton.addActionListener(e -> applyMorphology());
JButton saveButton = new SaveButton();
JPanel controlPanel = createControlPanel(applyButton);
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("Traitement morphologique complexe");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
pack();
}
@Override
public void initValues() {
modeTab.addChangeListener(e -> updateControls());
presenter.setView(this);
updateControls();
}
private JPanel createControlPanel(JButton applyButton) {
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
JLabel maskLabelTitle = new JLabel("Masque géodésique :");
JLabel iterationsLabel = new JLabel("Nombre d'itérations :");
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(maskLabelTitle)
.addComponent(maskButton)
.addComponent(maskLabel)
.addComponent(iterationsLabel)
.addComponent(
iterationsSpinner,
GroupLayout.PREFERRED_SIZE,
70,
GroupLayout.PREFERRED_SIZE
)
.addComponent(applyButton)
);
layout.setVerticalGroup(
layout.createSequentialGroup()
.addComponent(maskLabelTitle)
.addComponent(maskButton)
.addComponent(maskLabel)
.addComponent(iterationsLabel)
.addComponent(
iterationsSpinner,
GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE
)
.addComponent(applyButton)
);
return panel;
}
private void chooseGeodesicMask() {
FileChooser chooser = new FileChooser("./");
if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
File file = chooser.getSelectedFile();
loadImage(file);
maskLabel.setText(file.getName());
applyMorphology();
}
private void updateControls() {
boolean geodesicOperation = modeTab.getSelectedIndex() == 0 || modeTab.getSelectedIndex() == 1;
maskButton.setEnabled(geodesicOperation);
maskLabel.setEnabled(geodesicOperation);
}
private void applyMorphology() {
int operation = modeTab.getSelectedIndex();
int iterations = (Integer) iterationsSpinner.getValue();
switch (operation) {
case 0 -> presenter.computeGeodesicDilation(iterations);
case 1 -> presenter.computeGeodesicReconstruction();
case 2 -> presenter.computeMedianFilter(iterations);
}
}
@Override
public void updateMask(Image mask) {
}
@Override
public void updateDilation(Image image) {
modeTab.loadImage(DILATION_TAB_TITLE, image);
}
@Override
public void updateReconstruction(Image image) {
modeTab.loadImage(RECONSTRUCTION_TAB_TITLE, image);
}
@Override
public void updateMedian(Image image) {
modeTab.loadImage(MEDIAN_TAB_TITLE, image);
}
@Override
public void disposeCore() {
presenter.setView(null);
presenter.dispose();
}
private void loadImage(File file) {
try {
Image mask = ImageLoader.loadImage(file);
presenter.setMask(mask);
} catch (IOException ioe) {
displayErrorMessage("Oups !", "Impossible de charger le masque");
}
}
private void displayErrorMessage(String title, String message) {
JOptionPane.showMessageDialog(
this,
message,
title,
JOptionPane.ERROR_MESSAGE
);
}
}

Binary file not shown.