1

I saw http://opencv-java-tutorials.readthedocs.io/en/latest/05-fourier-transform.html and I want to do it in Spring Framework. It seems to work well. But number of controller is 2. And When I click button, new controller appears. How can I modify it to get same controller?

App.java

package com.hoex.fourier;

import org.opencv.core.Core;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

/**
 * Hello world!
 *
 */
@SpringBootApplication
public class App extends Application {

    private static String[] args;

    public static void main( String[] args ) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        App.args = args;
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        try {
            ApplicationContext context = SpringApplication.run(App.class, args);

            // Create a Scene
            FourierController fourierController = context.getBean(FourierController.class);
            System.out.println("Bean FourierController--------------");
            System.out.println(fourierController);
            System.out.println("Bean FourierController--------------");
            fourierController.setRoot((BorderPane)FXMLLoader.load(getClass().getResource(FourierController.VIEW)));

            System.out.println("App primaryStage -----------------");
            System.out.println(primaryStage);
            System.out.println("App primaryStage -----------------");
            fourierController.setStage(primaryStage);
            System.out.println("App set primaryStage -----------------");
            System.out.println(fourierController.getStage());
            System.out.println("App set primaryStage -----------------");
            Scene scene = new Scene((Parent)fourierController.getRoot());
            // Set the scene on the primary stage
            primaryStage.setScene(scene);
            // Any other shenanigans on the primary stage
            primaryStage.show();
            System.out.println("App Root ---------------------");
            System.out.println(primaryStage.getScene().getRoot());
            System.out.println(fourierController.getRoot());
            System.out.println("App Root ---------------------");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

AppConfig.java

package com.hoex.fourier;

import java.io.IOException;
import java.io.InputStream;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javafx.fxml.FXMLLoader;

@Configuration
public class AppConfig {

    @Bean
    public FourierController fourierController() throws IOException {
        return loadController(FourierController.VIEW);
    }

    private <T> T loadController(String url) throws IOException {
        try (InputStream fxmlStream = getClass().getResourceAsStream(url)) {
            FXMLLoader loader = new FXMLLoader();
            loader.load(fxmlStream);
            return loader.getController();
        }
    }
}

FourierController.java

package com.hoex.fourier;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;

import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

public class FourierController {

    public static final String VIEW = "fxml/Fourier.fxml";
    @FXML
    private Node root;
    // images to show in the view
    @FXML
    private ImageView originalImage;
    @FXML
    private ImageView transformedImage;
    @FXML
    private ImageView antitransformedImage;
    // a FXML button for performing the transformation 
    @FXML
    private Button transformButton;
    // a FXML button for performing the antitransformation 
    @FXML
    private Button antitransformButton;

    // the main stage 
    private Stage stage;
    // the JavaFX file chooser
//  private FileChooser fileChooser;
    private FileChooser fileChooser = new FileChooser();
    // support variables
//  private Mat image;
    private Mat image = new Mat();
//  private List<Mat> planes;
    private List<Mat> planes = new ArrayList<>();
    // the final complex image
//  private Mat complexImage;
    private Mat complexImage = new Mat();

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    /**
     * Init the needed variables
     */ 
    @PostConstruct
    protected void init() {
        System.out.println("Bean init -------------------------");
        System.out.println(this);
        System.out.println("Bean init -------------------------");
        fileChooser = new FileChooser();
        image = new Mat();
        planes = new ArrayList<>();
        complexImage = new Mat();
    }

    /**
     * Load an image from disk
     */ 
    @FXML
    protected void loadImage()
    {
        System.out.println("loadImage -------------------------");
        System.out.println(this);
        System.out.println("loadImage -------------------------");
        System.out.println("loadImage stage -------------------------");
        System.out.println(stage);
        System.out.println("loadImage stage -------------------------");


        // show the open dialog window
        File file = fileChooser.showOpenDialog(stage);
        if (file != null)
        {
            // read the image in gray scale
            image = Imgcodecs.imread(file.getAbsolutePath(), Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE);
            // show the image
            originalImage.setImage(mat2Image(image));
            // set a fixed width
            originalImage.setFitWidth(250);
            // preserve image ratio
            originalImage.setPreserveRatio(true);
            // update the UI
            transformButton.setDisable(false);

            // empty the image planes and the image views if it is not the first
            // loaded image
            if (!planes.isEmpty())
            {
                planes.clear();
                transformedImage.setImage(null);
                antitransformedImage.setImage(null);
            }

        }
    }

    /**
     * The action triggered by pushing the button for apply the dft to the
     * loaded image
     */
    @FXML
    protected void transformImage()
    {
        System.out.println("transformImage -------------------------");
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println("transformImage -------------------------");
        // optimize the dimension of the loaded image
        Mat padded = optimizeImageDim(image);
        padded.convertTo(padded, CvType.CV_32F);
        // prepare the image planes to obtain the complex image
        planes.add(padded);
        planes.add(Mat.zeros(padded.size(), CvType.CV_32F));
        // prepare a complex image for performing the dft
        Core.merge(planes, complexImage);

        // dft
        Core.dft(complexImage, complexImage);

        // optimize the image resulting from the dft operation
        Mat magnitude = createOptimizedMagnitude(complexImage);

        // show the result of the transformation as an image
        transformedImage.setImage(mat2Image(magnitude));
        // set a fixed width
        transformedImage.setFitWidth(250);
        // preserve image ratio
        transformedImage.setPreserveRatio(true);

        // enable the button for performing the antitransformation
        antitransformButton.setDisable(false);
        // disable the button for applying the dft
        transformButton.setDisable(true);
    }

    /**
     * The action triggered by pushing the button for apply the inverse dft to
     * the loaded image
     */
    @FXML
    protected void antitransformImage()
    {
        System.out.println("antitransformImage -------------------------");
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println(this);
        System.out.println("antitransformImage -------------------------");
        Core.idft(complexImage, complexImage);

        Mat restoredImage = new Mat();
        Core.split(complexImage, planes);
        Core.normalize(planes.get(0), restoredImage, 0, 255, Core.NORM_MINMAX);

        antitransformedImage.setImage(mat2Image(restoredImage));
        // set a fixed width
        antitransformedImage.setFitWidth(250);
        // preserve image ratio
        antitransformedImage.setPreserveRatio(true);

        // disable the button for performing the antitransformation
        antitransformButton.setDisable(true);
    }

    /**
     * Optimize the image dimensions
     * 
     * @param image
     *            the {@link Mat} to optimize
     * @return the image whose dimensions have been optimized
     */
    private Mat optimizeImageDim(Mat image)
    {
        // init
        Mat padded = new Mat();
        // get the optimal rows size for dft
        int addPixelRows = Core.getOptimalDFTSize(image.rows());
        // get the optimal cols size for dft
        int addPixelCols = Core.getOptimalDFTSize(image.cols());
        // apply the optimal cols and rows size to the image
        Core.copyMakeBorder(image, padded, 0, addPixelRows - image.rows(), 0, addPixelCols - image.cols(),
                Core.BORDER_CONSTANT, Scalar.all(0));

        return padded;
    }

    /**
     * Optimize the magnitude of the complex image obtained from the DFT, to
     * improve its visualization
     * 
     * @param complexImage
     *            the complex image obtained from the DFT
     * @return the optimized image
     */
    private Mat createOptimizedMagnitude(Mat complexImage)
    {
        // init
        List<Mat> newPlanes = new ArrayList<>();
        Mat mag = new Mat();
        // split the comples image in two planes
        Core.split(complexImage, newPlanes);
        // compute the magnitude
        Core.magnitude(newPlanes.get(0), newPlanes.get(1), mag);

        // move to a logarithmic scale
        Core.add(Mat.ones(mag.size(), CvType.CV_32F), mag, mag);
        Core.log(mag, mag);
        // optionally reorder the 4 quadrants of the magnitude image
        shiftDFT(mag);
        // normalize the magnitude image for the visualization since both JavaFX
        // and OpenCV need images with value between 0 and 255
        // convert back to CV_8UC1
        mag.convertTo(mag, CvType.CV_8UC1);
        Core.normalize(mag, mag, 0, 255, Core.NORM_MINMAX, CvType.CV_8UC1);

        // you can also write on disk the resulting image...
        // Imgcodecs.imwrite("../magnitude.png", mag);

        return mag;
    }

    /**
     * Reorder the 4 quadrants of the image representing the magnitude, after
     * the DFT
     * 
     * @param image
     *            the {@link Mat} object whose quadrants are to reorder
     */
    private void shiftDFT(Mat image)
    {
        image = image.submat(new Rect(0, 0, image.cols() & -2, image.rows() & -2));
        int cx = image.cols() / 2;
        int cy = image.rows() / 2;

        Mat q0 = new Mat(image, new Rect(0, 0, cx, cy));
        Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy));
        Mat q2 = new Mat(image, new Rect(0, cy, cx, cy));
        Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy));

        Mat tmp = new Mat();
        q0.copyTo(tmp);
        q3.copyTo(q0);
        tmp.copyTo(q3);

        q1.copyTo(tmp);
        q2.copyTo(q1);
        tmp.copyTo(q2);
    }

    /**
     * Set the current stage (needed for the FileChooser modal window)
     * 
     * @param stage
     *            the stage
     */
    public Stage getStage() {
        return stage;
    }

    public void setStage(Stage stage)
    {
        this.stage = stage;
    }

    /**
     * Convert a Mat object (OpenCV) in the corresponding Image for JavaFX
     * 
     * @param frame
     *            the {@link Mat} representing the current frame
     * @return the {@link Image} to show
     */
    private Image mat2Image(Mat frame)
    {
        // create a temporary buffer
        MatOfByte buffer = new MatOfByte();
        // encode the frame in the buffer, according to the PNG format
        Imgcodecs.imencode(".png", frame, buffer);
        // build and return an Image created from the image encoded in the
        // buffer
        return new Image(new ByteArrayInputStream(buffer.toArray()));
    }
}

Fourier.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.hoex.fourier.FourierController">
   <left>
      <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
         <children>
            <ImageView fx:id="originalImage" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
         </children>
      </VBox>
   </left>
   <right>
      <VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
         <children>
            <ImageView fx:id="transformedImage" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
            <ImageView fx:id="antitransformedImage" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
         </children>
      </VBox>
   </right>
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button fx:id="loadButton" mnemonicParsing="false" onAction="#loadImage" text="Load Image">
               <HBox.margin>
                  <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
               </HBox.margin>
            </Button>
            <Button fx:id="transformButton" disable="true" mnemonicParsing="false" onAction="#transformImage" text="Apply transform">
               <HBox.margin>
                  <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
               </HBox.margin>
            </Button>
            <Button fx:id="antitransformButton" disable="true" mnemonicParsing="false" onAction="#antitransformImage" text="Apply anti transform">
               <HBox.margin>
                  <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
               </HBox.margin>
            </Button>
         </children>
      </HBox>
   </bottom>
</BorderPane>

Running Application and clicking button, next appears in console. FourierController is BEAN, but They are different. How can I fix it?

Bean FourierController--------------
com.hoex.fourier.FourierController@48309481
Bean FourierController--------------
loadImage -------------------------
com.hoex.fourier.FourierController@b0b5062
loadImage -------------------------
blank_popup
  • 261
  • 1
  • 2
  • 13
  • it looks like same controller but different instance? what happens when you call it third time. does it change to another instance?? – LynAs May 31 '16 at 09:30
  • Sort of related blog post (by me): http://www.marshall.edu/genomicjava/2015/09/27/experiments-with-spring-and-javafx/ I have a somewhat different approach to integrating JavaFX and Spring. – James_D May 31 '16 at 15:47

1 Answers1

1

You request one controller from the Spring IoC container with context.getBean(FourierController.class), and then you load the FXML again with fourierController.setRoot(FXMLLoader.load(...)), causing the FXMLLoader to instantiate the controller class again.

It's not at all clear why you are setting the root via a method call anyway; just add fx:id="root" to the root element of the FXML, and let the FXMLLoader inject the root element in the same way as it does all the other elements. Then your application code can simply be

@Override
public void start(Stage primaryStage) throws Exception {
    try {
        ApplicationContext context = SpringApplication.run(App.class, args);

        // Create a Scene
        FourierController fourierController = context.getBean(FourierController.class);

        System.out.println("App primaryStage -----------------");
        System.out.println(primaryStage);
        System.out.println("App primaryStage -----------------");
        fourierController.setStage(primaryStage);
        System.out.println("App set primaryStage -----------------");
        System.out.println(fourierController.getStage());
        System.out.println("App set primaryStage -----------------");
        Scene scene = new Scene((Parent)fourierController.getRoot());
        // Set the scene on the primary stage
        primaryStage.setScene(scene);
        // Any other shenanigans on the primary stage
        primaryStage.show();
        System.out.println("App Root ---------------------");
        System.out.println(primaryStage.getScene().getRoot());
        System.out.println(fourierController.getRoot());
        System.out.println("App Root ---------------------");
    } catch(Exception e) {
        e.printStackTrace();
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Wow! I cannot find a book or a internet page that covers JavaFX and Spring togerther. Would you introduce reference to learn JavaFX and Spring to me?Thank you for your help. – blank_popup Jun 01 '16 at 00:33
  • 1
    Apart from the blog I wrote (linked below your question) and the [github example](https://github.com/james-d/SpringFXExample) that accompanies it, there's this [old question](http://stackoverflow.com/questions/16147076/how-to-use-spring-with-javafx), [this](http://stancalau.ro/javafx-and-spring/), and [this](http://javafx.steveonjava.com/javafx-and-spring-day-1/). You might also be interested in [afterburner.fx](http://afterburner.adam-bien.com/), which isn't directly related to Spring but is a JavaFX-specific dependency injection framework. – James_D Jun 01 '16 at 00:42
  • 1
    To be honest, though, if you have a reasonable grasp of Spring and understand a fair amount about how to use `FXMLLoader` (see e.g. http://stackoverflow.com/questions/19265954) and JavaFX controllers (see http://stackoverflow.com/questions/14187963) it becomes fairly intuitive how to combine the two. (I guess I assume you know in general [how MVC works](http://stackoverflow.com/questions/32342864) in a thick-client app too...) – James_D Jun 01 '16 at 00:46