public class SemanticObjEdit extends BoxEdit {

    /**
     *
     * @param parent the parent component of the dialog
     * @param frameImage the original image, in original size
     * @param currentObj the object being segmented
     * @param objectAttributes the list of object attributes: type, class, value
     * @param actionOwner the scope of the dialog: create new box, edit existing
     * one
     * @param objColorsList the list of already used colors (for other objects)
     * @param userPreferences user preferences regarding application
     * configuration
     */
    public SemanticObjEdit(Frame parent,
            BufferedImage frameImage,
            Objects currentObj,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences) {
        super(parent, frameImage, currentObj, objectAttributes, actionOwner, objColorsList, userPreferences);

        jBFilterObjMap.setVisible(true);
    }

    @Override
    protected void drawObjContour(Graphics2D g2d) {
        // draw the outer box of the object
        Rectangle bBox = drawOuterBox(g2d);

        // display the object map if the object is scribble
        displayObjMap(bBox);
    }

    @Override
    protected void filterObjMap() {
        // return if the object is not scribble
        if (!(currentObject instanceof ObjectScribble)) {
            return;
        }

        Utils.filterObjectMap(((ObjectScribble) currentObject).getObjectMap());

        // show the new image
        showImage();
    }

    /**
     * Displays the object map of the object, as a merge between the color of
     * the object and the color of the object segmentation.
     */
    private void displayObjMap(Rectangle bBox) {
        // return if the object is not scribble
        if (!(currentObject instanceof ObjectScribble)) {
            return;
        }

        // get the object map
        byte[][] objMap = ((ObjectScribble) currentObject).getObjectMap();

        // return if the object map is null
        if (objMap == null) {
            return;
        }

        // The formula for alpha blending: R = (foregroundRed*foregroundAlpha) + (backgroundRed*(1-foregroundAlpha))
        int[] bkg;  // backgroung
        int[] fg;   // foreground
        int[] rgb = new int[3];
        float alpha = 120.0f / 255.0f;

        // apply background/object color on the image
        for (int y = bBox.y; y < (bBox.y + bBox.height); y++) {
            for (int x = bBox.x; x < (bBox.x + bBox.width); x++) {

                // compute the position in the original object map matrix
                Point transformedPoint = new Point(x - bBox.x, y - bBox.y);
                int posX = transformedPoint.x;
                int posY = transformedPoint.y;

                if (objMap[posX][posY] > 0) {
                    bkg = Utils.getRGB(origImg.getRGB(x, y));
                    fg = Utils.getRGB(objectColor.getRGB());

                    rgb[0] = (int) ((fg[0] * alpha) + (bkg[0] * (1 - alpha)));
                    rgb[1] = (int) ((fg[1] * alpha) + (bkg[1] * (1 - alpha)));
                    rgb[2] = (int) ((fg[2] * alpha) + (bkg[2] * (1 - alpha)));

                    workImg.setRGB(x, y, new Color(rgb[0], rgb[1], rgb[2]).getRGB());
                }
            }
        }
    }

} public class Constants {

    /**
     * Number of objects which can be segmented in the application
     */
    public static final int NUMBER_OF_OBJECTS = 2;

    /**
     * The path to the user preferences containing the configuration specific to
     * the user.
     */
    public static final String USER_PREF_FILE_PATH = "." + File.separatorChar + "cfg" + File.separatorChar + "userPreferences.txt";

    /**
     * Defines the when max size of the brush for which the mouse is an arrow.
     * If the brush size is greater than the specified value, an icon is
     * generated for the mouse.
     */
    public static final int MIN_MOUSE_BRUSH_ICON = 2;

    /**
     * The width of the result panel where the segmented image is shown.
     */
    public static final int RESULT_PANEL_WIDTH = 400;
    /**
     * The height of the result panel where the segmented image is shown.
     */
    public static final int RESULT_PANEL_HEIGHT = 300;

    /**
     * The path to where the User manual of Pixie is stored.
     */
    public static final String APPLICATION_MANUAL = "." + File.separatorChar + "doc" + File.separatorChar + "Pixie_Manual.pdf";

    /**
     * The release number of the software.
     */
    public static final String RELEASE_NUMBER = "0.5";
    /**
     * The number of the current sprint.
     */
    public static final String SPRINT_NUMBER = "3";
    /**
     * The software version is made of the release number together with the
     * sprint number. This will help in tracking the implemented issue for each
     * release.
     */
    public static final String SOFTWARE_VERSION = RELEASE_NUMBER + "." + SPRINT_NUMBER;

    /**
     * The version of JCuda used in the current software.
     */
    public static final String JCUDA_VERSION = "0.8.0";
    /**
     * The version of JCuda used in the current software.
     */
    public static final String JOCL_VERSION = "2.0.0";
    /**
     * The version of the jar used for the drawing tablet communication.
     */
    public static final String JPEN_VERSION = "2";
    /**
     * The version of the jar used for the drawing tablet communication.
     */
    public static final String HAMCREST_VERSION = "1.3";
    /**
     * The version of the JUnit for which the tests are running.
     */
    public static final String JUNIT_VERSION = "4.12";
    /**
     * The version of openCV used in the project.
     */
    public static final String OPENCV_VERSION = "3.2";

    /**
     * Play video file in forward mode
     */
    public static final int PLAY_MODE_FORWARD = 0;

    /**
     * Play video file in backward mode
     */
    public static final int PLAY_MODE_BACKWARD = 1;

    /**
     * Marks an action regarding the next frame.
     */
    public static final int NEXT_FRAME = 0;
    /**
     * Marks an action regarding the previous frame.
     */
    public static final int PREV_FRAME = 1;
    /**
     * Marks an action regarding the jump to a certain frame.
     */
    public static final int JUMP_TO_FRAME = 2;

    /**
     * Shows that the matting algorithm shall be run on the original image.
     */
    public static final int RUN_MATT_ORIG_IMG = 0;
    /**
     * Shows that the matting algorithm shall be run on the highlighted image.
     */
    public static final int RUN_MATT_HIGHLIGHT_IMG = 1;

    /**
     * The percentage of border which has to be added to the box, in preview
     * mode.
     */
    public static final float BORDER_PERCENTAGE = 0.2f;
    /**
     * The min border which shall be added to the box, in preview mode.
     */
    public static final int MIN_BORDER = 10;

    /**
     * Allow the max percentage of the screen to be used for resizing the
     * BBoxes/Crops
     */
    public static final double MAX_SCREEN_PERCENT_RESIZE = 0.85;

    /**
     * How much percentage shall be left for insets and other screen
     * decorations, on the horizontal direction.
     */
    public static final float TOLERANCE_WIDTH = 0.06f;

    /**
     * How much percentage shall be left for insets and other screen
     * decorations, on the vertical direction.
     */
    public static final float TOLERANCE_HEIGHT = 0.12f;

    /**
     * The text used to mark an invalid attribute.
     */
    public static final String INVALID_ATTRIBUTE_TEXT = "undefined";
    
    /**
     * The default text in the root of the object attributes tree.
     */
    public static final String OBJECT_ATTRIBUTES_TEXT = "Object Attributes";
    
     /**
     * The default text in the root of the frame attributes tree.
     */
    public static final String FRAME_ATTRIBUTES_TEXT = "Frame Attributes";

    /**
     * The name of the original image.
     */
    public static final String ORIGINAL_IMG = "Original Image";
    /**
     * The name of the segmented image.
     */
    public static final String SEGMENTED_IMG = "Working Panel";
    /**
     * The name of the segmented image.
     */
    public static final String SEGMENTED_IMG_RESIZED = "Working Panel Resized";
    /**
     * The name of the result image.
     */
    public static final String SEMANTIC_SEGMENTATION_IMG = "Semantic Segmentation";
    /**
     * The name of the joined images.
     */
    public static final String JOINED_IMGS = "Joined Images";

    /**
     * The name of the default folder where to export files.
     */
    public static final String EXPORT_IMGS_PATH = new File("").getAbsolutePath() + File.separatorChar + "Export";

    /**
     * The possible options of getting a grayscale value out of an rgb one.
     */
    public enum GrayscaleOption {
        /**
         * Standard luminance grayscale option.
         */
        STANDARD_LUMINANCE, // L = 0.2126*R + 0.7152*G + 0.0722*B
        /**
         * Gimp luminance grayscale option.
         */
        GIMP_LUMINANCE, // L = 0.222*R + 0.717*G + 0.061*B
        /**
         * Fast grayscale grayscale option.
         */
        FAST_GRAYSCALE          // L = ((2*R + 5*G + 1*B) / 8) = ((r<<1 + g<<2 + g + b) >> 3)
    }

    /**
     * The possible normalisation technics of a vector.
     */
    public enum Normalisation {
        /**
         * L 2 norm block normalisation.
         */
        L2_NORM_BLOCK, // |x| = sqrt(x1^2 + x2^2 + x3^2 + ... + xn^2)
        /**
         * L 2 hys block normalisation.
         */
        L2_HYS_BLOCK, // l2-norm followd by clipping (limiting the maximum values to 0.2) and renormalising
        /**
         * Tile level normalisation.
         */
        TILE_LEVEL, // normalisation at tile level (divide by the max of the tile)
        /**
         * Block level normalisation.
         */
        BLOCK_LEVEL, // normalisation at block level (divide by the max of the block)
        /**
         * Image level normalisation.
         */
        IMAGE_LEVEL, // normalisation at image level (divide by the max of the image)
        /**
         * Do nothing normalisation.
         */
        DO_NOTHING
    }

    /**
     * Defines the possible modes of the application.
     */
    public enum AppConfigMode {
        /**
         * Premium edition app config mode.
         */
        PREMIUM_EDITION, // the application opens videos coming from the server and sends the data back to be saved in the database
        /**
         * The Community edition.
         */
        COMMUNITY_EDITION       // the application opens pictures coming from a local source and saves the data locally on the machine (in a file)
    }

    /**
     * Defines the possible errors or interruptions which could happen during the save of ground
     * truth.
     */
    public enum GTSaveInterruptCodes {
        /**
         * No error gt save interrupt codes.
         */
        NO_ERROR,
        /**
         * Frame attrib not set gt save interrupt codes.
         */
        FRAME_ATTRIB_NOT_SET,
        /**
         * Data not found gt save interrupt codes.
         */
        DATA_NOT_FOUND
    }

    /**
     * The constant IMG_EXTENSION_LIST.
     */
    public static final List<String> IMG_EXTENSION_LIST = Arrays.asList("bmp", "jpg", "jpeg", "png");
    /**
     * The constant VIDEO_EXTENSION_LIST.
     */
    public static final List<String> VIDEO_EXTENSION_LIST = Arrays.asList("mp4", "nv12", "bgr", "avi");
    /**
     * The constant EXTENSION_LIST.
     */
    public static final List<String> EXTENSION_LIST = Stream.concat(IMG_EXTENSION_LIST.stream(), VIDEO_EXTENSION_LIST.stream()).collect(Collectors.toList());

    /**
     * The path to the file containing the frame attributes, for offline mode.
     */
    public static final String FRAME_ATTRIBUTES_PATH = "cfg" + File.separator + "frameAttributes.txt";

    /**
     * The path to the file containing the object attributes, for offline mode.
     */
    public static final String OBJECT_ATTRIBUTES_PATH = "cfg" + File.separator + "objectAttributes.txt";

    /**
     * The list of colors used in the application for the objects and which are
     * reserved for object segmentation. The user cannot select and use one of
     * them.
     */
    public static final List<Color> COLORS_LIST = Arrays.asList(Color.black, Color.green, Color.blue, Color.yellow, Color.magenta, Color.cyan,
            (new Color(153, 255, 153)), (new Color(153, 51, 255)), (new Color(51, 204, 255)), (new Color(255, 153, 102)), (new Color(102, 102, 255)),
            (new Color(255, 99, 71)), (new Color(165, 42, 42)), (new Color(154, 205, 50)), (new Color(0, 206, 209)), (new Color(75, 0, 130)),
            (new Color(175, 159, 106)), (new Color(119, 159, 106)), (new Color(0, 159, 106)), (new Color(0, 80, 106)), (new Color(0, 200, 106)),
            (new Color(124, 128, 39)), (new Color(124, 128, 236)), (new Color(185, 112, 56)), (new Color(185, 75, 125)), (new Color(128, 0, 0)),
            (new Color(128, 128, 0)), (new Color(0, 128, 0)), (new Color(128, 0, 128)), (new Color(0, 128, 128)), (new Color(0, 0, 128)),
            (new Color(238, 130, 238)), (new Color(245, 222, 179)), (new Color(210, 105, 30)), (new Color(105, 105, 105)), (new Color(30, 144, 255))
    );

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private Constants() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }
} public class ConstantsLabeling {

    //----------------------Label type definition-----------------------------//
    /**
     * Label type definition: 2D bounding box.
     */
    public static final String LABEL_2D_BOUNDING_BOX = "2D_bounding_box";

    /**
     * Label type definition: 3D bounding box.
     */
    public static final String LABEL_3D_BOUNDING_BOX = "3D_bounding_box";

    /**
     * Label type definition: pixel segmentation.
     */
    public static final String LABEL_SCRIBBLE = "pixel_segmentation_scribble";

    /**
     * Label type definition: polygon segmentation.
     */
    public static final String LABEL_POLYGON = "polygon_segmentation";

    /**
     * Label type definition: free drawing segmentation.
     */
    public static final String LABEL_FREE_DRAW = "free_drawing_segmentation";

    //----------------------Label source definition---------------------------//
    /**
     * Label source definition: manual.
     */
    public static final String LABEL_SOURCE_MANUAL = "manual";

    //----------------------Actions while in scribble mode--------------------//
    /**
     * Types of possible actions while in scribble mode: erase scribbles.
     */
    public static final int ACTION_TYPE_ERASE = -1;

    /**
     * Types of possible actions while in scribble mode: draw background
     * scribble.
     */
    public static final int ACTION_TYPE_BACKGROUND = 0;

    /**
     * Types of possible actions while in scribble mode: draw object scribble.
     */
    public static final int ACTION_TYPE_OBJECT = 1;

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private ConstantsLabeling() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }
} public class UserPreferences implements Serializable {

    /**
     * Serial class version in form of MAJOR_MINOR_BUGFIX_DAY_MONTH_YEAR
     */
    private static final long serialVersionUID = 0x00_06_01_11_09_2017L;

    /**
     * logger instance
     */
    private final transient org.slf4j.Logger log = LoggerFactory.getLogger(UserPreferences.class);

    /**
     * The path to the last used directory.
     */
    private String lastDirectory;

    /**
     * Indicator for knowing if the background should be merged or not in the
     * image.
     */
    private boolean mergeBKG;

    /**
     * Indicator for knowing if the scribbles should be shown on the work panel.
     */
    private boolean showScribbles;

    /**
     * Indicator for knowing if the crops should be shown on the work panel.
     */
    private boolean showCrops;

    /**
     * Indicator for knowing if only the crops and scribbles of the current
     * object have to be displayed on the working panel.
     */
    private boolean showJustCurrentObj;

    /**
     * Indicator for knowing if the objects should be displayed to the user,
     * after pressing next and going to the next file, for review.
     */
    private boolean popUpObjects;

    /**
     * Indicator for knowing if play mode of the video shall be forward or
     * backward.
     */
    private boolean playBackward;

    /**
     * Indicator for knowing if file map shall be saved when the data is saved
     * in the database.
     */
    private boolean saveFrameObjMap;

    /**
     * Indicator for knowing if the user wants to have the object highlighted.
     * True = draws a transparent layer over the object to highlight it.
     */
    private boolean showObjHighlight;

    /**
     * The value of the alpha to be used for the highlight of the object.
     */
    private int objAlphaVal;

    /**
     * Indicator for knowing if the image shall be flipped vertically or not.
     */
    private boolean flipVertically;

    /**
     * Indicator for knowing if the image shall be mirrored or not.
     */
    private boolean mirrorImage;

    /**
     * The preferred value of the DPI for the display of the application.
     */
    private int preferredDPI;

    /**
     * Indicator for knowing if the application is running for the first time.
     */
    private boolean firstRun;

    /**
     * The path where to export images using the Export option.
     */
    private String imgExportPath;

    /**
     * The extension used for the export of images.
     */
    private String imgExportExtension;

    /**
     * Export every frame the selected type of image.
     */
    private boolean imgAutoExportEveryFrame;

    /**
     * The type of image which is to be saved every frame.
     */
    private String imgTypeForExport;

    /**
     * The type of images to be joined for the export of every frame.
     */
    private String imgTypeJoinedExport;

    /**
     * Indicator for knowing if the frame annotations should be checked to
     * insure they are valid.
     */
    private boolean checkFrameAnnotations;

    /**
     * Indicator for knowing if the object attributes should be checked to
     * insure they are valid.
     */
    private boolean checkObjectAttributes;

    /**
     * Instantiate a new user preferences class, which is meant to read the user
     * configuration file and set all its preferences in the application when it
     * starts.
     */
    public UserPreferences() {
        String readLine;

        setDefaults();

        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(Constants.USER_PREF_FILE_PATH), Charset.forName("UTF-8")))) {
            while ((readLine = br.readLine()) != null) {
                String[] wordsList = readLine.split("=");

                // if the words list is not correct, jump over the line
                if ((wordsList == null) || (wordsList.length < 2)) {
                    continue;
                }

                switch (wordsList[0]) {
                    case "lastUsedDirectory":
                        lastDirectory = wordsList[wordsList.length - 1];
                        break;

                    case "mergeBKG":
                        mergeBKG = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showScribbles":
                        showScribbles = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showCrops":
                        showCrops = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "showJustCurrentObj":
                        showJustCurrentObj = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "popUpObjects":
                        popUpObjects = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "playBackward":
                        playBackward = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "saveFrameObjMap":
                        saveFrameObjMap = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;


                    case "showObjHighlight":
                        showObjHighlight = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "objAlphaVal":
                        objAlphaVal = Integer.parseInt(wordsList[wordsList.length - 1]);
                        break;

                    case "flipVertically":
                        flipVertically = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "mirrorImage":
                        mirrorImage = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "preferredDPI":
                        preferredDPI = Integer.parseInt(wordsList[wordsList.length - 1]);
                        break;

                    case "firstRun":
                        firstRun = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "imgExportPath":
                        imgExportPath = wordsList[wordsList.length - 1];
                        break;

                    case "imgExportExtension":
                        imgExportExtension = wordsList[wordsList.length - 1];
                        break;

                    case "imgAutoExportEveryFrame":
                        imgAutoExportEveryFrame = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "imgTypeForExport":
                        imgTypeForExport = wordsList[wordsList.length - 1];
                        break;

                    case "imgTypeJoinedExport":
                        imgTypeJoinedExport = wordsList[wordsList.length - 1];
                        break;

                    case "checkFrameAnnotations":
                        checkFrameAnnotations = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    case "checkObjectAttributes":
                        checkObjectAttributes = Boolean.parseBoolean(wordsList[wordsList.length - 1]);
                        break;

                    default:
                        log.info("The userPreferences.txt file contains an unknown key: {}", wordsList[0]);
                        break;
                }
            }
        } catch (IOException ex) {
            log.error("The user preferences file was not loaded! Will load default values!!");
            log.debug("The user preferences file was not loaded! Will load default values!! {}", ex);
        }
    }

    /**
     * Returns the directory of the last opened file.
     *
     * @return - the path to the directory from where the last file was opened
     */
    public String getLastDirectory() {
        return lastDirectory;
    }

    /**
     * Saves the path to the last opened file. This helps the user to not always
     * navigate through folders.
     *
     * @param lastDirectory - the path to the directory from where the last file was opened
     */
    public void setLastDirectory(String lastDirectory) {
        this.lastDirectory = lastDirectory;
    }

    /**
     * Shows if the user wants to have the background merged into the big result
     * or not. This is scribble labeling specific.
     *
     * @return - true if the user used last time the merge background and false otherwise
     */
    public boolean isMergeBKG() {
        return mergeBKG;
    }

    /**
     * Shows if the user wants to have the scribbles of the segmented objects,
     * visible on the drawing panel.
     *
     * @return - true if the user used last time the show scribbles and false otherwise
     */
    public boolean isShowScribbles() {
        return showScribbles;
    }

    /**
     * Shows if the user wants to have just the current object visible in the
     * drawing panel.
     *
     * @return - true if the user used last time the show just current object and false otherwise
     */
    public boolean isShowJustCurrentObj() {
        return showJustCurrentObj;
    }

    /**
     * Sets if the user wants to have the background merged into the big result
     * or not. This is scribble labeling specific.
     *
     * @param mergeBKG - true if the user used last time the merge background and false otherwise
     */
    public void setMergeBKG(boolean mergeBKG) {
        this.mergeBKG = mergeBKG;
    }

    /**
     * Sets if the user wants to have the scribbles of the segmented objects,
     * visible on the drawing panel.
     *
     * @param showScribbles - true if the user used last time the show scribbles and false otherwise
     */
    public void setShowScribbles(boolean showScribbles) {
        this.showScribbles = showScribbles;
    }

    /**
     * Sets if the user wants to have just the current object visible in the
     * drawing panel.
     *
     * @param showJustCurrentObj - true if the user used last time the show just current object and false otherwise
     */
    public void setShowJustCurrentObj(boolean showJustCurrentObj) {
        this.showJustCurrentObj = showJustCurrentObj;
    }

    /**
     * Shows if the user wants to have the crops of the segmented objects,
     * visible on the drawing panel.
     *
     * @return true if the user used last time the show crops and false otherwise
     */
    public boolean isShowCrops() {
        return showCrops;
    }

    /**
     * Sets if the user wants to have the crops of the segmented objects,
     * visible on the drawing panel.
     *
     * @param showCrops true if the user used last time the show crops and false otherwise
     */
    public void setShowCrops(boolean showCrops) {
        this.showCrops = showCrops;
    }

    /**
     * Shows if the user wants to have the objects pop up for review, after the
     * get next frame call.
     *
     * @return - true if the user wants to review the objects, therefore the user wants them to pop up and false otherwise
     */
    public boolean isPopUpObjects() {
        return popUpObjects;
    }

    /**
     * Sets if the user wants to have the objects pop up for review, after the
     * get next frame call.
     *
     * @param popUpObjects - true if the user wants to review the objects, therefore the user wants them to pop up and false otherwise
     */
    public void setPopUpObjects(boolean popUpObjects) {
        this.popUpObjects = popUpObjects;
    }

    /**
     * Shows if the user wants to have the video played forward or backward.
     *
     * @return - true if the user wants the video played backward and false if the user wants the video played forward
     */
    public boolean isPlayBackward() {
        return playBackward;
    }

    /**
     * Sets if the user wants to have the video played forward or backward.
     *
     * @param playBackward - true if the user wants the video played backward and false if the user wants the video played forward
     */
    public void setPlayBackward(boolean playBackward) {
        this.playBackward = playBackward;
    }

    /**
     * Shows if the user wants to save the file object map, when saving data in
     * the database.
     *
     * @return - true if the user wants to save the file object map and false if no file shall be saved
     */
    public boolean isSaveFrameObjMap() {
        return saveFrameObjMap;
    }

    /**
     * Sets if the user wants to save the file object map, when saving data in
     * the database.
     *
     * @param saveFrameObjMap - true if the user wants to save the file object map and false if no file shall be saved
     */
    public void setSaveFrameObjMap(boolean saveFrameObjMap) {
        this.saveFrameObjMap = saveFrameObjMap;
    }

    /**
     * Shows if the user wants to have the object highlighted.
     *
     * @return true if the user wants to have drawn a transparent layer over the object to highlight it and false if the user wants to have just the border of the object and no filling
     */
    public boolean isShowObjHighlight() {
        return showObjHighlight;
    }

    /**
     * Sets if the user wants to have the object highlighted.
     *
     * @param showObjHighlight true if the user wants to have drawn a transparent layer over the object to highlight it and false if the user wants to have just the border of the object and no filling
     */
    public void setShowObjHighlight(boolean showObjHighlight) {
        this.showObjHighlight = showObjHighlight;
    }

    /**
     * Shows the value of the alpha to be used for the highlight of the object.
     *
     * @return the integer value, between 0 and 255, for the alpha to be used for highlighting the object
     */
    public int getObjAlphaVal() {
        return objAlphaVal;
    }

    /**
     * Sets the value of the alpha to be used for the highlight of the object.
     *
     * @param objAlphaVal the integer value, between 0 and 255, for the alpha to be used for highlighting the object
     */
    public void setObjAlphaVal(int objAlphaVal) {
        this.objAlphaVal = objAlphaVal;
    }

    /**
     * Show if the user wants to have the image flipped vertically or not.
     *
     * @return true if the image shall be flipped vertically and false if the image shall not be changed
     */
    public boolean isFlipVertically() {
        return flipVertically;
    }

    /**
     * Sets if the user wants to have the image flipped vertically or not.
     *
     * @param flipVertically true if the image shall be flipped vertically and false if the image shall not be changed
     */
    public void setFlipVertically(boolean flipVertically) {
        this.flipVertically = flipVertically;
    }

    /**
     * Shows if the user wants to have the image mirrored or not.
     *
     * @return true if the image shall be mirrored and false if the image shall not be changed
     */
    public boolean isMirrorImage() {
        return mirrorImage;
    }

    /**
     * Sets if the user wants to have the image mirrored or not.
     *
     * @param mirrorImage true if the image shall be mirrored and false if the image shall not be changed
     */
    public void setMirrorImage(boolean mirrorImage) {
        this.mirrorImage = mirrorImage;
    }

    /**
     * Returns the preferred value of the DPI, used for rescaling the screen
     * image.
     *
     * @return the last used value for the DPI
     */
    public int getPreferredDPI() {
        return preferredDPI;
    }

    /**
     * Sets the preferred value of the DPI, used for rescaling the screen image.
     *
     * @param preferredDPI the user defined value for the DPI
     */
    public void setPreferredDPI(int preferredDPI) {
        this.preferredDPI = preferredDPI;
    }

    /**
     * Shows if the application is running for the first time.
     *
     * @return true if the application is running for the first time and false otherwise
     */
    public boolean isFirstRun() {
        return firstRun;
    }

    /**
     * Sets the flag regarding the application running for the first time.
     *
     * @param firstRun true if the application is running for the first time and false otherwise
     */
    public void setFirstRun(boolean firstRun) {
        this.firstRun = firstRun;
    }

    /**
     * Get the path where the user prefers to export images.
     *
     * @return the string representing the path where the images should be saved
     */
    public String getImgExportPath() {
        return imgExportPath;
    }

    /**
     * Set the path where the user prefers to export images.
     *
     * @param imgExportPath the string representing the path where the images should be saved
     */
    public void setImgExportPath(String imgExportPath) {
        this.imgExportPath = imgExportPath;
    }

    /**
     * Get the file type the user prefers for the export of images.
     *
     * @return the string representing the file type used for the saved images
     */
    public String getImgExportExtension() {
        return imgExportExtension;
    }

    /**
     * Set the file type the user prefers for the export of images.
     *
     * @param imgExportExtension the string representing the file type used for the saved images
     */
    public void setImgExportExtension(String imgExportExtension) {
        this.imgExportExtension = imgExportExtension;
    }

    /**
     * Set the possibility to export a certain type of image, every frame, till
     * the user changes the option.
     *
     * @return true if every frame should be exported; false if no automatic export shall be done
     */
    public boolean isImgAutoExportEveryFrame() {
        return imgAutoExportEveryFrame;
    }

    /**
     * Find out if the application should export a certain type of image, every
     * frame, till the user changes the option.
     *
     * @param imgAutoExportEveryFrame true if every frame should be exported; false if no automatic export shall be done
     */
    public void setImgAutoExportEveryFrame(boolean imgAutoExportEveryFrame) {
        this.imgAutoExportEveryFrame = imgAutoExportEveryFrame;
    }

    /**
     * Get the type of image to be exported: original, segmented or result
     * image.
     *
     * @return a string representing the type of the image exported every frame
     */
    public String getImgTypeForExport() {
        return imgTypeForExport;
    }

    /**
     * Set the type of image to be exported: original, segmented or result
     * image.
     *
     * @param imgTypeForExport a string representing the type of the image exported every frame
     */
    public void setImgTypeForExport(String imgTypeForExport) {
        this.imgTypeForExport = imgTypeForExport;
    }

    /**
     * Get the types of images to be joined for export: original + segmented +
     * result image.
     *
     * @return string representing the type of the images to be joined for exporting them every frame
     */
    public String getImgTypeJoinedExport() {
        return imgTypeJoinedExport;
    }

    /**
     * Set the types of images to be joined for export: original + segmented +
     * result image.
     *
     * @param imgTypeJoinedExport a string representing the type of the images to be joined for exporting them every frame
     */
    public void setImgTypeJoinedExport(String imgTypeJoinedExport) {
        this.imgTypeJoinedExport = imgTypeJoinedExport;
    }

    /**
     * Get the state of the check of frame attributes option (checks if the
     * frame attributes are set to a different value than the default).
     *
     * @return true if the frame attributes should be checked and false otherwise
     */
    public boolean isCheckFrameAnnotations() {
        return checkFrameAnnotations;
    }

    /**
     * Enables/Disables the check of frame attributes (checks if the frame
     * attributes are set to a different value than the default).
     *
     * @param checkFrameAnnotations true if the frame attributes should be checked and false otherwise
     */
    public void setCheckFrameAnnotations(boolean checkFrameAnnotations) {
        this.checkFrameAnnotations = checkFrameAnnotations;
    }

    /**
     * Get the state of the check of object attributes option (checks if the
     * object attributes are set to a different value than the default).
     *
     * @return true if the object attributes should be checked and false
     * otherwise
     */
    public boolean isCheckObjectAttributes() {
        return checkObjectAttributes;
    }

    /**
     * Enables/Disables the check of object attributes (checks if the object
     * attributes are set to a different value than the default).
     *
     * @param checkObjectAttributes true if the frame attributes should be
     * checked and false otherwise
     */
    public void setCheckObjectAttributes(boolean checkObjectAttributes) {
        this.checkObjectAttributes = checkObjectAttributes;
    }

    /**
     * Saves into the user preferences file the latest wishes of the user.
     */
    public void saveUserPreferences() {
        try {
            String fileContent = "";
            try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(Constants.USER_PREF_FILE_PATH), Charset.forName("UTF-8")))) {
                fileContent += "lastUsedDirectory=" + lastDirectory + "\r\n";
                fileContent += "mergeBKG=" + mergeBKG + "\r\n";
                fileContent += "showScribbles=" + showScribbles + "\r\n";
                fileContent += "showCrops=" + showCrops + "\r\n";
                fileContent += "showJustCurrentObj=" + showJustCurrentObj + "\r\n";
                fileContent += "popUpObjects=" + popUpObjects + "\r\n";
                fileContent += "playBackward=" + playBackward + "\r\n";
                fileContent += "saveFrameObjMap=" + saveFrameObjMap + "\r\n";
                fileContent += "showObjHighlight=" + showObjHighlight + "\r\n";
                fileContent += "objAlphaVal=" + objAlphaVal + "\r\n";
                fileContent += "flipVertically=" + flipVertically + "\r\n";
                fileContent += "mirrorImage=" + mirrorImage + "\r\n";
                fileContent += "preferredDPI=" + preferredDPI + "\r\n";
                fileContent += "firstRun=" + false + "\r\n";
                fileContent += "imgExportPath=" + imgExportPath + "\r\n";
                fileContent += "imgExportExtension=" + imgExportExtension + "\r\n";
                fileContent += "imgAutoExportEveryFrame=" + imgAutoExportEveryFrame + "\r\n";
                fileContent += "imgTypeForExport=" + imgTypeForExport + "\r\n";
                fileContent += "imgTypeJoinedExport=" + imgTypeJoinedExport + "\r\n";
                fileContent += "checkFrameAnnotations=" + checkFrameAnnotations + "\r\n";
                fileContent += "checkObjectAttributes=" + checkObjectAttributes + "\r\n";

                bw.write(fileContent, 0, fileContent.length());
                bw.flush();
            }
        } catch (IOException ex) {
            Logger.getLogger(UserPreferences.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Set default values for user configuration parameters.
     */
    private void setDefaults() {
        // set some default initialisation values because the configuration file was not found
        lastDirectory = "";
        mergeBKG = false;
        showScribbles = true;
        showCrops = true;
        showJustCurrentObj = false;
        popUpObjects = true;
        playBackward = false;
        saveFrameObjMap = false;
        showObjHighlight = true;
        objAlphaVal = 60;
        flipVertically = false;
        mirrorImage = false;
        preferredDPI = 144;
        firstRun = true;
        imgExportPath = Constants.EXPORT_IMGS_PATH;
        imgExportExtension = "jpeg";
        imgAutoExportEveryFrame = false;
        imgTypeForExport = Constants.SEGMENTED_IMG;
        imgTypeJoinedExport = Constants.ORIGINAL_IMG + "," + Constants.SEGMENTED_IMG;
        checkFrameAnnotations = true;
        checkObjectAttributes = true;
    }
} public class Utils {

    /**
     * logger instance
     */
    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);

    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors. Java adds an implicit public
     * constructor to every class which does not define at least one explicitly.
     * Hence, at least one non-public constructor should be defined.
     */
    private Utils() {
        throw new IllegalStateException("Utility class, do not instantiate!");
    }

    /**
     * Get the image from the specified box.
     *
     * @param origImg - the original image from where the selection shall be
     * copied
     * @param posOrig - the position where the image has to be copied from
     * @return - the image contained in the specified crop
     */
    public static BufferedImage getSelectedImg(BufferedImage origImg, Rectangle posOrig) {
        Rectangle pos = posOrig;
        BufferedImage tempImg = null;

        if ((pos != null) && (pos.width > 0) && (pos.height > 0)) {
            tempImg = new BufferedImage(pos.width, pos.height, origImg.getType());

            for (int y = pos.y; y < pos.y + pos.height; y++) {
                for (int x = pos.x; x < pos.x + pos.width; x++) {
                    tempImg.setRGB(x - pos.x, y - pos.y, origImg.getRGB(x, y));
                }
            }
        }

        return tempImg;
    }

    /**
     * Compute the l^2-Norm of the input vector.
     * <p>
     * |x| = sqrt(x1^2 + x2^2 + x3^2 + ... + xn^2)
     *
     * @param vector - the input vector
     * @return - the value of the norm of the vector
     */
    public static double computeVectorL2Norm(double[] vector) {
        double sum = 0.0;

        // compute the sum of squares
        for (int index = 0; index < vector.length; index++) {
            sum += vector[index] * vector[index];
        }

        // compute the compute square root of the sum of squares
        return Math.sqrt(sum);
    }

    /**
     * Compute squared euclidian distance double.
     *
     * @param vectorA the vector a
     * @param vectorB the vector b
     * @return the double
     */
    public static double computeSquaredEuclidianDistance(double[] vectorA, double[] vectorB) {
        if (vectorA.length != vectorB.length) {
            LOG.error("The input arrays have different sizes!!!");
        }

        double squaredSum = 0.0;

        // compute the sum of square differences
        for (int index = 0; index < vectorA.length; index++) {
            squaredSum += (vectorA[index] - vectorB[index]) * (vectorA[index] - vectorB[index]);
        }

        return (squaredSum / vectorA.length);
    }

    /**
     * Gets the current date and time in the form of "yyyy-MM-dd HH:mm:ss".
     *
     * @return - the current date and time in the format: "yyyy-MM-dd HH:mm:ss"
     */
    public static String getCurrentTimeStamp() {
        java.util.Date dt = new java.util.Date();

        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        return sdf.format(dt);
    }

    /**
     * Compute the location of the window relative to the mouse location. The
     * window shall be centered on the mouse location.
     *
     * @param winDim - the dimension of the window to be centered
     * @return - the point where the window shall be located
     */
    public static Point winLocRelativeToMouse(Dimension winDim) {
        Point mouse = MouseInfo.getPointerInfo().getLocation();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

        int x = (int) (mouse.getX() - (winDim.width / 2.0));
        int y = (int) (mouse.getY() - (winDim.height / 2.0));

        // check if the window will get out of the image
        x = Math.min(Math.max(0, x), Math.max(0, (screenSize.width - winDim.width)));
        y = Math.min(Math.max(0, y), Math.max(0, (screenSize.height - winDim.height - 100)));

        return (new Point(x, y));
    }

    /**
     * Check if the component com1 fits inside component comp2.
     *
     * @param comp1 - the size of the component to be checked if it fits
     * @param comp2 - the size of the component where the other component should
     * fit in
     * @return - return true if the component 1 fits in the component 2
     */
    public static boolean checkPlausability(Dimension comp1, Dimension comp2) {
        return ((comp1.width > 0) && (comp1.width <= comp2.width))
                && (comp1.height > 0) && (comp1.height <= comp2.height);
    }

    /**
     * Checks the extension of the given file and returns true if it is an image
     * file, or false otherwise.
     *
     * @param fileName - the name of the file to be checked
     * @return - true for image files and false for other file types
     */
    public static boolean isImageFile(String fileName) {
        return checkExtension(fileName, common.Constants.IMG_EXTENSION_LIST);
    }

    /**
     * Checks the extension of the given file and returns true if it is a video
     * file, or false otherwise.
     *
     * @param fileName - the name of the file to be checked
     * @return - true for video files and false for other file types
     */
    public static boolean isVideoFile(String fileName) {
        return checkExtension(fileName, common.Constants.VIDEO_EXTENSION_LIST);
    }

    /**
     * Get the extension of a file.
     *
     * @param f - the file itself
     * @return - the string representing the extension of the file
     */
    public static String getExtension(File f) {
        String fileName = f.getName();
        return getExtension(fileName);
    }

    /**
     * Get the extension of the string representing the name of a file.
     *
     * @param fileName - the name of the file
     * @return - the string representing the extension of the file
     */
    public static String getExtension(String fileName) {
        String ext = null;
        int i = fileName.lastIndexOf('.');
        if ((i > 0) && (i < fileName.length() - 1)) {
            ext = fileName.substring(i + 1).toLowerCase(Locale.ENGLISH);
        }
        return ext;
    }

    /**
     * Obtains the data from the raster of the given image as a byte array.
     *
     * @param image The image
     * @return The image data
     * @throws IllegalArgumentException If the given image is not backed by a
     * DataBufferByte. Usually, only BufferedImages of with the types
     * BufferedImage.TYPE_BYTE_* have a DataBufferByte
     */
    public static byte[] getByteData(BufferedImage image) {
        DataBuffer dataBuffer = image.getRaster().getDataBuffer();
        if (!(dataBuffer instanceof DataBufferByte)) {
            throw new IllegalArgumentException(
                    "Image does not contain a DataBufferByte");
        }
        DataBufferByte dataBufferByte = (DataBufferByte) dataBuffer;
        byte data[] = dataBufferByte.getData();
        return data;
    }

    /**
     * Copy one buffered image to another.
     *
     * @param src - source image
     * @param dst - destination image
     */
    public static void copySrcIntoDstAt(final BufferedImage src, final BufferedImage dst) {
        byte[] srcBuf = getByteData(src);
        byte[] dstBuf = getByteData(dst);
        int representationFactor;

        // width * hight * 3 because of rgb components and byte representation
        switch (src.getType()) {
            case BufferedImage.TYPE_BYTE_GRAY:
            case BufferedImage.TYPE_BYTE_INDEXED:
                representationFactor = 1;
                break;
            case BufferedImage.TYPE_3BYTE_BGR:
                representationFactor = 3;
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                representationFactor = 4;
                break;
            default:
                representationFactor = 3;
                break;
        }

        System.arraycopy(srcBuf, 0, dstBuf, 0, src.getWidth() * src.getHeight() * representationFactor);
    }

    /**
     * Copy one buffered image to another and return the copied image.
     *
     * @param src - source image
     * @return - the copied image of the original
     */
    public static BufferedImage createImageCopy(final BufferedImage src) {
        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());

        byte[] srcBuf = getByteData(src);
        byte[] dstBuf = getByteData(dst);
        int representationFactor;

        // width * hight * 3 because of rgb components and byte representation
        switch (src.getType()) {
            case BufferedImage.TYPE_BYTE_GRAY:
                representationFactor = 1;
                break;
            case BufferedImage.TYPE_3BYTE_BGR:
                representationFactor = 3;
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                representationFactor = 4;
                break;
            default:
                representationFactor = 3;
                break;
        }

        System.arraycopy(srcBuf, 0, dstBuf, 0, src.getWidth() * src.getHeight() * representationFactor);

        return dst;
    }

    /**
     * Convert a matrix into a gray scale buffered image.
     *
     * @param width - the width of the image
     * @param height - the height of the image
     * @param imageType - the type of image to create
     * @param imgMatrix - the matrix with the pixels of the image
     * @return - a buffered image, based on the input matrix
     */
    public static BufferedImage convertToBuffImage(int width, int height, int imageType, double[][] imgMatrix) {
        BufferedImage bi = new BufferedImage(width, height, imageType);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixel = Utils.limit(0, (int) imgMatrix[x][y], 255);
                bi.setRGB(x, y, new Color(pixel, pixel, pixel).getRGB());
            }
        }

        return bi;
    }

    /**
     * Apply the histogram equalisation for the input image.
     *
     * @param originalImage - the image to be processed
     * @return - the image with the equalisation of histogram applied - in
     * grayscale
     */
    public static BufferedImage histogramEqGrayscale(BufferedImage originalImage) {
        BufferedImage workImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), originalImage.getType());

        int[] rgb;
        int[] meanIntensity = new int[256];
        int[] histCum = new int[256];
        int[] transf = new int[256];

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));

                int average = (int) (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);

                meanIntensity[average]++;
            }
        }
        histCum[0] = meanIntensity[0];

        for (int i = 1; i < 256; i++) {
            histCum[i] = meanIntensity[i] + histCum[i - 1];
        }

        for (int i = 0; i < 256; i++) {
            transf[i] = (255 * histCum[i]) / (originalImage.getWidth() * originalImage.getHeight());
        }

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                int average = (int) (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]);
                workImage.setRGB(i, j, new Color(transf[average], transf[average], transf[average]).getRGB());
            }
        }
        return workImage;

    }

    /**
     * Apply the histogram equalisation for the input image.
     *
     * @param originalImage - the image to be processed
     * @return - the image with the equalisation of histogram applied - in color
     */
    public static BufferedImage histogramEqColor(BufferedImage originalImage) {
        BufferedImage workImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), originalImage.getType());

        int[] rgb;
        int[] meanIntensityR = new int[256];
        int[] histCumR = new int[256];
        int[] transfR = new int[256];

        int[] meanIntensityG = new int[256];
        int[] histCumG = new int[256];
        int[] transfG = new int[256];

        int[] meanIntensityB = new int[256];
        int[] histCumB = new int[256];
        int[] transfB = new int[256];

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                meanIntensityR[rgb[0]]++;
                meanIntensityG[rgb[1]]++;
                meanIntensityB[rgb[2]]++;
            }
        }
        histCumR[0] = meanIntensityR[0];
        histCumG[0] = meanIntensityG[0];
        histCumB[0] = meanIntensityB[0];

        for (int i = 1; i < 256; i++) {
            histCumR[i] = meanIntensityR[i] + histCumR[i - 1];
            histCumG[i] = meanIntensityG[i] + histCumG[i - 1];
            histCumB[i] = meanIntensityB[i] + histCumB[i - 1];
        }

        for (int i = 0; i < 256; i++) {
            transfR[i] = (255 * histCumR[i]) / (originalImage.getWidth() * originalImage.getHeight());
            transfG[i] = (255 * histCumG[i]) / (originalImage.getWidth() * originalImage.getHeight());
            transfB[i] = (255 * histCumB[i]) / (originalImage.getWidth() * originalImage.getHeight());
        }

        for (int i = 0; i < originalImage.getWidth(); i++) {
            for (int j = 0; j < originalImage.getHeight(); j++) {
                rgb = getRGB(originalImage.getRGB(i, j));
                workImage.setRGB(i, j, new Color(transfR[rgb[0]], transfG[rgb[1]], transfB[rgb[2]]).getRGB());
            }
        }
        return workImage;
    }

    /**
     * Changes the input color to a similar color, which is not reserved.
     *
     * @param inputColor - the input color
     * @param objColorsList the obj colors list
     * @return - a color which is not reserved and it is similar to the input
     * one
     */
    public static Color changeColor(Color inputColor, List<Color> objColorsList) {
        int[] rgb = getRGB(inputColor.getRGB());
        Random rand = new Random();
        int randRange = 60;

        // change the color
        for (int index = 0; index < rgb.length; index++) {

            // add random values in the range [-0.5*range; +0.5*range)
            rgb[index] += (rand.nextInt(randRange) - (int) (randRange * 0.5f));

            // limit the value to byte
            rgb[index] = limit(0, rgb[index], 255);
        }

        // set the new color 
        Color color = new Color(rgb[0], rgb[1], rgb[2]);

        // make sure that the color is not reserved as well
        if (objColorsList.contains(color) || Constants.COLORS_LIST.contains(color)) {
            changeColor(color, objColorsList);
        }

        return color;
    }

    /**
     * Parse the given file path and name in order to extract just the file name
     * (with or without the extension, depending on the choice).
     *
     * @param fileNamePath the original file name, including its path
     * @param withExtension true if the returned name should include the file
     * extension; false if just the name of the file alone should be provided
     * @return the name of the file (with or without extension - as specified),
     * without its path
     */
    public static String getFileName(String fileNamePath, boolean withExtension) {
        // extract the name of the file and save it to create a folder there and store the info
        String[] tempStr;
        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) {
            tempStr = fileNamePath.split(File.separator + File.separator);
        } else {
            tempStr = fileNamePath.split(File.separator);
        }

        if (withExtension) {
            return tempStr[tempStr.length - 1];
        } else {
            // get the name of the file without extension
            return tempStr[tempStr.length - 1].substring(0, tempStr[tempStr.length - 1].lastIndexOf('.'));
        }
    }

    /**
     * Adjusts the contrast of the image - NOT TESTED yet - to be checked and
     * optimised.
     *
     * @param workImage the work image
     * @param min the min
     * @param max the max
     * @return buffered image
     */
    public static BufferedImage contrast(BufferedImage workImage, int min, int max) {
        int[] rgb;
        int minimR = 255;
        int maximR = 0;
        int minimG = 255;
        int maximG = 0;
        int minimB = 255;
        int maximB = 0;

        // find the minimum and maximum values for R G and B
        for (int i = 0; i < workImage.getWidth(); i++) {
            for (int j = 0; j < workImage.getHeight(); j++) {
                rgb = getRGB(workImage.getRGB(i, j));

                minimR = Math.min(minimR, rgb[0]);
                minimG = Math.min(minimG, rgb[1]);
                minimB = Math.min(minimB, rgb[2]);

                maximR = Math.max(maximR, rgb[0]);
                maximG = Math.max(maximG, rgb[1]);
                maximB = Math.max(maximB, rgb[2]);
            }
        }

        // adjust the intensities
        for (int i = 0; i < workImage.getWidth(); i++) {
            for (int j = 0; j < workImage.getHeight(); j++) {
                rgb = getRGB(workImage.getRGB(i, j));
                int intensR = (rgb[0] - minimR) * (max - min) / (maximR - minimR) + min;
                int intensG = (rgb[1] - minimG) * (max - min) / (maximG - minimG) + min;
                int intensB = (rgb[2] - minimB) * (max - min) / (maximB - minimB) + min;
                workImage.setRGB(i, j, new Color(intensR, intensG, intensB).getRGB());
            }
        }
        return workImage;
    }

    /**
     * Extract the RGB information from a pixel.
     *
     * @param pixel - the packed value of the color of the pixel
     * @return - returns an array which represents in this order the values of
     * R, G and B components of the pixel
     */
    public static int[] getRGB(int pixel) {
        int[] rgb = new int[3];
        rgb[0] = ((pixel & 0x00FF0000) >>> 16);   //red color
        rgb[1] = ((pixel & 0x0000FF00) >>> 8);    //green color
        rgb[2] = (pixel & 0x000000FF);            //blue color
        return rgb;
    }

    /**
     * Returns a list of all the files (jpg and png) contained by the specified
     * folder.
     *
     * @param folder - the folder for which the list of image files is wanted
     * @return - an array of strings representing all the image files found
     * inside
     */
    public static List<String> listFilesForFolder(final File folder) {
        List<String> filesList = new ArrayList<>();

        if (folder.length() > 0) {
            for (final File fileEntry : folder.listFiles()) {
                if (fileEntry.isDirectory()) {
                    listFilesForFolder(fileEntry);
                } else if (fileEntry.getName().endsWith(".jpg") || fileEntry.getName().endsWith(".JPG")
                        || fileEntry.getName().endsWith(".png") || fileEntry.getName().endsWith(".PNG")) {
                    filesList.add(fileEntry.getAbsolutePath());
                    LOG.trace(fileEntry.getAbsolutePath());
                }
            }
        }

        return filesList;
    }

    /**
     * Fill in a matrix with a default value.
     *
     * @param width - the width of the matrix
     * @param height - the height of the matrix
     * @param value - the value to be filled in
     * @return - the matrix filled in with the wanted default value
     */
    public static int[][] initMatrix(int width, int height, int value) {
        int[][] array = new int[height][width];

        for (int y = 0; y < height; y++) {
            Arrays.fill(array[y], value);
        }

        return array;
    }

    /**
     * Fill in a matrix with a default value.
     *
     * @param width - the width of the matrix
     * @param height - the height of the matrix
     * @param value - the value to be filled in
     * @return - the matrix containing the wanted default value
     */
    public static float[][] initMatrix(int width, int height, float value) {
        float[][] array = new float[height][width];

        for (int y = 0; y < height; y++) {
            Arrays.fill(array[y], value);
        }

        return array;
    }

    /**
     * Generate a custom mouse icon to have it as preview while drawing
     * scribbles.
     *
     * @param brushOpt - the configuration of the icon to be generated
     * @param actionType - the id of the object for which the icon is created
     * @param objColor the obj color
     * @return - the cursor to be displayed by the mouse
     */
    public static Cursor generateCustomIcon(BrushOptions brushOpt, int actionType, Color objColor) {
        Cursor cursor;
        if (brushOpt.getBrushSize() > Constants.MIN_MOUSE_BRUSH_ICON) {
            Toolkit toolkit = Toolkit.getDefaultToolkit();

            Dimension dim = toolkit.getBestCursorSize(brushOpt.getBrushSize(), brushOpt.getBrushSize());
            BufferedImage buffered = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = buffered.createGraphics();
            float density = brushOpt.getBrushDensity();
            Random rand = new Random();
            byte[][] circleMask = getCircleMask(brushOpt.getBrushSize());  // the mask making the mouse pointer a circle

            if (brushOpt.getBrushSize() <= 5) {
                density = 1.0f;
            } else if (brushOpt.getBrushSize() > 5 && brushOpt.getBrushSize() < 10) {
                density = Math.max(0.5f, density);
            }

            // objects smaller than 0 are for erase purposes and the density is always full
            if (actionType < 0) {
                density = 1.0f;
            }

            // draw the inner part of the brush, the one which will show after a click
            g.setColor(getDrawingColor(actionType, objColor));

            for (int x = 1; x <= brushOpt.getBrushSize(); x++) {
                for (int y = 1; y <= brushOpt.getBrushSize(); y++) {
                    float randFloat = rand.nextFloat();
                    if ((randFloat < density) && (circleMask[x - 1][y - 1] == (byte) 1)) {
                        g.drawLine(x, y, x, y);
                    }
                }
            }

            // draw a white box around the brush for better visibility
            g.setColor(Color.white);

            g.drawOval(0, 0, brushOpt.getBrushSize() + 1, brushOpt.getBrushSize() + 1);

            g.dispose();

            int drawlLocation = (int) (brushOpt.getBrushSize() * 0.5f);
            cursor = toolkit.createCustomCursor(buffered, new Point(drawlLocation, drawlLocation), "myCursor");

        } else {
            cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
        }

        return cursor;
    }

    /**
     * Choose the best background for text: white for dark text, black for
     * bright text.
     *
     * @param color - the color for which the background shall be computed.
     * @param transparency - the wanted transparency for the returned color
     * @return - the adviced color for the background based on luminance
     */
    public static Color getContrastColor(Color color, int transparency) {
        // no precision needed here
        int y = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;

        return ((y >= 128) ? new Color(0, 0, 0, transparency) : new Color(255, 255, 255, transparency));
    }

    /**
     * Returns the color: 0 = red (bkg), 1 = the color of the current object
     * (object), other: white (eraser).
     *
     * @param id - the value for which the color has to be established
     * @param objColor - the color of the object (object = not bkg , not erase)
     * @return - the corresponding color 0 = red, 1 = the color of the current
     * object, other = white
     */
    public static Color getDrawingColor(int id, Color objColor) {
        switch (id) {
            case 0:
                return Color.red;
            case 1:
                return objColor;
            default:
                return Color.white;
        }
    }

    /**
     * Maps the objects to colors.
     *
     * @param id - the id of the object, in the database
     * @return - the color of the object with the specified id
     */
    public static Color getColorOfObjByID(long id) {
        // compute the index of the color based on the object id
        int colorIndex = (int) (id % Constants.COLORS_LIST.size());

        return Constants.COLORS_LIST.get(colorIndex);
    }

    /**
     * Limit value between specified min and max values
     * <p>
     * value = [min,max]
     *
     * @param min min value
     * @param value value to be limited
     * @param max max value
     * @return returns the bounded value
     */
    public static int limit(int min, int value, int max) {
        return (value < min) ? min : ((value > max) ? max : value);
    }

    /**
     * Limit value between specified min and max values
     * <p>
     * value = [min,max]
     *
     * @param min min value
     * @param value value to be limited
     * @param max max value
     * @return returns the bounded value
     */
    public static long limit(long min, long value, long max) {
        return (value < min) ? min : ((value > max) ? max : value);
    }

    /**
     * Computes the CRC32 for a specified file
     *
     * @param fileName file for which the CRC32 is calculated
     * @return CRC32 for the specified file in String format
     */
    public static String CRC32(String fileName) {
        try (FileInputStream fi = new FileInputStream(fileName)) {

            /* allocates a buffer of 1MB for reading the video file
               this size seems to provide the fastest runtime
             */
            byte[] b = new byte[1024 * 1024];
            int len;
            CRC32 crc = new CRC32();

            while ((len = fi.read(b)) >= 0) {
                crc.update(b, 0, len);
            }

            // create the hash
            return (new BigInteger(Long.toString(crc.getValue())).toString(16));
        } catch (FileNotFoundException ex) {
            LOG.error("File {} not found", fileName);
            LOG.debug("File {} not found exception {}", fileName, ex);
        } catch (IOException ex) {
            LOG.error("File {} not accessible", fileName);
            LOG.debug("File {} not accessible exception {}", fileName, ex);
        }
        return null;
    }

    /**
     * Filter the object map array.
     * <p>
     * The filtering is done by sliding a window over the input array and
     * compute the sum of the values in the window (sum of 1s). If the sum is
     * smaller than a threshold, make the middle value of the window 0, else 1.
     * <p>
     * The filter aims to keep the white pixels which are in a neighbourhood of
     * other white pixels and discard the lonely pixels.
     *
     * @param objMap - the object map to be filtered
     */
    public static void filterObjectMap(byte[][] objMap) {
        if (objMap == null) {
            return;
        }

        // run N times the filtering algorithm
        // for (int steps = 0; steps < 10; steps++) {
        int filterSize = 3;
        byte[][] inputMap = new byte[objMap.length][objMap[0].length];

        for (int line = 0; line < objMap.length; line++) {
            inputMap[line] = objMap[line].clone();
        }

        for (int y = 0; y < inputMap[0].length - 2; y++) {
            for (int x = 0; x < inputMap.length - 2; x++) {
                int sum = 0;
                for (int j = 0; j < filterSize; j++) {
                    for (int i = 0; i < filterSize; i++) {
                        sum += inputMap[x + i][y + j];
                    }
                }

                if (sum < Math.ceil(filterSize * filterSize * 0.5)) {
                    objMap[x + (int) (filterSize * 0.5f)][y + (int) (filterSize * 0.5f)] = 0;
                } else {
                    objMap[x + (int) (filterSize * 0.5f)][y + (int) (filterSize * 0.5f)] = 1;
                }
            }
        }

        // filter the first and last line
        for (int x = 0; x < inputMap.length - 2; x++) {
            int sum0 = 0;   // sum for the first line
            int sumN = 0;   // sum for the last line
            for (int j = 0; j < filterSize - 1; j++) {
                for (int i = 0; i < filterSize; i++) {
                    sum0 += inputMap[x + i][j];
                    sumN += inputMap[x + i][inputMap[0].length - 1 - j];
                }
            }
            // filter first line
            if (sum0 < Math.ceil((filterSize - 1) * filterSize * 0.5)) {
                objMap[x + (int) (filterSize * 0.5f)][0] = 0;
            } else {
                objMap[x + (int) (filterSize * 0.5f)][0] = 1;
            }

            // filter last line
            if (sumN < Math.ceil((filterSize - 1) * filterSize * 0.5)) {
                objMap[x + (int) (filterSize * 0.5f)][inputMap[0].length - 1] = 0;
            } else {
                objMap[x + (int) (filterSize * 0.5f)][inputMap[0].length - 1] = 1;
            }
        }

        // filter the first and last column
        for (int y = 0; y < inputMap[0].length - 2; y++) {
            int sum0 = 0;   // sum for the first column
            int sumN = 0;   // sum for the last column
            for (int j = 0; j < filterSize; j++) {
                for (int i = 0; i < filterSize - 1; i++) {
                    sum0 += inputMap[i][y + j];
                    sumN += inputMap[inputMap.length - 1 - i][y + j];
                }
            }

            // filter first column
            if (sum0 < Math.ceil(filterSize * (filterSize - 1) * 0.5)) {
                objMap[0][y + (int) (filterSize * 0.5f)] = 0;
            } else {
                objMap[0][y + (int) (filterSize * 0.5f)] = 1;
            }

            // filter last column
            if (sumN < Math.ceil(filterSize * (filterSize - 1) * 0.5)) {
                objMap[inputMap.length - 1][y + (int) (filterSize * 0.5f)] = 0;
            } else {
                objMap[inputMap.length - 1][y + (int) (filterSize * 0.5f)] = 1;
            }
        }

        // filter the corners
        // left - top
        int sum = objMap[0][0] + objMap[0][1] + objMap[1][0] + objMap[1][1];
        objMap[0][0] = (byte) ((sum < 3) ? 0 : 1);

        // right - top
        sum = objMap[inputMap.length - 1][0] + objMap[inputMap.length - 1][1] + objMap[inputMap.length - 2][0] + objMap[inputMap.length - 2][1];
        objMap[inputMap.length - 1][0] = (byte) ((sum < 3) ? 0 : 1);

        // left - bottom
        sum = objMap[0][inputMap[0].length - 1] + objMap[0][inputMap[0].length - 2] + objMap[1][inputMap[0].length - 1] + objMap[1][inputMap[0].length - 2];
        objMap[0][inputMap[0].length - 1] = (byte) ((sum < 3) ? 0 : 1);

        // right - bottom
        sum = objMap[inputMap.length - 1][inputMap[0].length - 1]
                + objMap[inputMap.length - 2][inputMap[0].length - 1]
                + objMap[inputMap.length - 1][inputMap[0].length - 2]
                + objMap[inputMap.length - 2][inputMap[0].length - 2];
        objMap[inputMap.length - 1][inputMap[0].length - 1] = (byte) ((sum < 3) ? 0 : 1);

    }

    /**
     * Computes the integer logarithm in base 2 of the input number.
     *
     * @param n - the input number
     * @return - the integer value of the log base 2 of n
     */
    public static int log2N(int n) {
        if (n == 0) {
            return 0;
        }
        return (31 - Integer.numberOfLeadingZeros(n));
    }

    /**
     * A sigmoid function is a mathematical function having an "S" shaped curve
     * (sigmoid curve).
     *
     * @param value the value/point for which the sigmoid function has to be
     * computed
     * @return - the value of the sigmoid function in the given point
     */
    public static double sigmoidFunction(double value) {
        return 1.0 / (1.0 + Math.exp(-value));
    }

    /**
     * Based on the size of the generated box, compute a border size to be added
     * to the displayed image.
     *
     * @param box - the original box size
     * @param borderPercentageWidth - the percentage of border to be added on
     * width
     * @param borderPercentageHeight - the percentage of border to be added on
     * height
     * @param minBorder - the min border in pixels
     * @param frameSize - the max size the box can have, after being bordered
     * @return - the size of the possible border for the input box
     */
    public static BoxLRTB getBorderedSize(Rectangle box,
            double borderPercentageWidth,
            double borderPercentageHeight,
            int minBorder,
            Dimension frameSize) {

        int borderWidth = (int) (borderPercentageWidth * box.width);
        int borderHeight = (int) (borderPercentageHeight * box.height);

        // if the border is too small, make it at least 10 pixels
        if (borderWidth < minBorder) {
            borderWidth = minBorder;
        }

        if (borderHeight < minBorder) {
            borderHeight = minBorder;
        }

        // set the border as 20% of the size of the box
        BoxLRTB borderSize = new BoxLRTB(borderWidth, borderHeight);

        // limit the bordering in such way as to not get out of the image
        if ((box.x - borderWidth) < 0) {
            borderSize.setxLeft(box.x);
        }

        if ((box.y - borderHeight) < 0) {
            borderSize.setyTop(box.y);
        }

        if ((box.x + box.width + borderWidth) > frameSize.width) {
            borderSize.setxRight(frameSize.width - (box.x + box.width));
        }

        if ((box.y + box.height + borderHeight) > frameSize.height) {
            borderSize.setyBottom(frameSize.height - (box.y + box.height));
        }

        // return the new size of the border to be added
        return borderSize;
    }

    /**
     * Based on the size of the generated box, compute a border size to be added
     * to the displayed image.
     *
     * @param box - the original box size
     * @param targetBorder - the wanted border in pixels
     * @param frameSize - the max size the box can have, after being bordered
     * @return - the size of the possible border for the input box
     */
    public static BoxLRTB getBorderedSize(Rectangle box,
            int targetBorder,
            Dimension frameSize) {

        // set the border as 20% of the size of the box
        BoxLRTB borderSize = new BoxLRTB(targetBorder, targetBorder);

        // limit the bordering in such way as to not get out of the image
        if ((box.x - targetBorder) < 0) {
            borderSize.setxLeft(box.x);
        }

        if ((box.y - targetBorder) < 0) {
            borderSize.setyTop(box.y);
        }

        if ((box.x + box.width + targetBorder) > frameSize.width) {
            borderSize.setxRight(frameSize.width - (box.x + box.width));
        }

        if ((box.y + box.height + targetBorder) > frameSize.height) {
            borderSize.setyBottom(frameSize.height - (box.y + box.height));
        }

        // return the new size of the border to be added
        return borderSize;
    }

    /**
     * Check if it is possible to connect to the specified server.
     *
     * @param serverAddress the address of the server where the connection shall
     * be checked
     * @param portNo the number of the port on which the connection shall be
     * done
     * @return true if the server is reachable and false if the address and port
     * does not represent a valid path to a server
     */
    public static boolean isServerOnline(String serverAddress, int portNo) {
        try {
            InetSocketAddress socketAddress = new InetSocketAddress(serverAddress, portNo);
            // conect to the server
            try (Socket socket = new Socket()) {
                // conect to the server
                socket.connect(socketAddress, 100);
            }
            return true;
        } catch (Exception e) {
            LOG.error("Communication with the server couldn't be established");
            LOG.debug("Communication with the server couldn't be established {}", e);
            return false;
        }
    }

    /**
     * Generates a mask, representing a circle, of the size specified by the
     * size parameter. The mask contains a circle with 1s and background (as
     * 0s).The mask is to be used in the drawing of the mouse brush and for the
     * scribbles generation.
     * <p>
     * <p>
     * x = cx + r * cos(a)
     * <p>
     * y = cy + r * sin(a)
     * <p>
     * Where r is the radius, cx,cy the origin, and a the angle (in radian).
     *
     * @param size - the size of the mask
     * @return - the mask representing a filled circle, of radius = size / 2
     */
    public static byte[][] getCircleMask(int size) {
        switch (size) {
            case 3:
                return CircleTemplate.CIRCLE_MASK_3X3;

            case 4:
                return CircleTemplate.CIRCLE_MASK_4X4;

            case 5:
                return CircleTemplate.CIRCLE_MASK_5X5;

            case 6:
                return CircleTemplate.CIRCLE_MASK_6X6;

            case 7:
                return CircleTemplate.CIRCLE_MASK_7X7;

            case 8:
                return CircleTemplate.CIRCLE_MASK_8X8;

            case 9:
                return CircleTemplate.CIRCLE_MASK_9X9;

            case 10:
                return CircleTemplate.CIRCLE_MASK_10X10;

            case 11:
                return CircleTemplate.CIRCLE_MASK_11X11;

            case 12:
                return CircleTemplate.CIRCLE_MASK_12X12;

            case 13:
                return CircleTemplate.CIRCLE_MASK_13X13;

            case 14:
                return CircleTemplate.CIRCLE_MASK_14X14;

            case 15:
                return CircleTemplate.CIRCLE_MASK_15X15;

            case 16:
                return CircleTemplate.CIRCLE_MASK_16X16;

            case 17:
                return CircleTemplate.CIRCLE_MASK_17X17;

            case 18:
                return CircleTemplate.CIRCLE_MASK_18X18;

            case 19:
                return CircleTemplate.CIRCLE_MASK_19X19;

            case 20:
                return CircleTemplate.CIRCLE_MASK_20X20;

            case 21:
                return CircleTemplate.CIRCLE_MASK_21X21;

            case 22:
                return CircleTemplate.CIRCLE_MASK_22X22;

            case 23:
                return CircleTemplate.CIRCLE_MASK_23X23;

            case 24:
                return CircleTemplate.CIRCLE_MASK_24X24;

            case 25:
                return CircleTemplate.CIRCLE_MASK_25X25;

            case 26:
                return CircleTemplate.CIRCLE_MASK_26X26;

            case 27:
                return CircleTemplate.CIRCLE_MASK_27X27;

            case 28:
                return CircleTemplate.CIRCLE_MASK_28X28;

            case 29:
                return CircleTemplate.CIRCLE_MASK_29X29;

            case 30:
                return CircleTemplate.CIRCLE_MASK_30X30;

            default:
                return CircleTemplate.CIRCLE_MASK_5X5;
        }
    }

    /**
     * Returns the capitalised string
     *
     * @param name string to be capitalised
     * @return the capitalised string
     */
    public static String capitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }

        return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
    }

    /**
     * Create the directories structure for the given path.
     *
     * @param path - the path which has to be created if it does not exist
     */
    public static void createFolderPath(String path) {
        // create the directory structure
        File file = new File(path + "text.txt");
        file.getParentFile().mkdirs();
    }

    /**
     * Check if the extension of the file is one of the wanted. Only files which
     * represent images can be loaded.
     *
     * @param fileName the name of the current file in the directory structure
     * @param collection the collection which has to be checked for matching
     * (the list of extensions)
     * @return true if the file is a wanted type; false if the file represents a
     * not wanted format
     */
    public static boolean checkExtension(String fileName, List<String> collection) {
        // convert the name of the file to lower case, to avoid missing file types (jpg vs JPG)        
        return collection.stream().anyMatch((String extension) -> fileName.toLowerCase().endsWith("." + extension));
    }

    /**
     * Get an input string and replace all non alpha numeric characters with a
     * given string.
     *
     * @param inputStr the string to be changed
     * @param replaceStr the character to replace any non alpha numeric
     * character in the input string
     * @return string
     */
    public static String replaceNonAlphaNum(String inputStr, String replaceStr) {
        String output = inputStr.replaceAll("[^A-Za-z0-9 ]", replaceStr);

        // if the output string ends with a replace string, remove it
        if (output.endsWith(replaceStr)) {
            output = output.substring(0, output.length() - replaceStr.length());
        }

        return output;
    }

    /**
     * Converts a number representing bytes to the biggest possible multiple.
     *
     * @param bytes the number to be converted
     * @param si true if the display system should be decimal (kilobyte - kB,
     * megabyte - MB etc.); false if the display system should be binary
     * (kibibyte - KB, mebibyte - MiB, gibibyte - GiB etc.)
     * @return the transformation of the number to the biggest metric
     */
    public static String humanReadableByteCount(long bytes, boolean si) {
        int unit = si ? 1000 : 1024;
        if (bytes < unit) {
            return bytes + " B";
        }
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }

    /**
     * Mirror the image/buffer data
     *
     * @param image the image containing the pixel data which has to be
     * mirrored. This function will alter the bytes of the input image, so that
     * after this function the input image will contain the mirrored information
     */
    public static void mirrorImage(BufferedImage image) {
        byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        byte[] mirror = new byte[bgr.length];
        final int multiplier = 3; // BGR

        for (int j = 0; j < image.getHeight(); j++) {
            for (int i = 0; i < image.getWidth(); i++) {
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 0)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 0)]; // R
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 1)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 1)]; // G
                mirror[(j * (image.getWidth() * multiplier)) + ((((image.getWidth() - 1) - i) * multiplier) + 2)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 2)]; // B
            }
        }

        System.arraycopy(mirror, 0, bgr, 0, image.getWidth() * image.getHeight() * multiplier);
    }

    /**
     * Flip vertically the image
     *
     * @param image the image containing the pixel data which has to be flipped
     * vertically. This function will alter the bytes of input image, so that
     * after this function the input image will contain the flipped information
     */
    public static void flipVerticallyImage(BufferedImage image) {
        byte[] bgr = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        byte[] flipped = new byte[bgr.length];
        final int multiplier = 3; // BGR

        for (int j = 0; j < image.getHeight(); j++) {
            for (int i = 0; i < image.getWidth(); i++) {
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 0)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 0)]; // R
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 1)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 1)]; // G
                flipped[((image.getHeight() - 1) - j) * (image.getWidth() * multiplier) + (i * multiplier + 2)] = bgr[(j * (image.getWidth() * multiplier)) + (i * multiplier + 2)]; // B
            }
        }

        System.arraycopy(flipped, 0, bgr, 0, image.getWidth() * image.getHeight() * multiplier);
    }

    /**
     * Export the original image and the result image, concatenated, for demo
     * purpose.
     *
     * @param leftImage the image to be displayed on the left side of the output
     * image
     * @param rightImage the image to be displayed on the right side of the
     * output image
     * @param outputPath the path where to output the image
     * @param fileType the file type/extension for the export
     * @param bkgColor the color used for the background between the images
     */
    public static void exportTwoImages(BufferedImage leftImage, BufferedImage rightImage, String outputPath, String fileType, Color bkgColor) {
        // inform the user if the images are different
        if ((leftImage.getWidth() != rightImage.getWidth()) || (leftImage.getHeight() != rightImage.getHeight())) {
            LOG.info("The width or height of the exported images differs! left: {}x{}, right: {}x{}",
                    leftImage.getWidth(), leftImage.getHeight(), rightImage.getWidth(), rightImage.getHeight());
        }

        // compute the size of the result image, adding also some space between them
        int width = leftImage.getWidth() + 50 + rightImage.getWidth();      // 50 represents the inset between the images
        int height = Math.max(leftImage.getHeight(), rightImage.getHeight());

        // generate a new image composed of proposed images
        BufferedImage resultImg = new BufferedImage(width, height, leftImage.getType());
        Graphics2D g2DResImg = (Graphics2D) resultImg.getGraphics();

        g2DResImg.setColor(bkgColor);
        g2DResImg.fillRect(0, 0, width, height);

        // put the left image
        g2DResImg.drawImage(leftImage, 0, 0, leftImage.getWidth(), leftImage.getHeight(), null);

        // add the right image
        g2DResImg.drawImage(rightImage,
                resultImg.getWidth() - rightImage.getWidth() - 1,
                0, rightImage.getWidth(), rightImage.getHeight(), null);

        g2DResImg.dispose();

        // export the image
        ExportImage.exportImage(resultImg, outputPath, fileType);
    }

    /**
     * Searches the list of radio buttons of the given button group, for the
     * specified text. When found, the radio button is selected.
     *
     * @param group the group being searched
     * @param text the text being searched
     */
    public static void selectRadioButton(ButtonGroup group, String text) {
        // get the list of elements of the button group
        Enumeration<AbstractButton> elements = group.getElements();

        // search in the button group for the user selected value
        while (elements.hasMoreElements()) {
            AbstractButton button = elements.nextElement();

            // if the selected value is found, select the corresponding radio button
            if (button.getActionCommand().equals(text)) {
                group.setSelected(button.getModel(), true);
                break;
            }
        }
    }

    /**
     * Allow the user to choose a path where to save its files and set it in the
     * path text field.
     *
     * @param currentDirectory the directory where the browser should open
     * @param parent the parent frame/dialog of the browser dialog
     * @return the path selected by the user
     */
    public static String browseFilePath(String currentDirectory, Component parent) {
        String selectedPath = "";

        JFileChooser chooser = new JFileChooser();

        // set the initial directory
        chooser.setCurrentDirectory(new File(currentDirectory));

        // allow only the choosing of directories
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        if (chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
            // set the chosen text as the path where to save the file
            try {
                selectedPath = chooser.getSelectedFile().getCanonicalPath();
            } catch (IOException ex) {
                java.util.logging.Logger.getLogger(ExportImagesGUI.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return selectedPath;
    }

    /**
     * Check if the sent text equals to the predefined invalid attribute. When
     * this is true, it means the attribute was not correctly chosen.
     *
     * @param text the text where to append the invalid attribute
     * @param attribute the attribute to be checked
     * @param attributeName the name of the attribute being checked
     */
    public static void checkAttributeValid(StringBuilder text, String attribute, String attributeName) {
        if (Constants.INVALID_ATTRIBUTE_TEXT.equalsIgnoreCase(attribute)) {
            text.append(attributeName);
            text.append(", ");
        }
    }

    /**
     * Asks the user to correct the attributes. If the user chooses to correct
     * them, the application does not continue with the saving of data; it will
     * cancel the process and go back to the normal state.
     *
     * @param invalidAttribute a string with the invalid attributes (is any)
     * @param editAttribAvailable true if the edit of attributes is allowed
     * @param parent the parent component of the message window
     * @return the choice of the user, whether he wants to save the data as it
     * is or the saving of data shall not be done yet, because the user wants to
     * correct it (or the attributes)
     */
    public static int messageAttributesSet(String invalidAttribute, boolean editAttribAvailable, Component parent) {
        // if the application is running offline, show the path to the attributes
        String attributesPathMsg = editAttribAvailable ? "If you do not find the wanted attributes, please go to Menu->Options->Attributes<br>"
                + "and define the missing attributes!<br><br>" : "";

        // ask the user to correct the missing attributes
        String message = "<html>There are invalid attributes (or attributes which were not yet set):<br>"
                + invalidAttribute.toLowerCase(Locale.ENGLISH) + ".<br><br>"
                + "<font color=#4d0000>"
                + "<b>Do you want to correct them now?</b><br>"
                + "</font>"
                + "<i>(If you choose No, the current attributes will be saved as ground truth)<br>"
                + attributesPathMsg
                + "</i></html>";

        // display the warning dialog
        if (editAttribAvailable) {
            Object[] options = {"Yes", "No", "Edit Attributes"};
            return Messages.showQuestionMessage(parent, message, "Invalid attributes!!!", JOptionPane.YES_NO_CANCEL_OPTION, options, options[0]);
        } else {
            Object[] options = {"Yes", "No"};
            return Messages.showQuestionMessage(parent, message, "Invalid attributes!!!", JOptionPane.YES_NO_OPTION, options, options[0]);
        }
    }

    /**
     * Opens the folder or file from the specified path, in an explorer window
     * or in a default file specific application.
     *
     * @param path the path to the wanted folder/file
     */
    public static void openFileFolderInExplorer(String path) {
        try {
            if (new File(path).exists()) {
                Desktop.getDesktop().open(new File(path));
            }
        } catch (IOException e) {
            LOG.debug("The open file/folder in explorer failed!");
            LOG.debug("The open file/folder in explorer failed! {}", e);
        }
    }

    /**
     * Check if the point is inside the bounds.
     *
     * @param point the point to be checked if it is inside the bounds
     * @param bounds the max values that the point's x and y can have
     * @return return true if the point is inside the bounds or false otherwise
     */
    public static boolean checkBounds(Point point, Dimension bounds) {
        return (point.x >= 0 && point.x < bounds.width)
                && point.y >= 0 && point.y < bounds.height;
    }

    /**
     * Clone the input tree; create a new tree, which is the copy of the input
     * one.
     *
     * @param rootNode the root node of the tree to be copied
     * @return a new tree, with new nodes, which represent the deep copy of the
     * input tree
     */
    public static CustomTreeNode cloneTree(CustomTreeNode rootNode) {
        // the list of nodes of the input tree
        List<CustomTreeNode> nodesList = new ArrayList<>();
        // the list of nodes of the cloned tree
        List<CustomTreeNode> clonedList = new ArrayList<>();
        // the new tree, representing the deep copy of the input one
        CustomTreeNode clonedTree = new CustomTreeNode(rootNode.getRoot());

        // in the nodes list, add the first nodes
        nodesList.add(rootNode);
        clonedList.add(clonedTree);

        // keep track of the size of the list of nodes
        int listLength = nodesList.size();

        // take every node and create copies of the children node
        for (int index = 0; index < listLength; index++) {
            CustomTreeNode node = nodesList.get(index);
            // add the list of children to the list of nodes to be copied
            List<CustomTreeNode> children = node.getChildrenNodes();
            nodesList.addAll(children);
            // by adding new chi
            listLength += children.size();

            // create new nodes, based on the children of the current node
            clonedList.get(index).addChildrenStr(node.getChildren());
            clonedList.addAll(clonedList.get(index).getChildrenNodes());
        }

        return clonedTree;
    }

    /**
     * Check if a string is numeric.
     *
     * @param str the string to be checked
     * @return true if the string contains just number specific characters:
     * 0...9 , -
     */
    public static boolean isNumeric(String str) {
        return str.matches("-?\\d+(\\.\\d+)?");  //match a number with optional '-' and decimal.
    }

    /**
     * Compute the distance from the point P to the line defined by the points A
     * and B.
     *
     * https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
     *
     * @param a the point A of the line
     * @param b the point B of the line
     * @param p the point from where the distance is computed
     * @return
     */
    public static float distPointToLine(Point a, Point b, Point p) {
        float nominator = (b.y - a.y) * p.x - (b.x - a.x) * p.y + b.x * a.y - b.y * a.x;
        float denominator = (b.y - a.y) * (b.y - a.y) + (b.x - a.x) * (b.x - a.x);

        return (float) (Math.abs(nominator) / Math.sqrt(denominator));

    }

    /**
     * In analytic geometry, the distance between two points of the xy-plane can
     * be found using the distance formula. The distance between (x1, y1) and
     * (x2, y2) is given by:
     *
     * d = sqrt((Δx)^2 + (Δy)^2) = sqrt((x2 − x1)^2 + (y2 − y1)^2)
     *
     * @param a the point A of the segment
     * @param b the point B of the segment
     * @return The length of the segment formed by the two points, A and B.
     */
    public static float distPointToPoint(Point a, Point b) {
        return (float) Math.sqrt((b.y - a.y) * (b.y - a.y) + (b.x - a.x) * (b.x - a.x));
    }

    /**
     *
     * @param a the point A of the triangle
     * @param b the point B of the triangle
     * @param v the point V of the triangle
     * @return
     */
    public static float distPointToSegment(Point a, Point b, Point v) {
        float distVA = distPointToPoint(a, v);
        float distVB = distPointToPoint(b, v);
        float distAB = distPointToPoint(a, b);

        return ((distVA + distVB) / distAB);

    }

    /**
     * Create a deep copy of the polygon.
     *
     * @param poly the polygon to be replicated
     * @return a new polygon, copy of the initial one
     */
    public static Polygon deepCopyPoly(Polygon poly) {
        return new Polygon(poly.xpoints, poly.ypoints, poly.npoints);
    }

    /**
     * Open and decode a binary greyscale image. The file shall contain just the
     * bytes of the image.
     *
     * @param chosenFileName the name of the file to be opened
     * @param width the width of the image
     * @param height the height of the image
     * @return the image from the specified file
     */
    public static BufferedImage getBinaryByteImage(String chosenFileName, int width, int height) {
        int value, x, y, pos = 0;
        // create the image
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);

        try (DataInputStream in = new DataInputStream(new FileInputStream(chosenFileName))) {
            while (in.available() > 0) {
                // read the image byte by byte
                value = in.readByte();
                
                // the data is uint8
                if (value < 0) {
                    value += 255;
                }
                // compute the position in the image
                y = pos / width;
                x = pos % width;
                bi.setRGB(x, y, new Color(value, value, value).getRGB());
                
                pos++;
            }
        } catch (IOException ex) {
            java.util.logging.Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
        }

        return bi;
    }
} public class BoxEdit extends EditWindow {

    /**
     * The box which represents the displayed image. It includes the object
     * outer box and the border.
     */
    protected Rectangle displayBox;

    /**
     * The size of the border for the image. The number of extra pixels added to
     * the labeled image, in each direction.
     */
    private transient BoxLRTB borderSize;

    /**
     * The selected object for being edited.
     */
    protected transient Objects currentObject;

    /**
     *
     * @param parent the parent component of the dialog
     * @param frameImage the original image, in original size
     * @param currentObj the object being segmented
     * @param objectAttributes the list of object attributes: type, class, value
     * @param actionOwner the scope of the dialog: create new box, edit existing
     * one
     * @param objColorsList the list of already used colors (for other objects)
     * @param userPreferences user preferences regarding application
     * configuration
     */
    public BoxEdit(Frame parent,
            BufferedImage frameImage,
            Objects currentObj,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences) {
        super(parent, frameImage, objectAttributes, actionOwner, objColorsList,
                userPreferences, currentObj.getUserPreference());
        this.currentObject = currentObj;
        // save the original object position in order to be able to reset the changes
        this.origObjPos = new Rectangle(currentObj.getOuterBBox());

        // save the initial color of the object
        this.objectColor = currentObject.getColor();

        // init the border size based on computation or user preferences
        initObjPreviewBorder();

        // get the selected object preview image
        displayImage();

        initOtherVariables();

        enableSemanticOptions(false);
        enableBoxOptions(true);

        prepareFrame();
    }

    private void initOtherVariables() {
        // display a label with the object id
        displayObjId();

        // add the size of the object        
        setFrameTitle();

        // set object type specific properties
        applyObjProperties();
    }

    @Override
    protected void saveObject() {
        if (!isObjectAttributesSet()) {
            // the user wants to correct the data, do not close the application!
            return;
        }

        // save the object attributes
        saveObjInfo();

        // notify the controller that the attributes were saved
        observable.notifyObservers(actionOwner);

        closeWindow();
    }

    @Override
    protected void cancelObject() {
        // notify to track the object if the box is in edit mode
        if (actionOwner != ObservedActions.Action.SAVE_BOUNDING_BOX) {
            // save the object in the ground truth
            observable.notifyObservers(ObservedActions.Action.SAVE_LABEL);
        }

        cancelWindow();
    }

    @Override
    protected void deleteObject() {
        // if the textfield id the focus owner, do not erase the object
        if (jTFBorder.isFocusOwner()) {
            return;
        }

        if (actionOwner == ObservedActions.Action.SAVE_BOUNDING_BOX) {
            // in it is a new object, cancel it
            cancelWindow();

        } else if ((actionOwner == ObservedActions.Action.UPDATE_OBJECT_SCRIBBLE)
                || (actionOwner == ObservedActions.Action.UPDATE_BOUNDING_BOX)) {
            // if it is an existing object, in edit mode, remove it
            removeObject();
        }
    }

    @Override
    protected void moveObject(int offsetX, int offsetY) {
        // move the object in the display window
        currentObject.move(offsetX, offsetY, new Dimension(frameImg.getWidth(), frameImg.getHeight()));

        // refresh the displayed image
        displayImage();

        // refresh also the image on the GUI
        observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
    }

    @Override
    protected void changeSize(int left, int top, int right, int bottom) {
        currentObject.changeSize(left, top, right, bottom, null, null, new Dimension(frameImg.getWidth(), frameImg.getHeight()));

        // refresh the displayed image
        displayImage();

        // change the title of the frame
        setFrameTitle();

        // refresh also the image on the GUI
        observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
    }

    @Override
    protected void restoreObjOrigPos() {
        // reset the object coordinates
        resetObjCoordinates();

        // refresh the displayed image
        displayImage();

        // change the title of the frame
        setFrameTitle();

        // refresh also the image on the GUI
        observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
    }

    @Override
    protected void changeObjColor() {
        // set the color of the object
        objectColor = getNewObjColor(objectColor);

        // set the color on the drawing panel
        dPPreviewImg.setObjColor(objectColor);

        // refresh the image on the panel
        showImage();

        // refresh the color of the object id label
        setObjIdColor();
    }

    @Override
    protected void displayObjId() {
        // add the object id
        // set the text of the object
        jLObjId.setText("Object id: " + currentObject.getObjectId());

        // set the color of the object id label
        setObjIdColor();
    }

    @Override
    protected void setFrameTitle() {
        String title = "";

        // add the size of the object (original)
        title += "Preview " + currentObject.getOuterBBox().width + "x" + currentObject.getOuterBBox().height;

        // if the work image has different size, show it in brackets (for zooming)
        if (!dPPreviewImg.getWorkImgSize().equals(dPPreviewImg.getOrigImgSize())) {
            // compute the size of the box with the zooming factor
            Rectangle resizedOuterBox = dPPreviewImg.getResize().resizeBox(currentObject.getOuterBBox());

            // display the zoomed size
            title += " (" + resizedOuterBox.width + "x" + resizedOuterBox.height + ")";
        }

        setTitle(title);
    }

    @Override
    protected void updateBorderValue(int value) {
        // get the value of the border only if it is valid
        // the border has to be greater than a min value
        value = Math.max(value, Constants.MIN_BORDER);

        // the border cannot be more than the distance from the box to the image border
        value = Math.min(value, getMaxPossibleBorder());

        // set the border size
        borderPX = value;

        // save the user preference
        currentObject.getUserPreference().setBorderSize(borderPX);

        // update text field
        jTFBorder.setText(Integer.toString(borderPX));

        // refresh the displayed image
        displayImage();
    }

    /**
     * Compute the distances from the box to the borders and sort it.
     *
     * @return the value of the max displayable border
     */
    protected int getMaxPossibleBorder() {
        // for simplicity, use a shorter name
        Rectangle objBox = currentObject.getOuterBBox();

        // save the borders inside an array
        int[] maxBorder = new int[4];

        // save the max border values (the distance from box to the image border)
        maxBorder[0] = objBox.x;
        maxBorder[1] = objBox.y;
        maxBorder[2] = frameImg.getWidth() - (objBox.x + objBox.width);
        maxBorder[3] = frameImg.getHeight() - (objBox.y + objBox.height);

        // sort the array into ascending order, according to the natural ordering of its elements
        Arrays.sort(maxBorder);

        // return the greatest value of the array
        return maxBorder[3];
    }

    @Override
    protected final void displayImage() {
        displayBox = getBorderedSize(currentObject.getOuterBBox(), borderPX);

        displayImage(displayBox);
    }

    /**
     * Display the image with the segmented box.
     */
    @Override
    protected void showImage() {
        // get the graphics of the image
        Graphics2D g2d = workImg.createGraphics();
        // draw the outer box of the object
        drawObjContour(g2d);
        g2d.dispose();

        updatePreview(workImg, DrawConstants.DrawType.DO_NOT_DRAW, currentObject.getUserPreference().getZoomingIndex());
    }

    /**
     * Initialise the border size from the user preferences or from the border
     * percentage.
     */
    private void initObjPreviewBorder() {
        // if the user preferences are invalid, compute the border as the percentage of the object size
        if (currentObject.getUserPreference().getBorderSize() < Constants.MIN_BORDER) {
            this.borderPX = Math.max(Math.max((int) (Constants.BORDER_PERCENTAGE * currentObject.getOuterBBox().width),
                    (int) (Constants.BORDER_PERCENTAGE * currentObject.getOuterBBox().height)),
                    Constants.MIN_BORDER);
        } else {
            // if the user border preferences is valid, use them
            this.borderPX = currentObject.getUserPreference().getBorderSize();
        }

        // set the value of the border
        jTFBorder.setText(Integer.toString(borderPX));
    }

    /**
     * Draw the outer shape of the object.
     *
     * @param g2d the graphics object
     */
    protected void drawObjContour(Graphics2D g2d) {
        drawOuterBox(g2d);
    }

    /**
     * Draw the outer box of the object; a rectangle which is resized to the
     * proper image size.
     *
     * @param g2d the graphics object
     * @return the rectangle drawn on the image
     */
    protected final Rectangle drawOuterBox(Graphics2D g2d) {
        // compute the position of the object box, to be displayed
        Rectangle bBox = new Rectangle(borderSize.getxLeft(),
                borderSize.getyTop(),
                currentObject.getOuterBBox().width,
                currentObject.getOuterBBox().height);

        g2d.setColor(objectColor);

        g2d.drawRect(bBox.x - 1, bBox.y - 1, bBox.width + 1, bBox.height + 1);

        return bBox;
    }

    /**
     * Based on the size of the generated box, compute a border size to be added
     * to the displayed image. The purpose is to have a better view of the
     * object. If the tracker cut some parts of the object, the user has to
     * notice and correct it.
     *
     * @param outerBBox - the original segmentation size
     * @param border the number of pixels to be added to the box
     * @return - the new size of the display image
     */
    private Rectangle getBorderedSize(Rectangle outerBBox, int border) {

        // if the border is too small, make it at least 10 pixels
        if (border < Constants.MIN_BORDER) {
            border = Constants.MIN_BORDER;
        }

        // set the border as 20% of the size of the box
        borderSize = new BoxLRTB(border);

        // limit the bordering in such way as to not get out of the image
        if ((outerBBox.x - border) < 0) {
            borderSize.setxLeft(outerBBox.x);
        }

        if ((outerBBox.y - border) < 0) {
            borderSize.setyTop(outerBBox.y);
        }

        if ((outerBBox.x + outerBBox.width + border) > frameImg.getWidth()) {
            borderSize.setxRight(frameImg.getWidth() - (outerBBox.x + outerBBox.width));
        }

        if ((outerBBox.y + outerBBox.height + border) > frameImg.getHeight()) {
            borderSize.setyBottom(frameImg.getHeight() - (outerBBox.y + outerBBox.height));
        }

        // return the new size with the border added
        return (new Rectangle(outerBBox.x - borderSize.getxLeft(),
                outerBBox.y - borderSize.getyTop(),
                outerBBox.width + borderSize.getxLeft() + borderSize.getxRight(),
                outerBBox.height + borderSize.getyTop() + borderSize.getyBottom()));
    }

    /**
     * Sets the color of the label showing the object id and its background.
     */
    private void setObjIdColor() {
        // set the color of the text of the object
        jLObjId.setForeground(objectColor);

        // set the background for better readability
        jLObjId.setBackground(Utils.getContrastColor(objectColor, 190));
    }

    /**
     * Save the object attributes (type, class, value, occlusion) and other
     * object specific characteristics.
     */
    private void saveObjInfo() {
        // set the specified attributes
        if (jCBObjType.getSelectedIndex() > -1) {
            currentObject.setObjectType(jCBObjType.getItemAt(jCBObjType.getSelectedIndex()));
        }

        if (jCBObjClass.getSelectedIndex() > -1) {
            currentObject.setObjectClass(jCBObjClass.getItemAt(jCBObjClass.getSelectedIndex()));
        }

        if (jCBObjValue.getSelectedIndex() > -1) {
            currentObject.setObjectValue(jCBObjValue.getItemAt(jCBObjValue.getSelectedIndex()));
        }

        if (jCBOccluded.getSelectedIndex() > -1) {
            currentObject.setOccluded(jCBOccluded.getItemAt(jCBOccluded.getSelectedIndex()));
        }
        // update the color of the object
        currentObject.setColor(objectColor);

        // set the labeling source: human or machine
        currentObject.setSegmentationSource(ConstantsLabeling.LABEL_SOURCE_MANUAL);
    }

    /**
     * Close the window and cancel the object. The user does not want to save
     * it.
     */
    private void cancelWindow() {
        closeWindow();

        // cancel current object
        observable.notifyObservers(ObservedActions.Action.CANCEL_CURRENT_OBJECT);
    }

    /**
     * Close the window and delete the object. The user does not want to have
     * it.
     */
    private void removeObject() {
        closeWindow();

        // cancel current object
        observable.notifyObservers(ObservedActions.Action.REMOVE_SELECTED_OBJECT);
    }

    @Override
    protected void changeBorderSize(int notches) {
        int tempBorder = borderPX;

        if (notches < 0) {
            // Mouse wheel moved UP = zoom in
            tempBorder += Math.abs(notches);
        } else {
            // Mouse wheel moved DOWN = zoom out
            tempBorder -= notches;
        }

        // update the border value
        updateBorderValue(tempBorder);
    }

    @Override
    public void update(Observable o, Object arg) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    /**
     * Implement specific to the object actions, like fill in some data for the
     * drawing panel etc.
     */
    protected void applyObjProperties() {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * Reset the position of the object. The user wants to have the status of
     * the object from the beginning of the edit.
     */
    protected void resetObjCoordinates() {
        currentObject.setOuterBBox(new Rectangle(origObjPos));
    }

} public final class CropEdit extends EditWindow {

    /**
     * Allows the user to activate different functionalities from the graphic
     * tablet.
     */
    private transient JPenFunctions penFunctions;

    /**
     * The configuration of the crop window.
     */
    private transient CropWindowConfig cropWindowConfig;

    /**
     * The list of scribbles to be displayed on the panel.
     */
    private List<ScribbleInfo> scribbleList;

    /**
     * Creates new form CropWindow
     *
     * @param parent the parent component of the dialog
     * @param frameImage the original image, in original size
     * @param currentCrop the crop object, being segmented
     * @param cropWinCfg the configuration of the crop window
     * @param objectAttributes the list of object attributes: type, class, value
     * @param actionOwner the scope of the dialog: create new crop, edit
     * existing one
     * @param objColorsList the list of already used colors (for other objects)
     * @param userPreferences user preferences regarding application
     * configuration
     * @param objPreferences user preferences regarding the display of the
     * object
     */
    public CropEdit(java.awt.Frame parent,
            BufferedImage frameImage,
            CropObject currentCrop,
            CropWindowConfig cropWinCfg,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences,
            ObjectPreferences objPreferences) {
        super(parent, frameImage, objectAttributes, actionOwner, objColorsList, userPreferences, objPreferences);

        this.currentCrop = currentCrop;

        // save the original object position in order to be able to reset the changes
        this.origObjPos = new Rectangle(currentCrop.getPositionOrig());

        initOtherVariables();

        loadWindowConfig(cropWinCfg);

        displayImage();

        setBrushOptions();

        // add pen listener to be able to use some features of the graphic tablet
        AwtPenToolkit.addPenListener(dPPreviewImg, penFunctions);

        updateMouseAndBrush();

        enableSemanticOptions(true);
        enableBoxOptions(false);

        // prepare the frame to be shown
        prepareFrame();
    }

    private void initOtherVariables() {
        drawingType = 0;

        // init the listener for the pen for using the graphic tablet
        penFunctions = new JPenFunctions();
        // add observer to be notified when there are changes from the pen
        penFunctions.addObserver(this);

        // remove the text from the labels which should show an image
        jLBrushPreview.setText("");
        jLBrushSizePreview.setText("");
    }

    private void loadWindowConfig(CropWindowConfig cropWinCfg) {
        if (cropWinCfg != null) {
            jCBMergeBKG.setSelected(cropWinCfg.isMergeBKG());
            jSCropBrushSize.setValue(cropWinCfg.getBrushSize());
            jSCropBrushDensity.setValue(cropWinCfg.getBrushDensity());
            this.cropWindowConfig = cropWinCfg;

            displayObjectId();
        }
    }

    private void displayObjectId() {
        // add the object id
        // set the text of the object radio button
        if (cropWindowConfig.getObjectId() > 0) {
            jRBOption2.setText("obj_" + cropWindowConfig.getObjectId());
        }

        // set the color of the text of the object radio button
        jRBOption2.setForeground(cropWindowConfig.getObjectColor());
    }

    @Override
    protected final void displayImage() {
        displayImage(currentCrop.getPositionOrig());
    }

    @Override
    protected void showImage() {
        if (updatePreview(workImg, DrawConstants.DrawType.DRAW_SCRIBBLE, objPreferences.getZoomingIndex())) {

            loadCropResult(origImg);

            // set the drawing type to avoid changing from obj to bkg when editing the object
            dPPreviewImg.setActionType(drawingType);

            // add the existing scriblles
            if (scribbleList != null) {
                dPPreviewImg.setScribbleList(scribbleList);
            } else if (dPPreviewImg.getScribbleList().size() > 0) {
                scribbleList = dPPreviewImg.getScribbleList();
            }

            // update the scribble list with the zooming factor
            dPPreviewImg.updateScribbleList();
            
            // set the color on the drawing panel
            dPPreviewImg.setObjColor(cropWindowConfig.getObjectColor());

            // add the title of the frame
            setFrameTitle();
        }
    }

    /**
     * Load the segmented result of the crop image to a visualisation panel.
     */
    private void loadCropResult(BufferedImage cropResult) {
        // remove the old image if it exists
        if (dPSemanticResultImg != null) {
            this.remove(dPSemanticResultImg);
            jPSemanticResult.remove(dPSemanticResultImg);
        }

        // load the viewer of the result
        dPSemanticResultImg = new ResultPanel(cropResult, new Dimension(cropResult.getWidth(), cropResult.getHeight()), cropWindowConfig.getObjectColor());
        dPSemanticResultImg.updateResultImg();
        dPSemanticResultImg.setBackground(new java.awt.Color(255, 255, 255));

        jPSemanticResult.add(dPSemanticResultImg);

        this.validate();
        this.repaint();

    }

    @Override
    protected void saveObject() {
        if (!isObjectAttributesSet()) {
            // the user wants to correct the data, do not close the application!
            return;
        }
        // save the configuration of the crop window for the next time it will be opened
        cropWindowConfig = new CropWindowConfig(jCBMergeBKG.isSelected(), jSCropBrushSize.getValue(), jSCropBrushDensity.getValue(), jRBOption2.getForeground(), cropWindowConfig.getObjectId());

        // notify the other form that the needed info is available
        observable.notifyObservers(actionOwner);

        closeWindow();
    }

    @Override
    protected void cancelObject() {
        // notify to track the object if the box is in edit mode
        if ((actionOwner != ObservedActions.Action.SAVE_BOUNDING_BOX)
                && (actionOwner != ObservedActions.Action.ADD_CROP_TO_OBJECT)) {
            // save the object in the ground truth
            observable.notifyObservers(ObservedActions.Action.CANCEL_CURRENT_OBJECT);
        }

        closeWindow();
    }

    @Override
    protected void deleteObject() {
        closeWindow();
        observable.notifyObservers(ObservedActions.Action.REMOVE_SELECTED_OBJECT);
    }

    @Override
    protected void moveObject(int offsetX, int offsetY) {
        // move the object in the display window
        Rectangle newPos = new Rectangle(currentCrop.getPositionOrig().x + offsetX,
                currentCrop.getPositionOrig().y + offsetY,
                currentCrop.getPositionOrig().width,
                currentCrop.getPositionOrig().height);

        if ((newPos.x >= 0)
                && (newPos.y >= 0)
                && (newPos.x + newPos.width < frameImg.getWidth())
                && (newPos.y + newPos.height < frameImg.getHeight())) {

            currentCrop.setPositionOrig(newPos);

            // remove the scribbles which are out of the boundaries
            removeScribbles();

            // refresh the displayed image
            displayImage();

            // run matting on the new image
            runMatting(Constants.RUN_MATT_ORIG_IMG);

            // refresh also the image on the GUI
            observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
        }
    }

    @Override
    protected void changeSize(int left, int top, int right, int bottom) {
        // move the object in the display window
        Rectangle newPos = currentCrop.getPositionOrig();
        newPos.setBounds(newPos.x + left, newPos.y + top, newPos.width + right - left, newPos.height + bottom - top);

        currentCrop.setPositionOrig(newPos);

        // remove the scribbles which are out of the boundaries
        removeScribbles();

        // refresh the displayed image
        displayImage();

        // run matting on the new image
        runMatting(Constants.RUN_MATT_ORIG_IMG);

        // add the title of the frame
        setFrameTitle();

        // refresh also the image on the GUI
        observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
    }

    @Override
    /**
     * Restore the object to the original position. The user made changes but
     * does not want to keep them.
     */
    protected void restoreObjOrigPos() {
        // reset the object coordinates
        currentCrop.setPositionOrig(new Rectangle(origObjPos));

        // remove the scribbles which are out of the boundaries
        removeScribbles();

        // refresh the displayed image
        displayImage();

        // run matting on the new image
        runMatting(Constants.RUN_MATT_ORIG_IMG);

        // add the title of the frame
        setFrameTitle();

        // refresh also the image on the GUI
        observable.notifyObservers(ObservedActions.Action.SELECTED_OBJECT_MOVED);
    }

    @Override
    protected void changeObjColor() {
        Color newColor = getNewObjColor(jRBOption2.getForeground());
        // set the color of the object
        cropWindowConfig.setObjectColor(newColor);

        // update the local viewer
        jRBOption2.setForeground(newColor);

        // update the result panel color
        dPSemanticResultImg.setObjColor(newColor);

        // set the color on the drawing panel
        dPPreviewImg.setObjColor(newColor);

        // set the object as active 
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_OBJECT);
        jRBOption2.setSelected(true);

        // update the result image with the new color
        refreshCropResult();
    }

    @Override
    protected void displayObjId() {
        // add the object id
        // set the text of the object radio button
        if (cropWindowConfig.getObjectId() > 0) {
            jRBOption2.setText("obj_" + cropWindowConfig.getObjectId());
        }

        // set the color of the text of the object radio button
        jRBOption2.setForeground(cropWindowConfig.getObjectColor());
    }

    @Override
    protected void setFrameTitle() {
        setTitle("Cropped Image  " + origImg.getWidth() + "x" + origImg.getHeight());
    }

    /**
     * Update the type of action and the preview of the brush.
     *
     * @param actionType - the type of action: -1 = erase; 0 = background; 1 =
     * object
     */
    @Override
    protected void updateObjInfo(int actionType) {
        this.drawingType = actionType;
        dPPreviewImg.setActionType(actionType);

        updateMouseAndBrush();
    }

    /**
     * When a change in the mouse brush happens, update the mouse icon and the
     * brush preview.
     */
    @Override
    protected void updateMouseAndBrush() {
        // update the brush
        setBrushOptions();
        brushPreview();

        // update the mouse icon
        dPPreviewImg.setCursor(Utils.generateCustomIcon(dPPreviewImg.getBrushOpt(), drawingType, cropWindowConfig.getObjectColor()));
    }

    /**
     * Removes the scribbles which no longer fit in the image; the ones which
     * would generate a coordinates outer box exception.
     */
    private void removeScribbles() {
        // copy the panel scribble list in the scribble list, to avoid erasing them after the new load of image
        scribbleList = new ArrayList<>();
        scribbleList.addAll(dPPreviewImg.getScribbleList());

        if (scribbleList != null) {
            for (Iterator<ScribbleInfo> it = scribbleList.iterator(); it.hasNext();) {
                ScribbleInfo si = it.next();

                if ((si.getImgPosX() >= currentCrop.getPositionOrig().width)
                        || (si.getImgPosY() >= currentCrop.getPositionOrig().height)) {
                    // remove the scribble
                    it.remove();
                }
            }
        }
    }

    /**
     * Refresh the segmented crop image to a visualisation panel.
     */
    private void refreshCropResult() {
        if (dPSemanticResultImg != null) {
            dPSemanticResultImg.updateResultImg();
        }

        this.validate();
        this.repaint();
    }

    /**
     * Allows the user to apply the wanted options to the current drawing
     * environment.
     */
    private void setBrushOptions() {
        BrushOptions brushOpt = new BrushOptions();
        brushOpt.setBrushSize(jSCropBrushSize.getValue());
        brushOpt.setBrushDensity((float) jSCropBrushDensity.getValue() / (float) jSCropBrushDensity.getMaximum());

        dPPreviewImg.setBrushOptions(brushOpt);
    }

    /**
     * Show an image with a preview of the brush using the current selected
     * options (size, density, color).
     */
    private void brushPreview() {
        Random rand = new Random();
        float density = (float) jSCropBrushDensity.getValue() / (float) jSCropBrushDensity.getMaximum();
        int win = jSCropBrushSize.getValue();

        byte[][] circleMask = Utils.getCircleMask(win);  // the mask making the mouse pointer a circle

        //minimum size of the window 1
        win = win < 1 ? 1 : win;

        BufferedImage brushPrev;
        BufferedImage brushSizePrev;

        //for the erase option draw a special preview
        if (drawingType > ConstantsLabeling.ACTION_TYPE_ERASE) {
            brushPrev = new BufferedImage(win, win, BufferedImage.TYPE_INT_RGB);
            brushSizePrev = new BufferedImage(win, win, BufferedImage.TYPE_INT_RGB);
            for (int x = 0; x < win; x++) {
                for (int y = 0; y < win; y++) {
                    float randInt = rand.nextFloat();
                    //set white background for the preview
                    brushPrev.setRGB(x, y, Color.white.getRGB());
                    brushSizePrev.setRGB(x, y, Color.white.getRGB());

                    // draw just if it is inside the circle
                    if (circleMask[x][y] == (byte) 1) {
                        //for the size, the drawing is full
                        brushSizePrev.setRGB(x, y, Utils.getDrawingColor(drawingType, cropWindowConfig.getObjectColor()).getRGB());

                        //if the is chosen to be saved by the random generator, add it in the preview image
                        if (randInt < density) {
                            brushPrev.setRGB(x, y, Utils.getDrawingColor(drawingType, cropWindowConfig.getObjectColor()).getRGB());
                        }
                    }
                }
            }
        } else {
            win += 2;   //border the icon with one pixel
            brushPrev = new BufferedImage(win, win, BufferedImage.TYPE_INT_RGB);
            brushSizePrev = new BufferedImage(win, win, BufferedImage.TYPE_INT_RGB);

            //draw black border arround the icon for visibility
            for (int x = 1; x < win - 1; x++) {
                for (int y = 1; y < win - 1; y++) {
                    brushSizePrev.setRGB(x, y, Color.white.getRGB());
                    brushPrev.setRGB(x, y, Color.white.getRGB());
                }
            }
        }

        ImageIcon iconLogo = new ImageIcon(brushPrev);
        jLBrushPreview.setIcon(iconLogo);

        //draw also the preview of the brush size
        iconLogo = new ImageIcon(brushSizePrev);
        jLBrushSizePreview.setIcon(iconLogo);
    }

    /**
     * Prepare the needed input for the matting application, call it and do the
     * load actions for the GUI.
     *
     * @param imageOption - on which image shall the algorithm be run original,
     * highlighted etc.
     */
    @Override
    public void runMatting(int imageOption) {
        Thread cudaMattingThread;

        if ((dPPreviewImg != null) && (jBRunMattingCrop.isEnabled())) {
            // save the scribble points
            int noScribbles = dPPreviewImg.flushPixelList();

            // prepare the thread data to run the matting application
            if (noScribbles > 0) {
                // disable the matting button, to avoid calling the thread several times
                jBRunMattingCrop.setEnabled(false);
                jBRunHighlightMatt.setEnabled(false);

                mattingThread = new MattingThreading(getMattingImage(imageOption), dPPreviewImg.getScribbleList(), ObservedActions.Action.REFRESH_CROP_RESULT);

                // add observer to be notified when the thread is finished
                mattingThread.addObserver(this);

                cudaMattingThread = new Thread(mattingThread);
                cudaMattingThread.setName("Matting Thread");
                cudaMattingThread.start();

                currentCrop.resetFilterCount();
            }
        }
    }

    /**
     * Set the list of scribbles drawn for the segmentation of the current
     * object.
     *
     * @param scribbleList - the list of scribbles which are already available
     * for the object
     */
    public void setScribbleList(List<ScribbleInfo> scribbleList) {
        this.scribbleList = scribbleList;
        dPPreviewImg.setScribbleList(this.scribbleList);
    }

    /**
     * Get the object map of the segmentation.
     *
     * @return - the map containing the ids of the objects
     */
    public byte[][] getObjectMap() {
        return dPSemanticResultImg.getObjMap();
    }

    /**
     * Get the configuration of the crop window.
     *
     * @return - the configuration of the modifiable fields of the crop window:
     * merge BKG, brush size and density
     */
    public CropWindowConfig getCropConfig() {
        return cropWindowConfig;
    }

    /**
     * Get the result image, after applying the segmentation algorithms.
     *
     * @return - the image representing the result of the segmentation
     */
    public BufferedImage getResultImage() {
        return dPSemanticResultImg.getWorkImg();
    }

    /**
     * Get the list of scribbles drawn for the segmentation of the current
     * object.
     *
     * @return - the list of scribbles used for segmentation
     */
    public List<ScribbleInfo> getScribbleList() {
        return dPPreviewImg.getScribbleList();
    }

    /**
     * Get the color of the scribbles used for the segmentation of the object.
     *
     * @return - the color of the scribbles, represented in RGB
     */
    public Color getScribbleColor() {
        return cropWindowConfig.getObjectColor();
    }

    /**
     * Returns the original image or the modified one, based on user choice.
     *
     * @return - original image or the work image
     */
    private BufferedImage getMattingImage(int imageOption) {

        switch (imageOption) {

            case Constants.RUN_MATT_HIGHLIGHT_IMG:
                //the user wants to run the matting on the highlighted image
                return Utils.histogramEqColor(dPPreviewImg.getWorkImg());

            case Constants.RUN_MATT_ORIG_IMG:
                //the user wants to run the matting on the original image
                return dPPreviewImg.getOrigImg();

            default:
                // option unknown: return the original image
                return dPPreviewImg.getOrigImg();
        }
    }

    /**
     * Filter the object map, in order to remove the outlier points.
     */
    @Override
    protected void filterObjMap() {
        if (dPSemanticResultImg == null) {
            return;
        }
        // filter the object map to remove outlier points
        dPSemanticResultImg.filterObjectMap();
        refreshCropResult();

        if (segmentResultPrev != null) {
            // refresh the image with the output of the filtering
            segmentResultPrev.setImage(dPSemanticResultImg.getWorkImg());
        }
        // count how many times the filtering was done (statistics purposes)
        currentCrop.incrementFilterCount();
    }

    /**
     * Update the crop result.
     */
    private void updateCropResult() {
        if (mattingThread != null) {
            // the thread finished the run and the result should be ready
            dPSemanticResultImg.setObjMap(mattingThread.getObjMap());
            refreshCropResult();
        }

        // enable the button, so the user can run again the algorithm
        jBRunMattingCrop.setEnabled(true);
        jBRunHighlightMatt.setEnabled(true);
    }

    /**
     * Refresh the size of the brush.
     */
    private void refreshBrushSize() {
        if ((penFunctions.getBrushSize() != 0)
                && (penFunctions.getBrushSize() != penFunctions.getBrushSizePrev())) {

            jSCropBrushSize.setValue(penFunctions.getBrushSize());

            penFunctions.setBrushSizePrev(penFunctions.getBrushSize());

            updateMouseAndBrush();
        }
    }

    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof ObservedActions.Action) {
            ObservedActions.Action type = ((ObservedActions.Action) arg);
            switch (type) {
                case REFRESH_CROP_RESULT:
                    updateCropResult();
                    break;

                case REFRESH_BRUSH_SIZE:
                    refreshBrushSize();
                    break;

                case FILTER_OBJECT_MAP:
                    filterObjMap();
                    break;

                default:
                    break;
            }
        }
    }

} public abstract class EditWindow extends javax.swing.JDialog implements Observer {

    /**
     * The text being displayed on the histogram button when the image has been
     * enhanced and the user can go to the original one.
     */
    protected static final String ORIGINAL_TEXT = "Original";
    /**
     * The text on the show highlighted image button.
     */
    protected static final String HIGHLIGHT_TEXT = "Highlight";

    /**
     * The resolution of the current screen where the application is showed.
     */
    protected transient ScreenResolution screenRes;

    /**
     * The original image of the object in the original size.
     */
    protected transient BufferedImage origImg;

    /**
     * The original image in the frame; the whole image - original size.
     */
    protected final transient BufferedImage frameImg;

    /**
     * The image used to be displayed. It is computed out of the original image
     * in order to keep the details and not loose data.
     */
    protected transient BufferedImage workImg;

    /**
     * The panel containing the preview image.
     */
    protected DrawingPanel dPPreviewImg;

    /**
     * The panel containing the result of the semantic segmentation image.
     */
    protected ResultPanel dPSemanticResultImg;

    /**
     * Used to preview the result image in a new window, with zoom available.
     */
    protected ImagePreview segmentResultPrev;

    /**
     * Encapsulates the call for running the cuda matting on a different thread.
     */
    protected transient MattingThreading mattingThread;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    protected final transient NotifyObservers observable = new NotifyObservers();

    /**
     * Shows who called the crop window: the normal crop or the edit crop. Class
     * ObservedActions shows the possible actions.
     */
    protected final ObservedActions.Action actionOwner;

    /**
     * The type of drawing: background or object.
     */
    protected int drawingType;

    /**
     * The list of colors which were used for the segmented objects in the
     * current frame. It helps to prevent the segmentation of two objects with
     * the same color.
     */
    protected final transient List<Color> objColorsList;

    /**
     * The color of the object, used in the bounding box window. It will not be
     * saved for the object if the object is cancel. It will be saved for the
     * object if the object is saved.
     */
    protected Color objectColor;

    /**
     * The list of object attributes: type, class, value.
     */
    protected CustomTreeNode objectAttributes;

    /**
     * User preferences related to the application.
     */
    protected UserPreferences userPrefs;

    /**
     * The size of the border, in pixels, for displaying purposes (the user has
     * to see some of the environment to be able to label correctly).
     */
    protected int borderPX;

    /**
     * The original position of the object in the image; before being modified.
     */
    protected Rectangle origObjPos;

    /**
     * The selected crop for being edited.
     */
    protected transient CropObject currentCrop;

    /**
     * User preferences regarding the way an object should be displayed in edit
     * mode: how much zoom shall be used, how much border etc.
     */
    protected final transient ObjectPreferences objPreferences;

    /**
     * Creates new form BoundingBoxWindow
     *
     * @param parent the parent component of the dialog
     * @param frameImage the original image, in original size
     * @param objectAttributes the list of object attributes: type, class, value
     * @param actionOwner the scope of the dialog: create new box, edit existing
     * one
     * @param objColorsList the list of already used colors (for other objects)
     * @param userPreferences user preferences regarding application
     * configuration
     * @param objPreferences user preferences regarding the display of the
     * object
     */
    public EditWindow(java.awt.Frame parent,
            BufferedImage frameImage,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences,
            ObjectPreferences objPreferences) {
        super(parent, true);

        initComponents();

        this.actionOwner = actionOwner;
        this.objColorsList = objColorsList;

        // save the original image
        this.frameImg = frameImage;

        // save the user anf object preferences
        this.userPrefs = userPreferences;
        this.objPreferences = objPreferences;

        // get the tree in the form to be displayed (add if needed the invalid branch)
        this.objectAttributes = objectAttributes;

        initAttributesComponents();

        initResolutions();

        initOtherData();
    }

    private void initOtherData() {
        // compute the preffered width of the combo boxes based on the specified text
        jCBObjType.setPrototypeDisplayValue(Constants.OBJECT_ATTRIBUTES_TEXT);
        jCBObjClass.setPrototypeDisplayValue(Constants.OBJECT_ATTRIBUTES_TEXT);
        jCBObjValue.setPrototypeDisplayValue(Constants.OBJECT_ATTRIBUTES_TEXT);
    }

    /**
     * Update the displayed image with the specified one.
     *
     * @param image the new image to be displayed
     * @param drawType the type of drawing allowed on the panel
     * @param zoomingIndex the zoom wanted by the user
     * @return
     */
    protected boolean updatePreview(BufferedImage image, DrawConstants.DrawType drawType, int zoomingIndex) {
        if ((image != null)
                && (image.getWidth() > 0)
                && (image.getHeight() > 0)) {

            // remove the old image if it exists
            if (dPPreviewImg != null) {
                this.remove(dPPreviewImg);
                jPPreviewImg.remove(dPPreviewImg);

                dPPreviewImg.updateImage(image);
            } else {
                // Load the file to be labeled in the panel and show it
                dPPreviewImg = new DrawingPanel(image, drawType, zoomingIndex);
                dPPreviewImg.setBackground(new java.awt.Color(255, 255, 255));

                // add observer to be notified when there are changes on the panel
                dPPreviewImg.addObserver(this);
            }

            jPPreviewImg.add(dPPreviewImg);

            this.validate();
            this.repaint();

            return true;
        }

        return false;
    }

    /**
     * Equalize the histogram of the image, for all color components.
     */
    protected void histogramEqualisation() {
        // if the image was highlighted, do it again
        if (HIGHLIGHT_TEXT.equals(jBHistogramEq.getText())) {
            workImg = Utils.histogramEqColor(origImg);
            jBHistogramEq.setText(ORIGINAL_TEXT);
        } else if (ORIGINAL_TEXT.equals(jBHistogramEq.getText())) {
            workImg = new BufferedImage(origImg.getWidth(), origImg.getHeight(), origImg.getType());
            Utils.copySrcIntoDstAt(origImg, workImg);
            jBHistogramEq.setText(HIGHLIGHT_TEXT);
        }

        showImage();
    }

    /**
     * Get the piece of image representing the object from the frame image; load
     * it on a panel; display it and load the object attributes.
     *
     * @param imgPosition the position of the segmented to be be displayed,
     * relative to the full frame (image coordinates)
     */
    protected final void displayImage(Rectangle imgPosition) {
        this.origImg = Utils.getSelectedImg(frameImg, imgPosition);

        // if the image was highlighted, do it again
        if (ORIGINAL_TEXT.equals(jBHistogramEq.getText())) {
            workImg = Utils.histogramEqColor(origImg);
        } else {
            workImg = new BufferedImage(origImg.getWidth(), origImg.getHeight(), origImg.getType());
            Utils.copySrcIntoDstAt(origImg, workImg);
        }

        showImage();

        prepareFrame();
    }

    /**
     * Show the color chooser dialog and allow the user to choose a new color
     * for the object. Replace the color with a similar one, for the case when
     * the color was reserved or already used.
     *
     * @param objColor the current color of the object
     * @return the new color of the object, or the old color if no other was
     * wanted
     */
    protected Color getNewObjColor(Color objColor) {
        Color newColor = JColorChooser.showDialog(this,
                "Choose Object Color",
                objColor);

        if (newColor == null) {
            return objColor;
        }

        if (objColorsList.contains(newColor) || Constants.COLORS_LIST.contains(newColor)) {
            newColor = Utils.changeColor(newColor, objColorsList);

            String title = "Color warning";
            String message = "\nThe chosen color was already used for an object or it is reserved.\nIt will be changed to the one previewed here.";

            MessageWindow infoWin = new MessageWindow(new javax.swing.JFrame(), true, title, message, "OK", new MonochromeIcon(32, 32, newColor));
            infoWin.setLocationRelativeTo(this);
            infoWin.setVisible(true);
        }

        return newColor;
    }

    /**
     * Get the position of the segmented image (the one to be displayed) and
     * call the display image method.
     */
    protected abstract void displayImage();

    /**
     * Display the image and load the object specific data.
     */
    protected abstract void showImage();

    /**
     * Notifies the controller that the object was segmented and it has to be
     * saved.
     */
    protected abstract void saveObject();

    /**
     * Notifies the controller that the object was cancel.
     */
    protected abstract void cancelObject();

    /**
     * The delete key was pressed and the object has to be removed. Handle the
     * remove of an object by pressing the delete key.
     */
    protected abstract void deleteObject();

    /**
     * Moves an object in the image with the specified offsets.
     *
     * @param offsetX how many pixels should the object moved on the X axis
     * @param offsetY how many pixels should the object moved on the Y axis
     */
    protected abstract void moveObject(int offsetX, int offsetY);

    /**
     * Changes the size of the object on any direction.
     *
     * @param left add/remove pixels from the left side of the object
     * @param top add/remove pixels from the top side of the object
     * @param right add/remove pixels from the right side of the object
     * @param bottom add/remove pixels from the bottom side of the object
     */
    protected abstract void changeSize(int left, int top, int right, int bottom);

    /**
     * Restore the object to the original position. The user made changes but
     * does not want to keep them.
     */
    protected abstract void restoreObjOrigPos();

    /**
     * Change the color of the object to a new one, selected by the user.
     */
    protected abstract void changeObjColor();

    /**
     * Shows the object id in the frame and sets its color and background for
     * better readability.
     */
    protected abstract void displayObjId();

    /**
     * Sets the title of the frame with the current size of the image and the
     * size of the work image (which can be different due to the zooming).
     */
    protected abstract void setFrameTitle();

    /**
     * Initialises the resolution of the screen on which the application is
     * drawn.
     */
    private void initResolutions() {
        screenRes = new ScreenResolution(this);
    }

    /**
     * Code related to the frame like: pack, set location etc.
     */
    protected final void prepareFrame() {
        this.pack();
        this.setMinimumSize(getPreferredSize());
    }

    /**
     * Zoom the image based on the movement of the mouse scroll.
     *
     * @param notches how much the mouse scroll moves and in which direction
     */
    protected void zoomImage(int notches) {
        if (notches < 0) {
            // Mouse wheel moved UP = zoom in
            // check if the resize is not generating a bigger image than the screen
            Dimension origImgDimension = new Dimension(origImg.getWidth(), origImg.getHeight());
            if (dPPreviewImg.getResize().isSizeIncreaseOK(origImgDimension, screenRes.getScreenResolution())) {
                objPreferences.setZoomingIndex(dPPreviewImg.getResize().incrementWidthHeight(origImgDimension));
            }
        } else {
            Dimension origImgDimension = new Dimension(-origImg.getWidth(), -origImg.getHeight());
            // Mouse wheel moved DOWN = zoom out
            if (dPPreviewImg.getResize().isSizeDecreaseOK(origImgDimension)) {
                objPreferences.setZoomingIndex(dPPreviewImg.getResize().incrementWidthHeight(origImgDimension));
            }
        }

        // display the image on the panel
        showImage();

        // change the title of the frame
        setFrameTitle();

        // resize the frame and position it in the middle of the screen
        prepareFrame();
    }

    /**
     * Return the color of the segmented object as selected by the user in the
     * edit window.
     *
     * @return - the color chosen by the user for the object
     */
    public Color getObjectColor() {
        return objectColor;
    }

    /**
     * Create the proper design for the components representing the object
     * attributes and populate the combo boxes with the data defined in the
     * tree.
     */
    private void initAttributesComponents() {
        if (objectAttributes == null) {
            return;
        }

        initAttributesComponents(objectAttributes, jCBObjType);

        CustomTreeNode ctn = objectAttributes.getChild(jCBObjType.getSelectedItem().toString());

        initAttributesComponents(ctn, jCBObjClass);

        if ((ctn != null) && (!ctn.isLeaf())) {
            initAttributesComponents(ctn.getChild(jCBObjClass.getSelectedItem().toString()), jCBObjValue);
        }
    }

    /**
     * Create the proper design for the components representing the object
     * attributes and populate the combo boxes with the data defined in the
     * database.
     *
     * @param selectedNode the list of object attributes, including: the list of
     * types, the list of classes, the list of values
     * @param attribute the combo box for which the attribute is initialized
     */
    public void initAttributesComponents(CustomTreeNode selectedNode, JComboBox attribute) {
        if (selectedNode == null) {
            return;
        }

        List<String> typeAttrib = selectedNode.getChildren();

        attribute.removeAllItems();

        if ((typeAttrib != null) && (!typeAttrib.isEmpty())) {
            // set the attributes of the combo boxes
            typeAttrib.stream().forEach(objType -> attribute.addItem(Utils.capitalize(objType)));
        }

        attribute.repaint();
    }

    /**
     * Set the wanted type, class, value and occlusion for the preview.
     *
     * @param objType - wanted object type
     * @param objClass - wanted object class
     * @param objValue - wanted object value
     * @param occluded - wanted object occlusion
     */
    public void setTypeClassValOcc(String objType, String objClass, String objValue, String occluded) {
        jCBObjType.setSelectedItem(objType);
        jCBObjClass.setSelectedItem(objClass);
        jCBObjValue.setSelectedItem(objValue);
        jCBOccluded.setSelectedItem(occluded);
    }

    /**
     * Returns the selected type of object.
     *
     * @return - the chosen attribute for type for the object
     */
    public String getObjType() {
        return jCBObjType.getItemAt(jCBObjType.getSelectedIndex());
    }

    /**
     * Returns the selected class of object.
     *
     * @return - the chosen attribute for class for the object
     */
    public String getObjClass() {
        return jCBObjClass.getItemAt(jCBObjClass.getSelectedIndex());
    }

    /**
     * Returns the selected value of object.
     *
     * @return - the chosen attribute for value for the object
     */
    public String getObjValue() {
        return jCBObjValue.getItemAt(jCBObjValue.getSelectedIndex());
    }

    /**
     * Returns the occlusion of object.
     *
     * @return - the specified occlusion of the object
     */
    public String getObjOccluded() {
        return jCBOccluded.getItemAt(jCBOccluded.getSelectedIndex());
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);

        // notify to remove the GUI key event dispatcher
        observable.notifyObservers(ObservedActions.Action.REMOVE_GUI_KEY_EVENT_DISPATCHER);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

    /**
     * Close the window and release the key dispatcher.
     */
    protected void closeWindow() {
        // notify to add the GUI key event dispatcher back
        observable.notifyObservers(ObservedActions.Action.ADD_GUI_KEY_EVENT_DISPATCHER);

        dispose();
    }

    /**
     * When the window is opening it will have its own key dispatcher. The
     * dispatcher of the GUI should no longer be active.
     */
    public void removeOtherKeyEventDispatcher() {
        // notify to remove the GUI key event dispatcher
        observable.notifyObservers(ObservedActions.Action.REMOVE_GUI_KEY_EVENT_DISPATCHER);
    }

    /**
     * Checks if the frame attributes are correctly set and asks the user to
     * correct them. If the user chooses to correct them, the application does
     * not continue with the saving of data; it will cancel the process and go
     * back to the normal state.
     *
     * @return true if the user wants to save the data as it is and false if the
     * saving of data shall not be done yet, because the user wants to correct
     * it
     */
    protected boolean isObjectAttributesSet() {
        // make sure the user wants to check the frame attributes
        if (!userPrefs.isCheckObjectAttributes()) {
            return true;
        }

        StringBuilder invalidAttribute = new StringBuilder();

        // check each frame attribute and create a list of missing frame attributes
        Utils.checkAttributeValid(invalidAttribute, jCBObjType.getItemAt(jCBObjType.getSelectedIndex()), jLType.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBObjClass.getItemAt(jCBObjClass.getSelectedIndex()), jLClass.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBObjValue.getItemAt(jCBObjValue.getSelectedIndex()), jLValue.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBOccluded.getItemAt(jCBOccluded.getSelectedIndex()), jLOccluded.getText());

        // if missing attributes were found, display the message window to warn the user to correct the attributes
        if (invalidAttribute.length() > 0) {
            // remove the last comma and the last space from the string
            invalidAttribute.replace(invalidAttribute.length() - 2, invalidAttribute.length(), "");

            // ask the user if the current attributes shall be saved or not; if the user wants to correct them, stop the application and let him fix them
            int userChoice = Utils.messageAttributesSet(invalidAttribute.toString(), true, this);

            if (userChoice == JOptionPane.CANCEL_OPTION) {
                // the user chose to edit the attributes; display the dialog and allow it
                observable.notifyObservers(ObservedActions.Action.DISPLAY_EDIT_ATTRIBUTES_WIN);
            }

            return ((JOptionPane.YES_OPTION != userChoice) && (JOptionPane.CANCEL_OPTION != userChoice));
        }

        return true;
    }

    /**
     * Set the object attributes tree.
     *
     * @param objectAttributes the list of object attributes, saved as a tree
     */
    public void setObjectAttributes(CustomTreeNode objectAttributes) {
        this.objectAttributes = objectAttributes;
        initAttributesComponents();
    }

    /**
     * Increase the size of the brush with one.
     */
    private void increaseBrushSize() {
        // increase the size of the brush
        jSCropBrushSize.setValue(jSCropBrushSize.getValue() + 1);
        jSCropBrushSize.setValue(jSCropBrushSize.getValue() + 1);

        updateMouseAndBrush();
    }

    /**
     * Decrease the size of the brush with one.
     */
    private void decreaseBrushSize() {
        // decrease the size of the brush
        jSCropBrushSize.setValue(jSCropBrushSize.getValue() - 1);
        jSCropBrushSize.setValue(jSCropBrushSize.getValue() - 1);

        updateMouseAndBrush();
    }

    /**
     * Activate the object, in order to draw scribbles for it.
     */
    private void selectObject() {
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_OBJECT);
        jRBOption2.setSelected(true);
    }

    /**
     * Activate the eraser in order to remove the incorrect drawn points.
     */
    private void selectEraser() {
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_ERASE);
        jRBOption3.setSelected(true);
    }

    /**
     * Activate the background, in order to draw scribbles for it.
     */
    private void selectBackground() {
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_BACKGROUND);
        jRBOption1.setSelected(true);
    }

    /**
     * Show or hide the GUI elements related to the semantic segmentation.
     *
     * @param enable true if the semantic options should be available
     */
    protected final void enableSemanticOptions(boolean enable) {
        jPRBOptions.setVisible(enable);
        jPBrushOptions.setVisible(enable);
        jPSemanticResult.setVisible(enable);
        jBFilterObjMap.setVisible(enable);
        jMISemanticSegOptions.setVisible(enable);
        jCBMergeBKG.setVisible(enable);
        jBRunMattingCrop.setVisible(enable);
        jBRunHighlightMatt.setVisible(enable);
    }

    /**
     * Show or hide the GUI elements related to the bounding box segmentation.
     *
     * @param enable true if the bounding box options should be available
     */
    protected final void enableBoxOptions(boolean enable) {
        jLBorder.setVisible(enable);
        jTFBorder.setVisible(enable);
        jBSetBorder.setVisible(enable);
        jLObjId.setVisible(enable);
    }

    /**
     * *************************BOUNDING BOX AREA******************************
     */
    /**
     * Updates the value of the border with the value specified by the user in
     * the text box.
     *
     * @param value
     */
    protected void updateBorderValue(int value) {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * Change the border size for the event Ctrl + mouse scroll.
     *
     * @param notches how much the mouse scroll moves and in which direction
     */
    protected void changeBorderSize(int notches) {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * *********************SEMANTIC SEGMENTATION AREA*************************
     */
    /**
     * Update the type of action and the preview of the brush.
     *
     * @param actionType - the type of action: -1 = erase; 0 = background; 1 =
     * object
     */
    protected void updateObjInfo(int actionType) {
        // do nothing; the classes which need to implement this method; will do so

    }

    /**
     * When a change in the mouse brush happens, update the mouse icon and the
     * brush preview.
     */
    protected void updateMouseAndBrush() {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * Prepare the needed input for the matting application, call it and do the
     * load actions for the GUI.
     *
     * @param imageOption - on which image shall the algorithm be run original,
     * highlighted etc.
     */
    public void runMatting(int imageOption) {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * Filter the object map in order to remove some of the noise from the
     * image.
     */
    protected void filterObjMap() {
        // do nothing; the classes which need to implement this method; will do so
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        javax.swing.ButtonGroup jBGObjects = new javax.swing.ButtonGroup();
        javax.swing.JPanel jPBackground = new javax.swing.JPanel();
        javax.swing.JPanel jPObjAttributes = new javax.swing.JPanel();
        jLType = new javax.swing.JLabel();
        jLClass = new javax.swing.JLabel();
        jLValue = new javax.swing.JLabel();
        jLOccluded = new javax.swing.JLabel();
        javax.swing.JButton jBSaveObj = new javax.swing.JButton();
        jCBObjClass = new javax.swing.JComboBox<>();
        jCBObjType = new javax.swing.JComboBox<>();
        jCBObjValue = new javax.swing.JComboBox<>();
        jCBOccluded = new javax.swing.JComboBox<>();
        jLObjId = new javax.swing.JLabel();
        jPOptions = new javax.swing.JPanel();
        jBHistogramEq = new javax.swing.JButton();
        javax.swing.JButton jBChangeObjColor = new javax.swing.JButton();
        jBFilterObjMap = new javax.swing.JButton();
        javax.swing.JButton jBRestorePos = new javax.swing.JButton();
        javax.swing.JPanel jPBorder = new javax.swing.JPanel();
        jLBorder = new javax.swing.JLabel();
        jBSetBorder = new javax.swing.JButton();
        jTFBorder = new javax.swing.JTextField();
        jPRBOptions = new javax.swing.JPanel();
        jRBOption3 = new javax.swing.JRadioButton();
        jRBOption2 = new javax.swing.JRadioButton();
        jRBOption1 = new javax.swing.JRadioButton();
        jPPreviewImg = new javax.swing.JPanel();
        jPMoreOpt = new javax.swing.JPanel();
        jBRunMattingCrop = new javax.swing.JButton();
        jCBMergeBKG = new javax.swing.JCheckBox();
        jBRunHighlightMatt = new javax.swing.JButton();
        jPBrushOptions = new javax.swing.JPanel();
        jSCropBrushSize = new javax.swing.JSlider();
        jSCropBrushDensity = new javax.swing.JSlider();
        jLCropBrushSize = new javax.swing.JLabel();
        jLCropBrushDensity = new javax.swing.JLabel();
        jLBrushPreview = new javax.swing.JLabel();
        jLBrushSizePreview = new javax.swing.JLabel();
        jPSemanticResult = new javax.swing.JPanel();
        javax.swing.JMenuBar jMBEditWindow = new javax.swing.JMenuBar();
        javax.swing.JMenu jMFile = new javax.swing.JMenu();
        javax.swing.JMenuItem jMICloseDialog = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMISaveObj = new javax.swing.JMenuItem();
        javax.swing.JMenu jMEditObject = new javax.swing.JMenu();
        javax.swing.JMenuItem jMIMoveLeft = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIMoveRight = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIMoveUp = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIMoveDown = new javax.swing.JMenuItem();
        javax.swing.JPopupMenu.Separator jSeparator1 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIDecreaseLeft = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIDecreaseRight = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIDecreaseTop = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIDecreaseBottom = new javax.swing.JMenuItem();
        javax.swing.JPopupMenu.Separator jSeparator3 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIIncreaseLeft = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIIncreaseRight = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIIncreaseTop = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIIncreaseBottom = new javax.swing.JMenuItem();
        javax.swing.JPopupMenu.Separator jSeparator2 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIDecreaseBox = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIIncreaseBox = new javax.swing.JMenuItem();
        javax.swing.JPopupMenu.Separator jSeparator4 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIDeleteObj = new javax.swing.JMenuItem();
        jMISemanticSegOptions = new javax.swing.JMenu();
        jMSelectBKG = new javax.swing.JMenuItem();
        jMISelectObj = new javax.swing.JMenuItem();
        jMISelectErase = new javax.swing.JMenuItem();
        jSeparator5 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIIncreaseBrushSize = new javax.swing.JMenuItem();
        javax.swing.JMenuItem jMIDecreaseBrushSize = new javax.swing.JMenuItem();
        jSeparator6 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem jMIRunMatting = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        addMouseWheelListener(new java.awt.event.MouseWheelListener() {
            public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
                formMouseWheelMoved(evt);
            }
        });
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });
        getContentPane().setLayout(new java.awt.GridBagLayout());

        jPBackground.setBackground(new java.awt.Color(255, 255, 255));
        jPBackground.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPBackground.setLayout(new java.awt.GridBagLayout());

        jPObjAttributes.setBackground(new java.awt.Color(255, 255, 255));
        jPObjAttributes.setLayout(new java.awt.GridBagLayout());

        jLType.setLabelFor(jCBObjType);
        jLType.setText("Type");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jLType, gridBagConstraints);

        jLClass.setLabelFor(jCBObjClass);
        jLClass.setText("Class");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jLClass, gridBagConstraints);

        jLValue.setLabelFor(jCBObjValue);
        jLValue.setText("Value");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jLValue, gridBagConstraints);

        jLOccluded.setLabelFor(jCBOccluded);
        jLOccluded.setText("Occluded");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jLOccluded, gridBagConstraints);

        jBSaveObj.setBackground(new java.awt.Color(255, 255, 255));
        jBSaveObj.setText("Save");
        jBSaveObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBSaveObjActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 13;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(8, 0, 2, 0);
        jPObjAttributes.add(jBSaveObj, gridBagConstraints);

        jCBObjClass.setPreferredSize(new java.awt.Dimension(145, 23));
        jCBObjClass.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jCBObjClassItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jCBObjClass, gridBagConstraints);

        jCBObjType.setPreferredSize(new java.awt.Dimension(145, 23));
        jCBObjType.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                jCBObjTypeItemStateChanged(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jCBObjType, gridBagConstraints);

        jCBObjValue.setPreferredSize(new java.awt.Dimension(145, 23));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jCBObjValue, gridBagConstraints);

        jCBOccluded.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "false", "true (25%)", "true (50%)", "true (75%)", "true (100%)" }));
        jCBOccluded.setPreferredSize(new java.awt.Dimension(145, 23));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 11;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jCBOccluded, gridBagConstraints);

        jLObjId.setBackground(new java.awt.Color(255, 255, 255));
        jLObjId.setOpaque(true);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttributes.add(jLObjId, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPBackground.add(jPObjAttributes, gridBagConstraints);

        jPOptions.setBackground(new java.awt.Color(255, 255, 255));
        jPOptions.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPOptions.setLayout(new java.awt.GridBagLayout());

        jBHistogramEq.setBackground(new java.awt.Color(255, 255, 255));
        jBHistogramEq.setText(HIGHLIGHT_TEXT);
        jBHistogramEq.setPreferredSize(new java.awt.Dimension(120, 32));
        jBHistogramEq.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBHistogramEqActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPOptions.add(jBHistogramEq, gridBagConstraints);

        jBChangeObjColor.setBackground(new java.awt.Color(255, 255, 255));
        jBChangeObjColor.setText("Obj Color");
        jBChangeObjColor.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBChangeObjColorActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPOptions.add(jBChangeObjColor, gridBagConstraints);

        jBFilterObjMap.setBackground(new java.awt.Color(255, 255, 255));
        jBFilterObjMap.setText("Filter");
        jBFilterObjMap.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBFilterObjMapActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 9;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPOptions.add(jBFilterObjMap, gridBagConstraints);

        jBRestorePos.setBackground(new java.awt.Color(255, 255, 255));
        jBRestorePos.setText("Restore Obj");
        jBRestorePos.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBRestorePosActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPOptions.add(jBRestorePos, gridBagConstraints);

        jPBorder.setBackground(new java.awt.Color(255, 255, 255));
        jPBorder.setLayout(new java.awt.GridBagLayout());

        jLBorder.setText("Border (px)");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPBorder.add(jLBorder, gridBagConstraints);

        jBSetBorder.setBackground(new java.awt.Color(255, 255, 255));
        jBSetBorder.setText("Set Border");
        jBSetBorder.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBSetBorderActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 8;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPBorder.add(jBSetBorder, gridBagConstraints);

        jTFBorder.setText("jTextField1");
        jTFBorder.setPreferredSize(new java.awt.Dimension(60, 24));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPBorder.add(jTFBorder, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 0;
        jPOptions.add(jPBorder, gridBagConstraints);

        jPRBOptions.setBackground(new java.awt.Color(255, 255, 255));
        jPRBOptions.setLayout(new java.awt.GridBagLayout());

        jRBOption3.setBackground(new java.awt.Color(255, 255, 255));
        jBGObjects.add(jRBOption3);
        jRBOption3.setText("Erase");
        jRBOption3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBOption3ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPRBOptions.add(jRBOption3, gridBagConstraints);

        jRBOption2.setBackground(new java.awt.Color(255, 255, 255));
        jBGObjects.add(jRBOption2);
        jRBOption2.setForeground(new java.awt.Color(51, 255, 0));
        jRBOption2.setText("obj");
        jRBOption2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBOption2ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPRBOptions.add(jRBOption2, gridBagConstraints);

        jRBOption1.setBackground(new java.awt.Color(255, 255, 255));
        jBGObjects.add(jRBOption1);
        jRBOption1.setForeground(new java.awt.Color(255, 0, 0));
        jRBOption1.setSelected(true);
        jRBOption1.setText("bkg");
        jRBOption1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBOption1ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPRBOptions.add(jRBOption1, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        jPOptions.add(jPRBOptions, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        jPBackground.add(jPOptions, gridBagConstraints);

        jPPreviewImg.setBackground(new java.awt.Color(255, 255, 255));
        jPPreviewImg.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPPreviewImg.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPBackground.add(jPPreviewImg, gridBagConstraints);

        jPMoreOpt.setBackground(new java.awt.Color(255, 255, 255));
        jPMoreOpt.setLayout(new java.awt.GridBagLayout());

        jBRunMattingCrop.setBackground(new java.awt.Color(255, 255, 255));
        jBRunMattingCrop.setText("Run Matting");
        jBRunMattingCrop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBRunMattingCropActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0);
        jPMoreOpt.add(jBRunMattingCrop, gridBagConstraints);

        jCBMergeBKG.setBackground(new java.awt.Color(255, 255, 255));
        jCBMergeBKG.setSelected(true);
        jCBMergeBKG.setText("mergeBKG");
        jCBMergeBKG.setToolTipText("Check for merging the image as it was segmented.");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0);
        jPMoreOpt.add(jCBMergeBKG, gridBagConstraints);

        jBRunHighlightMatt.setBackground(new java.awt.Color(255, 255, 255));
        jBRunHighlightMatt.setText("Run Highlight");
        jBRunHighlightMatt.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBRunHighlightMattActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0);
        jPMoreOpt.add(jBRunHighlightMatt, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPBackground.add(jPMoreOpt, gridBagConstraints);

        jPBrushOptions.setBackground(new java.awt.Color(255, 255, 255));
        jPBrushOptions.setBorder(javax.swing.BorderFactory.createTitledBorder("Brush"));
        jPBrushOptions.setLayout(new java.awt.GridBagLayout());

        jSCropBrushSize.setBackground(new java.awt.Color(255, 255, 255));
        jSCropBrushSize.setMaximum(30);
        jSCropBrushSize.setMinimum(1);
        jSCropBrushSize.setOrientation(javax.swing.JSlider.VERTICAL);
        jSCropBrushSize.setPaintTicks(true);
        jSCropBrushSize.setToolTipText("");
        jSCropBrushSize.setValue(5);
        jSCropBrushSize.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                jSCropBrushSizeMouseReleased(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPBrushOptions.add(jSCropBrushSize, gridBagConstraints);

        jSCropBrushDensity.setBackground(new java.awt.Color(255, 255, 255));
        jSCropBrushDensity.setMinimum(1);
        jSCropBrushDensity.setOrientation(javax.swing.JSlider.VERTICAL);
        jSCropBrushDensity.setPaintTicks(true);
        jSCropBrushDensity.setValue(20);
        jSCropBrushDensity.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                jSCropBrushDensityMouseReleased(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPBrushOptions.add(jSCropBrushDensity, gridBagConstraints);

        jLCropBrushSize.setText("Size");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
        jPBrushOptions.add(jLCropBrushSize, gridBagConstraints);

        jLCropBrushDensity.setText("Density");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPBrushOptions.add(jLCropBrushDensity, gridBagConstraints);

        jLBrushPreview.setText("Brush");
        jLBrushPreview.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
        jLBrushPreview.setPreferredSize(new java.awt.Dimension(35, 35));
        jLBrushPreview.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
        gridBagConstraints.weightx = 0.02;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 0);
        jPBrushOptions.add(jLBrushPreview, gridBagConstraints);

        jLBrushSizePreview.setText("Brush");
        jLBrushSizePreview.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
        jLBrushSizePreview.setPreferredSize(new java.awt.Dimension(35, 35));
        jLBrushSizePreview.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
        gridBagConstraints.weighty = 0.01;
        jPBrushOptions.add(jLBrushSizePreview, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPBackground.add(jPBrushOptions, gridBagConstraints);

        jPSemanticResult.setBackground(new java.awt.Color(255, 255, 255));
        jPSemanticResult.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPSemanticResult.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jPSemanticResultMouseClicked(evt);
            }
        });
        jPSemanticResult.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(8, 5, 5, 5);
        jPBackground.add(jPSemanticResult, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.weighty = 0.05;
        getContentPane().add(jPBackground, gridBagConstraints);

        jMFile.setText("File");

        jMICloseDialog.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0));
        jMICloseDialog.setText("Cancel and Close");
        jMICloseDialog.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMICloseDialogActionPerformed(evt);
            }
        });
        jMFile.add(jMICloseDialog);

        jMISaveObj.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ENTER, 0));
        jMISaveObj.setText("Save and Close");
        jMISaveObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMISaveObjActionPerformed(evt);
            }
        });
        jMFile.add(jMISaveObj);

        jMBEditWindow.add(jMFile);

        jMEditObject.setText("Edit");

        jMIMoveLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, 0));
        jMIMoveLeft.setText("Move Left");
        jMIMoveLeft.setToolTipText("Move the whole object, one pixel, left");
        jMIMoveLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveLeft);

        jMIMoveRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, 0));
        jMIMoveRight.setText("Move Right");
        jMIMoveRight.setToolTipText("Move the whole object, one pixel, right");
        jMIMoveRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveRight);

        jMIMoveUp.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, 0));
        jMIMoveUp.setText("Move Up");
        jMIMoveUp.setToolTipText("Move the whole object, one pixel, up");
        jMIMoveUp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveUpActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveUp);

        jMIMoveDown.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, 0));
        jMIMoveDown.setText("Move Down");
        jMIMoveDown.setToolTipText("Move the whole object, one pixel, down");
        jMIMoveDown.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveDownActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveDown);
        jMEditObject.add(jSeparator1);

        jMIDecreaseLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseLeft.setText("Decrease Left");
        jMIDecreaseLeft.setToolTipText("Remove one pixel from the left side of the object");
        jMIDecreaseLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseLeft);

        jMIDecreaseRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseRight.setText("Decrease Right");
        jMIDecreaseRight.setToolTipText("Remove one pixel from the right side of the object");
        jMIDecreaseRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseRight);

        jMIDecreaseTop.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseTop.setText("Decrease Top");
        jMIDecreaseTop.setToolTipText("Remove one pixel from the top side of the object");
        jMIDecreaseTop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseTopActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseTop);

        jMIDecreaseBottom.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseBottom.setText("Decrease Bottom");
        jMIDecreaseBottom.setToolTipText("Remove one pixel from the bottom side of the object");
        jMIDecreaseBottom.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseBottomActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseBottom);
        jMEditObject.add(jSeparator3);

        jMIIncreaseLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseLeft.setText("Increase Left");
        jMIIncreaseLeft.setToolTipText("Add a pixel on the left side of the object");
        jMIIncreaseLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseLeft);

        jMIIncreaseRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseRight.setText("Increase Right");
        jMIIncreaseRight.setToolTipText("Add a pixel on the right side of the object");
        jMIIncreaseRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseRight);

        jMIIncreaseTop.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseTop.setText("Increase Top");
        jMIIncreaseTop.setToolTipText("Add a pixel on the top side of the object");
        jMIIncreaseTop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseTopActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseTop);

        jMIIncreaseBottom.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseBottom.setText("Increase Bottom");
        jMIIncreaseBottom.setToolTipText("Add a pixel on the bottom side of the object");
        jMIIncreaseBottom.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseBottomActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseBottom);
        jMEditObject.add(jSeparator2);

        jMIDecreaseBox.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, java.awt.event.InputEvent.ALT_MASK));
        jMIDecreaseBox.setText("Decrease Box");
        jMIDecreaseBox.setToolTipText("Remove one pixel from all sides of the object");
        jMIDecreaseBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseBoxActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseBox);

        jMIIncreaseBox.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseBox.setText("Increase Box");
        jMIIncreaseBox.setToolTipText("Add one pixel to all sides of the object");
        jMIIncreaseBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseBoxActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseBox);
        jMEditObject.add(jSeparator4);

        jMIDeleteObj.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DELETE, 0));
        jMIDeleteObj.setText("Delete Object");
        jMIDeleteObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDeleteObjActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDeleteObj);

        jMBEditWindow.add(jMEditObject);

        jMISemanticSegOptions.setText("Actions");

        jMSelectBKG.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, java.awt.event.InputEvent.CTRL_MASK));
        jMSelectBKG.setText("Select Background");
        jMSelectBKG.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMSelectBKGActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMSelectBKG);

        jMISelectObj.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_V, 0));
        jMISelectObj.setText("Select Object");
        jMISelectObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMISelectObjActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMISelectObj);

        jMISelectErase.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_B, 0));
        jMISelectErase.setText("Select Erase");
        jMISelectErase.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMISelectEraseActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMISelectErase);
        jMISemanticSegOptions.add(jSeparator5);

        jMIIncreaseBrushSize.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_CLOSE_BRACKET, 0));
        jMIIncreaseBrushSize.setText("Increase Brush Size");
        jMIIncreaseBrushSize.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseBrushSizeActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMIIncreaseBrushSize);

        jMIDecreaseBrushSize.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_OPEN_BRACKET, 0));
        jMIDecreaseBrushSize.setText("Decrease Brush Size");
        jMIDecreaseBrushSize.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseBrushSizeActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMIDecreaseBrushSize);
        jMISemanticSegOptions.add(jSeparator6);

        jMIRunMatting.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_E, 0));
        jMIRunMatting.setText("Run Matting");
        jMIRunMatting.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIRunMattingActionPerformed(evt);
            }
        });
        jMISemanticSegOptions.add(jMIRunMatting);

        jMBEditWindow.add(jMISemanticSegOptions);

        setJMenuBar(jMBEditWindow);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void formMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_formMouseWheelMoved
        int notches = evt.getWheelRotation();
        if ((evt.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
            // Ctrl + mouse scroll => change the border size
            changeBorderSize(notches);
        } else {
            // mouse scroll => zoom
            zoomImage(notches);
        }
    }//GEN-LAST:event_formMouseWheelMoved

    private void jBSaveObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBSaveObjActionPerformed
        saveObject();
    }//GEN-LAST:event_jBSaveObjActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        // cancel the object being edited
        cancelObject();
    }//GEN-LAST:event_formWindowClosing

    private void jBChangeObjColorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBChangeObjColorActionPerformed
        changeObjColor();
    }//GEN-LAST:event_jBChangeObjColorActionPerformed

    private void jBFilterObjMapActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBFilterObjMapActionPerformed
        filterObjMap();
    }//GEN-LAST:event_jBFilterObjMapActionPerformed

    private void jBRestorePosActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBRestorePosActionPerformed
        restoreObjOrigPos();
    }//GEN-LAST:event_jBRestorePosActionPerformed

    private void jMICloseDialogActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMICloseDialogActionPerformed
        // cancel the object being edited
        cancelObject();
    }//GEN-LAST:event_jMICloseDialogActionPerformed

    private void jMIMoveLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveLeftActionPerformed
        // move the object to the left
        moveObject(-1, 0);
    }//GEN-LAST:event_jMIMoveLeftActionPerformed

    private void jMIMoveRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveRightActionPerformed
        // move the object to the right
        moveObject(1, 0);
    }//GEN-LAST:event_jMIMoveRightActionPerformed

    private void jMIMoveUpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveUpActionPerformed
        // move the object up
        moveObject(0, -1);
    }//GEN-LAST:event_jMIMoveUpActionPerformed

    private void jMIMoveDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveDownActionPerformed
        // move the object down
        moveObject(0, 1);
    }//GEN-LAST:event_jMIMoveDownActionPerformed

    private void jMIDecreaseLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseLeftActionPerformed
        // decrease left
        changeSize(1, 0, 0, 0);
    }//GEN-LAST:event_jMIDecreaseLeftActionPerformed

    private void jMIDecreaseRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseRightActionPerformed
        // decrease right
        changeSize(0, 0, -1, 0);
    }//GEN-LAST:event_jMIDecreaseRightActionPerformed

    private void jMIDecreaseTopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseTopActionPerformed
        // decrease top
        changeSize(0, 1, 0, 0);
    }//GEN-LAST:event_jMIDecreaseTopActionPerformed

    private void jMIDecreaseBottomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseBottomActionPerformed
        // decrease bottom
        changeSize(0, 0, 0, -1);
    }//GEN-LAST:event_jMIDecreaseBottomActionPerformed

    private void jMIIncreaseLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseLeftActionPerformed
        // increase left
        changeSize(-1, 0, 0, 0);
    }//GEN-LAST:event_jMIIncreaseLeftActionPerformed

    private void jMIIncreaseRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseRightActionPerformed
        // increase right
        changeSize(0, 0, 1, 0);
    }//GEN-LAST:event_jMIIncreaseRightActionPerformed

    private void jMIIncreaseTopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseTopActionPerformed
        // increase top
        changeSize(0, -1, 0, 0);
    }//GEN-LAST:event_jMIIncreaseTopActionPerformed

    private void jMIIncreaseBottomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseBottomActionPerformed
        // increase bottom
        changeSize(0, 0, 0, 1);
    }//GEN-LAST:event_jMIIncreaseBottomActionPerformed

    private void jMIDecreaseBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseBoxActionPerformed
        // decrease box
        changeSize(1, 1, -1, -1);
    }//GEN-LAST:event_jMIDecreaseBoxActionPerformed

    private void jMIIncreaseBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseBoxActionPerformed
        // increase box
        changeSize(-1, -1, 1, 1);
    }//GEN-LAST:event_jMIIncreaseBoxActionPerformed

    private void jMIDeleteObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDeleteObjActionPerformed
        deleteObject();
    }//GEN-LAST:event_jMIDeleteObjActionPerformed

    private void jMISaveObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMISaveObjActionPerformed
        // if the textfield id the focus owner, do not save the object
        if (jTFBorder.isFocusOwner()) {
            jBSetBorderActionPerformed(evt);
            return;
        }
        saveObject();
    }//GEN-LAST:event_jMISaveObjActionPerformed

    private void jCBObjTypeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jCBObjTypeItemStateChanged
        if ((objectAttributes == null) || (jCBObjType.getSelectedItem() == null)) {
            return;
        }

        CustomTreeNode selectedNode = objectAttributes.getChild(jCBObjType.getSelectedItem().toString());

        initAttributesComponents(selectedNode, jCBObjClass);
    }//GEN-LAST:event_jCBObjTypeItemStateChanged

    private void jCBObjClassItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_jCBObjClassItemStateChanged
        if ((objectAttributes == null) || (jCBObjClass.getSelectedItem() == null)) {
            return;
        }

        CustomTreeNode ctn = objectAttributes.getChild(jCBObjType.getSelectedItem().toString());

        if ((ctn != null) && (!ctn.isLeaf())) {
            CustomTreeNode selectedNode = ctn.getChild(jCBObjClass.getSelectedItem().toString());
            initAttributesComponents(selectedNode, jCBObjValue);
        }
    }//GEN-LAST:event_jCBObjClassItemStateChanged

    private void jBHistogramEqActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBHistogramEqActionPerformed
        histogramEqualisation();
    }//GEN-LAST:event_jBHistogramEqActionPerformed

    private void jBSetBorderActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBSetBorderActionPerformed
        if ((jTFBorder.getText() == null) || !Utils.isNumeric(jTFBorder.getText())) {
            return;
        }

        updateBorderValue(Integer.parseInt(jTFBorder.getText()));
    }//GEN-LAST:event_jBSetBorderActionPerformed

    private void jRBOption1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBOption1ActionPerformed
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_BACKGROUND);
    }//GEN-LAST:event_jRBOption1ActionPerformed

    private void jRBOption2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBOption2ActionPerformed
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_OBJECT);
    }//GEN-LAST:event_jRBOption2ActionPerformed

    private void jRBOption3ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBOption3ActionPerformed
        updateObjInfo(ConstantsLabeling.ACTION_TYPE_ERASE);
    }//GEN-LAST:event_jRBOption3ActionPerformed

    private void jBRunMattingCropActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBRunMattingCropActionPerformed
        // display the original image
        dPPreviewImg.reloadWorkImg();
        jBHistogramEq.setText(HIGHLIGHT_TEXT);

        // run the matting algorithm on the original image
        runMatting(Constants.RUN_MATT_ORIG_IMG);
    }//GEN-LAST:event_jBRunMattingCropActionPerformed

    private void jBRunHighlightMattActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBRunHighlightMattActionPerformed
        // set the displayed image as highlighted
        dPPreviewImg.setWorkImg(Utils.histogramEqColor(dPPreviewImg.getWorkImg()));
        jBHistogramEq.setText(ORIGINAL_TEXT);

        // run the matting
        runMatting(Constants.RUN_MATT_HIGHLIGHT_IMG);
    }//GEN-LAST:event_jBRunHighlightMattActionPerformed

    private void jSCropBrushSizeMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jSCropBrushSizeMouseReleased
        updateMouseAndBrush();
    }//GEN-LAST:event_jSCropBrushSizeMouseReleased

    private void jSCropBrushDensityMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jSCropBrushDensityMouseReleased
        updateMouseAndBrush();
    }//GEN-LAST:event_jSCropBrushDensityMouseReleased

    private void jPSemanticResultMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jPSemanticResultMouseClicked
        if (dPSemanticResultImg.getWorkImg() != null) {
            segmentResultPrev = new ImagePreview((Frame) this.getParent(), true, dPSemanticResultImg.getWorkImg(), "Image Result", true, "Filter");
            segmentResultPrev.addObserver(this);
            segmentResultPrev.setVisible(true);
        }
    }//GEN-LAST:event_jPSemanticResultMouseClicked

    private void jMSelectBKGActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMSelectBKGActionPerformed
        selectBackground();
    }//GEN-LAST:event_jMSelectBKGActionPerformed

    private void jMISelectObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMISelectObjActionPerformed
        selectObject();
    }//GEN-LAST:event_jMISelectObjActionPerformed

    private void jMISelectEraseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMISelectEraseActionPerformed
        selectEraser();
    }//GEN-LAST:event_jMISelectEraseActionPerformed

    private void jMIIncreaseBrushSizeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseBrushSizeActionPerformed
        increaseBrushSize();
    }//GEN-LAST:event_jMIIncreaseBrushSizeActionPerformed

    private void jMIDecreaseBrushSizeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseBrushSizeActionPerformed
        decreaseBrushSize();
    }//GEN-LAST:event_jMIDecreaseBrushSizeActionPerformed

    private void jMIRunMattingActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIRunMattingActionPerformed
        // run the matting app
        runMatting(Constants.RUN_MATT_ORIG_IMG);
    }//GEN-LAST:event_jMIRunMattingActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    protected javax.swing.JButton jBFilterObjMap;
    protected javax.swing.JButton jBHistogramEq;
    protected javax.swing.JButton jBRunHighlightMatt;
    protected javax.swing.JButton jBRunMattingCrop;
    private javax.swing.JButton jBSetBorder;
    protected javax.swing.JCheckBox jCBMergeBKG;
    protected javax.swing.JComboBox<String> jCBObjClass;
    protected javax.swing.JComboBox<String> jCBObjType;
    protected javax.swing.JComboBox<String> jCBObjValue;
    protected javax.swing.JComboBox<String> jCBOccluded;
    private javax.swing.JLabel jLBorder;
    protected javax.swing.JLabel jLBrushPreview;
    protected javax.swing.JLabel jLBrushSizePreview;
    private javax.swing.JLabel jLClass;
    private javax.swing.JLabel jLCropBrushDensity;
    private javax.swing.JLabel jLCropBrushSize;
    protected javax.swing.JLabel jLObjId;
    private javax.swing.JLabel jLOccluded;
    private javax.swing.JLabel jLType;
    private javax.swing.JLabel jLValue;
    private javax.swing.JMenuItem jMISelectErase;
    private javax.swing.JMenuItem jMISelectObj;
    private javax.swing.JMenu jMISemanticSegOptions;
    private javax.swing.JMenuItem jMSelectBKG;
    private javax.swing.JPanel jPBrushOptions;
    private javax.swing.JPanel jPMoreOpt;
    private javax.swing.JPanel jPOptions;
    protected javax.swing.JPanel jPPreviewImg;
    protected javax.swing.JPanel jPRBOptions;
    protected javax.swing.JPanel jPSemanticResult;
    protected javax.swing.JRadioButton jRBOption1;
    protected javax.swing.JRadioButton jRBOption2;
    protected javax.swing.JRadioButton jRBOption3;
    protected javax.swing.JSlider jSCropBrushDensity;
    protected javax.swing.JSlider jSCropBrushSize;
    private javax.swing.JPopupMenu.Separator jSeparator5;
    private javax.swing.JPopupMenu.Separator jSeparator6;
    protected javax.swing.JTextField jTFBorder;
    // End of variables declaration//GEN-END:variables
} public class PolygonObjEdit extends BoxEdit {

    private final transient Polygon originalPolygon;

    /**
     *
     * @param parent the parent component of the dialog
     * @param frameImage the original image, in original size
     * @param currentObj the object being segmented
     * @param objectAttributes the list of object attributes: type, class, value
     * @param actionOwner the scope of the dialog: create new box, edit existing
     * one
     * @param objColorsList the list of already used colors (for other objects)
     * @param userPreferences user preferences regarding application
     * configuration
     */
    public PolygonObjEdit(Frame parent,
            BufferedImage frameImage,
            Objects currentObj,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences) {
        super(parent, frameImage, currentObj, objectAttributes, actionOwner, objColorsList, userPreferences);

        drawingType = 0;

        jPRBOptions.setVisible(true);

        jRBOption1.setForeground(Color.blue);
        jRBOption1.setText("Edit");

        jRBOption2.setForeground(new Color(0, 153, 51));
        jRBOption2.setText("Add Vertex");

        jRBOption3.setForeground(Color.red);
        jRBOption3.setText("Remove Vertex");

        // keep a copy of the initial object
        originalPolygon = Utils.deepCopyPoly(((ObjectPolygon) currentObject).getPolygon());
    }

    @Override
    protected void drawObjContour(Graphics2D g2d) {
        // draw the polygon object
        setPolygon();
    }

    /**
     * Draw the outer box of the object; a rectangle which is resized to the
     * proper image size.
     *
     * @param g2d the graphics object
     * @return the rectangle drawn on the image
     */
    private void setPolygon() {
        if (dPPreviewImg == null) {
            return;
        }

        Polygon poly = Utils.deepCopyPoly(((ObjectPolygon) currentObject).getPolygon());

        // shift the polygon with the size of the border offset
        for (int index = 0; index < poly.npoints; index++) {
            poly.xpoints[index] -= displayBox.x;
            poly.ypoints[index] -= displayBox.y;
        }

        dPPreviewImg.setCurrentPolygon(poly, ((ObjectPolygon) currentObject).getPolygon());
    }

    @Override
    protected void applyObjProperties() {
        dPPreviewImg.setDrawType(DrawConstants.DrawType.EDIT_POLYGON_VERTICES);

        setPolygon();

        dPPreviewImg.setObjColor(objectColor);
    }

    @Override
    protected void updateObjInfo(int actionType) {
        this.drawingType = actionType;
        dPPreviewImg.setActionType(actionType);
        dPPreviewImg.setHighlightPoints(false);
        dPPreviewImg.resetPolygonIndex();
    }

    @Override
    protected void resetObjCoordinates() {
        // reload the initial state of the polygon
        ((ObjectPolygon) currentObject).setPolygon(Utils.deepCopyPoly(originalPolygon));
    }

    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof ObservedActions.Action) {
            ObservedActions.Action type = ((ObservedActions.Action) arg);

            if (ObservedActions.Action.UPDATE_POLYGON_VERTICES == type) {
                Polygon poly = dPPreviewImg.getCurrentPolygonImg();
                // shift the polygon with the size of the border offset
                for (int index = 0; index < poly.npoints; index++) {
                    poly.xpoints[index] += displayBox.x;
                    poly.ypoints[index] += displayBox.y;
                }

                ((ObjectPolygon) currentObject).setPolygon(Utils.deepCopyPoly(poly));
                ((ObjectPolygon) currentObject).computeOuterBBoxCurObj();
            }
        }

    }

} public abstract class Objects {

    /**
     * The object id in the video - used to track the object in the video
     * (constant for several frames).
     */
    private long objectId;

    /**
     * The type of segmentation used: bounding box, scribble etc.
     *
     */
    private String segmentationType;

    /**
     * The segmentation source: manual, automatic.
     */
    protected String segmentationSource;

    /**
     * The type of segmented object.
     */
    private String objType;

    /**
     * The class of segmented object.
     */
    private String objClass;

    /**
     * The value of segmented object.
     */
    private String objValue;

    /**
     * Shows if the object is occluded or not.
     */
    private String occluded;

    /**
     * The color of the displayed object.
     */
    private Color color;

    /**
     * The outer bounding box containing all the crops - panel coordinates.
     */
    protected Rectangle outerBBox;

    /**
     * The predicted coordinates of the object in the next frame.
     */
    private volatile Rectangle predictedBBox;
    /**
     * The predicted coordinates of the object in the next frame using HOG
     * features.
     */
    private volatile Rectangle hogPredictedBBox;
    /**
     * The predicted coordinates of the object in the next frame using HOG
     * features and openCV SVM.
     */
    private volatile Rectangle svmPredictedBBox;

    /**
     * The prediction is valid and can be used.
     */
    private final AtomicBoolean validPrediction;

    /**
     * The list of timings needed to complete the segmentation of the object.
     */
    protected List<Timings> labelingDuration;

    /**
     * Local user preferences.
     */
    private ObjectPreferences userPreference;


    /**
     * The thread on which the tracker is running.
     */
    private Thread trackerThread;

    /**
     * Create an instance of the Objects class.
     */
    public Objects() {
        this.labelingDuration = new ArrayList<>();
        this.predictedBBox = new Rectangle(0, 0, 0, 0);
        this.hogPredictedBBox = new Rectangle(0, 0, 0, 0);
        this.svmPredictedBBox = new Rectangle(0, 0, 0, 0);
        this.userPreference = new ObjectPreferences();
        this.validPrediction = new AtomicBoolean(false);
        
        // avoid null pointer in the object attributes
        this.objType = "";
        this.objClass = "";
        this.objValue = "";
        this.occluded = "";
    }

    /**
     * Gets the user preferences for the current object
     *
     * @return user preferences
     */
    public ObjectPreferences getUserPreference() {
        return userPreference;
    }

    /**
     * Sets the user preferences for the current object
     *
     * @param userPreference user preferences
     */
    public void setUserPreference(ObjectPreferences userPreference) {
        this.userPreference = userPreference;
    }

    /**
     * Returns the id of the object.
     *
     * @return - Returns the id of the object.
     */
    public long getObjectId() {
        return objectId;
    }

    /**
     * Sets the id of the object.
     *
     * @param objectId - the id of the object
     */
    public void setObjectId(long objectId) {
        this.objectId = objectId;
    }

    /**
     * Returns the type of object: bounding box, scribble, 3D bounding box.
     *
     * @return - the type of object
     */
    public String getSegmentationType() {
        return segmentationType;
    }

    /**
     * Sets the type of object: bounding box, scribble, 3D bounding box.
     *
     * @param segmentationType - the type of object
     */
    public void setSegmentationType(String segmentationType) {
        this.segmentationType = segmentationType;
    }

    /**
     * Returns the type of segmentation used for the current object: manual,
     * automatic etc.
     *
     * @return - the type of segmentation used for the current object
     */
    public String getSegmentationSource() {
        return segmentationSource;
    }

    /**
     * Sets the type of segmentation used for the object.
     *
     * @param segmentationSource - the segmentation used for the object: manual, automatic etc.
     */
    public void setSegmentationSource(String segmentationSource) {
        this.segmentationSource = segmentationSource;
    }

    /**
     * Set the type of the object which is being labeled.
     *
     * @param objectType - the name of the object type
     */
    public void setObjectType(String objectType) {
        this.objType = objectType;
    }

    /**
     * Return the name of the type of object being labeled.
     *
     * @return - the name of the object type
     */
    public String getObjectType() {
        return this.objType;
    }

    /**
     * Set the class of the object which is being labeled.
     *
     * @param objectClass - the name of the object class
     */
    public void setObjectClass(String objectClass) {
        this.objClass = objectClass;
    }

    /**
     * Return the name of the class of object being labeled.
     *
     * @return - the name of the object class
     */
    public String getObjectClass() {
        return this.objClass;
    }

    /**
     * Set the value of the object which is being labeled.
     *
     * @param objectValue - the name of the object value
     */
    public void setObjectValue(String objectValue) {
        this.objValue = objectValue;
    }

    /**
     * Return the name of the value of object being labeled.
     *
     * @return - the name of the object value
     */
    public String getObjectValue() {
        return objValue;
    }

    /**
     * Return the occlusion characteristic of the object. It will be defined
     * with the customer every time.
     *
     * @return - the string defining if the object is occluded or not
     */
    public String getOccluded() {
        return occluded;
    }

    /**
     * Set the occlusion of the object.
     *
     * @param occluded - the string defining if the object is occluded or not
     */
    public void setOccluded(String occluded) {
        this.occluded = occluded;
    }

    /**
     * Returns the color used to draw the object.
     *
     * @return Returns the color used to draw the object.
     */
    public Color getColor() {
        return color;
    }

    /**
     * Sets the color of the object.
     *
     * @param color - the color of the object
     */
    public void setColor(Color color) {
        this.color = color;
    }

    /**
     * Returns the rectangle which limits the entire object, the outer bounds of
     * the object - image coordinates.
     *
     * @return - the rectangle containing the object
     */
    public Rectangle getOuterBBox() {
        return outerBBox;
    }

    /**
     * Return the openCV predicted bounding box.
     *
     * @return the predicted position of the object in the next frame
     */
    public Rectangle getPredictedBBox() {
        return predictedBBox;
    }

    /**
     * Return the position of the object in the next frame, based on HOG
     * features template matching.
     *
     * @return - the position of the object in the next frame
     */
    public Rectangle getHogPredictedBBox() {
        return hogPredictedBBox;
    }

    /**
     * Set the position of the object in the next frame. The prediction is done
     * based on matching of HOG features.
     *
     * @param hogPredictedBBox - the position of the object in the next frame
     */
    public void setHogPredictedBBox(Rectangle hogPredictedBBox) {
        this.hogPredictedBBox = hogPredictedBBox;
    }

    /**
     * Return the position of the object in the next frame, based on openCV SVM
     * with HOG features.
     *
     * @return - the position of the object in the next frame
     */
    public synchronized Rectangle getSvmPredictedBBox() {
        return svmPredictedBBox;
    }

    /**
     * Set the position of the object in the next frame. The prediction is done
     * based on openCV SVM with HOG features.
     *
     * @param svmPredictedBBox - the position of the object in the next frame
     */
    public synchronized void setSvmPredictedBBox(Rectangle svmPredictedBBox) {
        this.svmPredictedBBox = svmPredictedBBox;
    }

    /**
     * Compares if the given rectangle is the same with the outer box of the
     * object; it checks if there is overlapping.
     *
     * @param rectCompare - the bounding box which will be checked for overlapping
     * @return - true if the bounding boxes overlap and false otherwise
     */
    public boolean isEqualOuterBBox(Rectangle rectCompare) {
        return outerBBox.equals(rectCompare);
    }

    /**
     * Creates a new timings object to save the time needed for the object.
     * (position 0 is the segmentation time and all the others are edit times)
     *
     * @return - the last created timing, where to map the duration of the current action (new object, edit)
     */
    public Timings newLabelingDuration() {
        Timings times = new Timings();
        this.labelingDuration.add(times);
        return labelingDuration.get(labelingDuration.size() - 1);
    }

    /**
     * *Return the current timer of the object, which is always the last one.
     *
     * @return - the current timer for the object
     */
    public Timings getCurrentLabelingDuration() {
        return labelingDuration.get(labelingDuration.size() - 1);
    }

    /**
     * Returns the list of labeling durations for the object. The first entry is
     * the labeling time and the others are coming from the edit mode.
     *
     * @return - the list of times needed for the segmentation of the object
     */
    public String getLabelingDuration() {

        StringBuilder durationString = new StringBuilder();

        labelingDuration.forEach((times) -> {
            durationString.append(times.getNeededTime()).append(",");
        });

        // remove the last comma
        if (durationString.length() > 0) {
            durationString.setLength(durationString.length() - 1);
            return durationString.toString();
        }
        return "";
    }

    /**
     * Set the labeling duration with an existent duration.
     *
     * @param labelingDuration - the new labeling duration
     */
    public void setLabelingDuration(ArrayList<Timings> labelingDuration) {
        this.labelingDuration = labelingDuration;
    }

    /**
     * Return the thread on which the tracking is done.
     *
     * @return the thread on which the tracking is done
     */
    public Thread getTrackerThread() {
        return trackerThread;
    }

    /**
     * Return true if the prediction of the object is valid and false if not.
     *
     * @return - true if the prediction of the object is valid and false if not.
     */
    public Boolean isValidPrediction() {
        return validPrediction.get();
    }

    /**
     * Set the status of the prediction: true if the prediction of the object is
     * valid and false if not.
     *
     * @param validPrediction true if the prediction of the object is valid and false if not
     */
    public void setValidPrediction(Boolean validPrediction) {
        this.validPrediction.set(validPrediction);
    }

    /**
     * Moves the whole object, together with the components to the new location
     * specified by the parameter.
     *
     * @param newPositionImg - the new location of the object - image coordinates
     * @param frameSize      the size of the original image
     */
    public void move(Point newPositionImg, Dimension frameSize) {
        int xOffset = newPositionImg.x - outerBBox.x;
        int yOffset = newPositionImg.y - outerBBox.y;

        move(xOffset, yOffset, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Moves the whole object, together with the components to the new location
     * specified by the parameter.
     *
     * @param newPositionPanel - the new location of the object - image coordinates
     * @param resizeRate       - the resize ratio between the image and the panel
     * @param frameSize        the size of the original image
     */
    public void move(Point newPositionPanel, Resize resizeRate, Dimension frameSize) {
        Point newPositionImg = resizeRate.resizedToOriginal(newPositionPanel);

        int xOffset = newPositionImg.x - outerBBox.x;
        int yOffset = newPositionImg.y - outerBBox.y;

        // the coordinates are already image, therefore the resize shall be 1.0.
        move(xOffset, yOffset, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Changes the size of the object to the new specified size (the object
     * outer box is changed!!!).
     *
     * @param newSize   the new size of the box
     * @param frameSize the size of the original image
     */
    public void changeSize(Rectangle newSize, Dimension frameSize) {
        int offsetLeft = newSize.x - outerBBox.x;
        int offsetTop = newSize.y - outerBBox.y;
        int offsetRight = (newSize.x + newSize.width) - (outerBBox.x + outerBBox.width);
        int offsetBottom = (newSize.y + newSize.height) - (outerBBox.y + outerBBox.height);

        changeSize(offsetLeft, offsetTop, offsetRight, offsetBottom, outerBBox, new Resize(1.0, 1.0), frameSize);
    }

    /**
     * Save the object predicted coordinates for the next frame
     *
     * @param newPosition predicted position in the next frame
     */
    public void setPredictedCoordinates(Point newPosition) {
        predictedBBox.setLocation(newPosition.x, newPosition.y);
    }


    /**
     * Sets the rectangle which limits the entire object, the outer bounds of
     * the object.
     *
     * @param outerBBox - the rectangle containing the object
     */
    public void setOuterBBox(Rectangle outerBBox) {
        this.outerBBox = outerBBox;
    }

	/**
     * Moves one box with the specified offset and returns the new image
     * coordinates.
     *
     * @param xOffset - how much should the object move on the X axis
     * @param yOffset - how much the object should move on the Y axis
     * @param boxPosOrig - the position of the selected box in image coordinates
     * @return - the new image coordinates of the box
     */
    protected Rectangle moveBox(int xOffset, int yOffset, Rectangle boxPosOrig) {
        boxPosOrig.setLocation(boxPosOrig.x + xOffset, boxPosOrig.y + yOffset);

        return boxPosOrig;
    }
	
    /**
     * Compute the position and size of the outer bounding box containing the
     * crops of the object.
     */
    public abstract void computeOuterBBoxCurObj();

    /**
     * Moves the object completely in the specified direction (crops, scribbles,
     * outer box etc.).
     *
     * @param xOffset       - how much should the object move on the X axis
     * @param yOffset       - how much the object should move on the Y axis
     * @param coordPanelBox - the coordinates of the selected box in image coordinates
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @param frameSize     the size of the original image
     */
    public abstract void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize);

	/**
     * Moves the object completely in the specified direction (crops, scribbles,
     * outer box etc.).
     *
     * @param xOffset - how much should the object move on the X axis
     * @param yOffset - how much the object should move on the Y axis
     * @param frameSize the size of the original image
     */
    public abstract void move(int xOffset, int yOffset, Dimension frameSize);
	
    /**
     * Delete the selected box. The method checkd if the box is the same as the
     * whole object. If it is true, it will not delete it. The parent function
     * is responsible for that. This method can manage only the parts of the
     * object, not the object itself.
     *
     * @param coordPanelBox - the coordinates of the selected box in image
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @return - true if one of the boxes was erased and false if the whole object shall be erased from outside.
     */
    public abstract boolean remove(Rectangle coordPanelBox, Resize resizeRate);

    /**
     * Changes the size of the object in each of the directions, with the amount
     * of specified pixels.
     *
     * @param left          - how much should the object be modified on the left side
     * @param top           - how much should the object be modified on the top part
     * @param right         - how much should the object be modified on the right side
     * @param bottom        - how much should the object be modified on the bottom part
     * @param coordPanelBox - the coordinates of the selected box in image coordinates
     * @param resizeRate    - the resize rate between the image coordinates and the panel coordinates
     * @param frameSize     the size of the original image
     */
    public abstract void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize);

    /**
     * Verifies if the object contains the given bounding box (as a crop or as
     * the objects outer box).
     *
     * @param coordPanelBox - the coordinates of the box in panel coordinates
     * @param resizeRate    - the resize ratio between the image and the panel
     * @return - true if the object contains it; false if not
     */
    public abstract boolean contains(Rectangle coordPanelBox, Resize resizeRate);
} public class AttributesDefinition extends javax.swing.JDialog implements Observer {

    /**
     * logger instance
     */
    private final transient org.slf4j.Logger log = LoggerFactory.getLogger(AttributesDefinition.class);

    /**
     * The name of the objects tab.
     */
    public static final String TAB_OBJECT = "Object";
    /**
     * The name of the frame tab.
     */
    public static final String TAB_FRAME = "Frame";

    /**
     * Encapsulates the list of frame attributes: illumination, weather etc.
     */
    private final LAttributesFrame frameAttribString;

    /**
     * Encapsulates the list of object attributes: type, class, value.
     */
    private CustomTreeNode objectAttributes;

    /**
     * Encapsulates the list of frame attributes: illumination, weather etc.
     */
    private CustomTreeNode<String> frameAttributes;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    private final transient NotifyObservers observable = new NotifyObservers();

    /**
     * The tree mapping the object attributes.
     */
    private DynamicTree objTreePanel;

    /**
     * The tree mapping the frame attributes.
     */
    private DynamicTree frameTreePanel;

    /**
     * The depth of the object attributes tree.
     */
    private static final int OBJ_ATTRIB_TREE_DEPTH = 4;

    /**
     * The depth of the object attributes tree.
     */
    private static final int FRAME_ATTRIB_TREE_DEPTH = 3;

    /**
     * The list of fixed frame attributes.
     */
    private static final String[] FRAME_ATTRIBUTE_NAMES = {Constants.FRAME_ATTRIBUTES_TEXT, "Illumination", "Weather", "Road Type", "Road Event", "Country"};

    /**
     * Creates new form AttributesDefinition
     *
     * @param parent the parent component of the dialog
     * @param frameAttrib the frame attributes to be displayed on the interface,
     * as an array list of strings.
     * @param objAttrib the object attributes to be displayed on the interface,
     * as an array list of strings.
     */
    public AttributesDefinition(java.awt.Frame parent, LAttributesFrame frameAttrib, CustomTreeNode objAttrib) {
        super(parent, true);

        initComponents();

        addKeyboardListener();

        frameAttribString = frameAttrib;
        objectAttributes = objAttrib;

        loadFrameAttributes();

        loadObjectAttributes();

        prepareFrame();
    }

    /**
     * Code related to the frame like: pack, set location etc.
     */
    private void prepareFrame() {
        this.pack();
        this.setMinimumSize(this.getSize());

        this.setLocationRelativeTo(null);
    }

    /**
     * Set the frame attributes from the application configuration.
     */
    private void loadFrameAttributes() {
        populateFrameTree(true);

        configureFrameAttribTree();
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);

        // notify to remove the gui key event dispatcher
        observable.notifyObservers(ObservedActions.Action.REMOVE_GUI_KEY_EVENT_DISPATCHER);
    }

    /**
     * Add keyboard listener to the dialog window.
     */
    private void addKeyboardListener() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher((KeyEvent e) -> {
            int key = e.getKeyCode();

            if (key == KeyEvent.VK_ESCAPE) {
                closeWindow();
            }

            return false;
        });
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jPContainer = new javax.swing.JPanel();
        jTPAttributes = new javax.swing.JTabbedPane();
        jPFrameAttrib = new javax.swing.JPanel();
        jPFrameAttribExpand = new javax.swing.JPanel();
        jBFrameExpand = new javax.swing.JButton();
        jBFrameCollapse = new javax.swing.JButton();
        jPFrameAttribPreviewTree = new javax.swing.JPanel();
        javax.swing.JPanel jPFrameButtons = new javax.swing.JPanel();
        jBRemoveFrameAttrib = new javax.swing.JButton();
        jBClearFrameAttrib = new javax.swing.JButton();
        jPFrameAttribButtons = new javax.swing.JPanel();
        jBAddFrameAttrib = new javax.swing.JButton();
        jTFFrameAttribName = new javax.swing.JTextField();
        javax.swing.JPanel jPObjectDefinition = new javax.swing.JPanel();
        jPObjAttributesDefinition = new javax.swing.JPanel();
        jPObjAttribPreviewTree = new javax.swing.JPanel();
        javax.swing.JPanel jPObjButtons = new javax.swing.JPanel();
        jBRemoveObjAttrib = new javax.swing.JButton();
        jBClearObjAttrib = new javax.swing.JButton();
        jPObjAttribButtons = new javax.swing.JPanel();
        jBAddObjAttrib = new javax.swing.JButton();
        jTFObjAttribName = new javax.swing.JTextField();
        jPObjAttribExpand = new javax.swing.JPanel();
        jBObjExpand = new javax.swing.JButton();
        jBObjCollapse = new javax.swing.JButton();
        jBSaveAttributes = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setTitle("Attributes");
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });
        getContentPane().setLayout(new java.awt.GridBagLayout());

        jPContainer.setBackground(new java.awt.Color(255, 255, 255));
        jPContainer.setLayout(new java.awt.GridBagLayout());

        jTPAttributes.setBackground(new java.awt.Color(255, 255, 255));
        jTPAttributes.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jTPAttributesStateChanged(evt);
            }
        });

        jPFrameAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jPFrameAttrib.setLayout(new java.awt.GridBagLayout());

        jPFrameAttribExpand.setBackground(new java.awt.Color(255, 255, 255));
        jPFrameAttribExpand.setLayout(new java.awt.GridBagLayout());

        jBFrameExpand.setBackground(new java.awt.Color(255, 255, 255));
        jBFrameExpand.setText("+");
        jBFrameExpand.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBFrameExpandActionPerformed(evt);
            }
        });
        jPFrameAttribExpand.add(jBFrameExpand, new java.awt.GridBagConstraints());

        jBFrameCollapse.setBackground(new java.awt.Color(255, 255, 255));
        jBFrameCollapse.setText("-");
        jBFrameCollapse.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBFrameCollapseActionPerformed(evt);
            }
        });
        jPFrameAttribExpand.add(jBFrameCollapse, new java.awt.GridBagConstraints());

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        jPFrameAttrib.add(jPFrameAttribExpand, gridBagConstraints);

        jPFrameAttribPreviewTree.setBackground(new java.awt.Color(255, 255, 255));
        jPFrameAttribPreviewTree.setPreferredSize(new java.awt.Dimension(200, 320));
        jPFrameAttribPreviewTree.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPFrameAttrib.add(jPFrameAttribPreviewTree, gridBagConstraints);

        jPFrameButtons.setBackground(new java.awt.Color(255, 255, 255));
        jPFrameButtons.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPFrameButtons.setLayout(new java.awt.GridBagLayout());

        jBRemoveFrameAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBRemoveFrameAttrib.setText("Remove");
        jBRemoveFrameAttrib.setToolTipText("Removes the selected attributes.");
        jBRemoveFrameAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBRemoveFrameAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBRemoveFrameAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPFrameButtons.add(jBRemoveFrameAttrib, gridBagConstraints);

        jBClearFrameAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBClearFrameAttrib.setText("Clear All");
        jBClearFrameAttrib.setToolTipText("Removes ALL the attributes!");
        jBClearFrameAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBClearFrameAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBClearFrameAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPFrameButtons.add(jBClearFrameAttrib, gridBagConstraints);

        jPFrameAttribButtons.setBackground(new java.awt.Color(255, 255, 255));
        jPFrameAttribButtons.setBorder(javax.swing.BorderFactory.createTitledBorder(Constants.FRAME_ATTRIBUTES_TEXT));
        jPFrameAttribButtons.setLayout(new java.awt.GridBagLayout());

        jBAddFrameAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBAddFrameAttrib.setText("Add");
        jBAddFrameAttrib.setToolTipText("Add a new attribute, under the selected attribute.");
        jBAddFrameAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBAddFrameAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBAddFrameAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPFrameAttribButtons.add(jBAddFrameAttrib, gridBagConstraints);

        jTFFrameAttribName.setBackground(new java.awt.Color(255, 255, 255));
        jTFFrameAttribName.setPreferredSize(new java.awt.Dimension(200, 24));
        jTFFrameAttribName.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                jTFFrameAttribNameKeyReleased(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPFrameAttribButtons.add(jTFFrameAttribName, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPFrameButtons.add(jPFrameAttribButtons, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_START;
        jPFrameAttrib.add(jPFrameButtons, gridBagConstraints);

        jTPAttributes.addTab(TAB_FRAME, jPFrameAttrib);

        jPObjectDefinition.setBackground(new java.awt.Color(255, 255, 255));
        jPObjectDefinition.setLayout(new java.awt.GridBagLayout());

        jPObjAttributesDefinition.setBackground(new java.awt.Color(255, 255, 255));
        jPObjAttributesDefinition.setLayout(new java.awt.GridBagLayout());

        jPObjAttribPreviewTree.setBackground(new java.awt.Color(255, 255, 255));
        jPObjAttribPreviewTree.setPreferredSize(new java.awt.Dimension(200, 320));
        jPObjAttribPreviewTree.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPObjAttributesDefinition.add(jPObjAttribPreviewTree, gridBagConstraints);

        jPObjButtons.setBackground(new java.awt.Color(255, 255, 255));
        jPObjButtons.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPObjButtons.setLayout(new java.awt.GridBagLayout());

        jBRemoveObjAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBRemoveObjAttrib.setText("Remove");
        jBRemoveObjAttrib.setToolTipText("Removes the selected attributes.");
        jBRemoveObjAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBRemoveObjAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBRemoveObjAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjButtons.add(jBRemoveObjAttrib, gridBagConstraints);

        jBClearObjAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBClearObjAttrib.setText("Clear All");
        jBClearObjAttrib.setToolTipText("Removes ALL the attributes!");
        jBClearObjAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBClearObjAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBClearObjAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjButtons.add(jBClearObjAttrib, gridBagConstraints);

        jPObjAttribButtons.setBackground(new java.awt.Color(255, 255, 255));
        jPObjAttribButtons.setBorder(javax.swing.BorderFactory.createTitledBorder(Constants.OBJECT_ATTRIBUTES_TEXT));
        jPObjAttribButtons.setLayout(new java.awt.GridBagLayout());

        jBAddObjAttrib.setBackground(new java.awt.Color(255, 255, 255));
        jBAddObjAttrib.setText("Add");
        jBAddObjAttrib.setToolTipText("Add a new attribute, under the selected attribute.");
        jBAddObjAttrib.setPreferredSize(new java.awt.Dimension(90, 32));
        jBAddObjAttrib.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBAddObjAttribActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttribButtons.add(jBAddObjAttrib, gridBagConstraints);

        jTFObjAttribName.setBackground(new java.awt.Color(255, 255, 255));
        jTFObjAttribName.setPreferredSize(new java.awt.Dimension(200, 24));
        jTFObjAttribName.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                jTFObjAttribNameKeyReleased(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjAttribButtons.add(jTFObjAttribName, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPObjButtons.add(jPObjAttribButtons, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LAST_LINE_START;
        jPObjAttributesDefinition.add(jPObjButtons, gridBagConstraints);

        jPObjAttribExpand.setBackground(new java.awt.Color(255, 255, 255));
        jPObjAttribExpand.setLayout(new java.awt.GridBagLayout());

        jBObjExpand.setBackground(new java.awt.Color(255, 255, 255));
        jBObjExpand.setText("+");
        jBObjExpand.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBObjExpandActionPerformed(evt);
            }
        });
        jPObjAttribExpand.add(jBObjExpand, new java.awt.GridBagConstraints());

        jBObjCollapse.setBackground(new java.awt.Color(255, 255, 255));
        jBObjCollapse.setText("-");
        jBObjCollapse.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBObjCollapseActionPerformed(evt);
            }
        });
        jPObjAttribExpand.add(jBObjCollapse, new java.awt.GridBagConstraints());

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        jPObjAttributesDefinition.add(jPObjAttribExpand, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPObjectDefinition.add(jPObjAttributesDefinition, gridBagConstraints);

        jTPAttributes.addTab(TAB_OBJECT, jPObjectDefinition);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(10, 10, 5, 10);
        jPContainer.add(jTPAttributes, gridBagConstraints);

        jBSaveAttributes.setBackground(new java.awt.Color(255, 255, 255));
        jBSaveAttributes.setText("Save");
        jBSaveAttributes.setToolTipText("Saves and applies the attributes.");
        jBSaveAttributes.setPreferredSize(new java.awt.Dimension(90, 32));
        jBSaveAttributes.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBSaveAttributesActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.PAGE_START;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 10, 10);
        jPContainer.add(jBSaveAttributes, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        getContentPane().add(jPContainer, gridBagConstraints);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jBSaveAttributesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBSaveAttributesActionPerformed
        saveAttributes();
    }//GEN-LAST:event_jBSaveAttributesActionPerformed

    private void jBAddObjAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBAddObjAttribActionPerformed
        addNewObjectNode();
    }//GEN-LAST:event_jBAddObjAttribActionPerformed

    private void jBRemoveObjAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBRemoveObjAttribActionPerformed
        objTreePanel.removeCurrentNode();
    }//GEN-LAST:event_jBRemoveObjAttribActionPerformed

    private void jBClearObjAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBClearObjAttribActionPerformed
        if (JOptionPane.YES_OPTION == Messages.showWarningYesNoMessager(this,
                "Are you sure you want to clear all the object attributes?\n(The root node will not be removed.)",
                "Attention!!")) {
            objTreePanel.clear();
        }
    }//GEN-LAST:event_jBClearObjAttribActionPerformed

    private void jTFObjAttribNameKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTFObjAttribNameKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            addNewObjectNode();
        }
    }//GEN-LAST:event_jTFObjAttribNameKeyReleased

    private void jBObjExpandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBObjExpandActionPerformed
        objTreePanel.expandRows();
    }//GEN-LAST:event_jBObjExpandActionPerformed

    private void jBObjCollapseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBObjCollapseActionPerformed
        objTreePanel.collapseRows();
    }//GEN-LAST:event_jBObjCollapseActionPerformed

    private void jBFrameExpandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBFrameExpandActionPerformed
        frameTreePanel.expandRows();
    }//GEN-LAST:event_jBFrameExpandActionPerformed

    private void jBFrameCollapseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBFrameCollapseActionPerformed
        frameTreePanel.collapseRows();
    }//GEN-LAST:event_jBFrameCollapseActionPerformed

    private void jBRemoveFrameAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBRemoveFrameAttribActionPerformed
        // do not add a node if it is wanted to be the child of the root node or
        for (String attribute : FRAME_ATTRIBUTE_NAMES) {
            if (frameTreePanel.getSelection().equalsIgnoreCase(attribute)) {
                // Either one of the main attributes, or the root was selected.
                Toolkit.getDefaultToolkit().beep();
                return;
            }
        }

        frameTreePanel.removeCurrentNode();
    }//GEN-LAST:event_jBRemoveFrameAttribActionPerformed

    private void jBClearFrameAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBClearFrameAttribActionPerformed
        if (JOptionPane.YES_OPTION == Messages.showWarningYesNoMessager(this,
                "Are you sure you want to clear all the frame attributes?\n(The main tree structure cannot be changed; it will not be removed.)",
                "Attention!!")) {
            clearFrameAttributes();
        }
    }//GEN-LAST:event_jBClearFrameAttribActionPerformed

    private void jBAddFrameAttribActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBAddFrameAttribActionPerformed
        addNewFrameNode();
    }//GEN-LAST:event_jBAddFrameAttribActionPerformed

    private void jTFFrameAttribNameKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jTFFrameAttribNameKeyReleased
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            addNewFrameNode();
        }
    }//GEN-LAST:event_jTFFrameAttribNameKeyReleased

    private void jTPAttributesStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jTPAttributesStateChanged
        // change the focus to the correct tab
        requestFocusedComponent();
    }//GEN-LAST:event_jTPAttributesStateChanged

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        closeWindow();
    }//GEN-LAST:event_formWindowClosing

    /**
     * Switches the tabbed pane to the wanted tab, specified by the name.
     *
     * @param tabName the name of the tab where it should change
     */
    public void switchToTab(String tabName) {
        int screenTabIndex = 0;

        // search for the index of the wanted tab in the list of tabs
        for (int index = 0; index < jTPAttributes.getTabCount(); index++) {
            if (tabName.equals(jTPAttributes.getTitleAt(index))) {
                screenTabIndex = index;
            }
        }

        // switch to the wanted tab
        jTPAttributes.setSelectedIndex(screenTabIndex);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(AttributesDefinition.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(() -> new AttributesDefinition(null, null, null).setVisible(true));
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jBAddFrameAttrib;
    private javax.swing.JButton jBAddObjAttrib;
    private javax.swing.JButton jBClearFrameAttrib;
    private javax.swing.JButton jBClearObjAttrib;
    private javax.swing.JButton jBFrameCollapse;
    private javax.swing.JButton jBFrameExpand;
    private javax.swing.JButton jBObjCollapse;
    private javax.swing.JButton jBObjExpand;
    private javax.swing.JButton jBRemoveFrameAttrib;
    private javax.swing.JButton jBRemoveObjAttrib;
    private javax.swing.JButton jBSaveAttributes;
    private javax.swing.JPanel jPContainer;
    private javax.swing.JPanel jPFrameAttrib;
    private javax.swing.JPanel jPFrameAttribButtons;
    private javax.swing.JPanel jPFrameAttribExpand;
    private javax.swing.JPanel jPFrameAttribPreviewTree;
    private javax.swing.JPanel jPObjAttribButtons;
    private javax.swing.JPanel jPObjAttribExpand;
    private javax.swing.JPanel jPObjAttribPreviewTree;
    private javax.swing.JPanel jPObjAttributesDefinition;
    private javax.swing.JTextField jTFFrameAttribName;
    private javax.swing.JTextField jTFObjAttribName;
    private javax.swing.JTabbedPane jTPAttributes;
    // End of variables declaration//GEN-END:variables

    @Override
    public void update(Observable o, Object arg) {
        // update the border of the panel where new attributes are added, with the data from the selected node  
        if (objTreePanel != null) {
            ((TitledBorder) jPObjAttribButtons.getBorder()).setTitle(objTreePanel.getSelection());
            jPObjAttribButtons.repaint();
        }

        if (frameTreePanel != null) {
            ((TitledBorder) jPFrameAttribButtons.getBorder()).setTitle(frameTreePanel.getSelection());
            jPFrameAttribButtons.repaint();
        }

        requestFocusedComponent();
    }

    /**
     * Add new node in the object attributes tree.
     */
    private void addNewObjectNode() {
        objTreePanel.addObject(jTFObjAttribName.getText());

        jTFObjAttribName.setText("");
        jTFObjAttribName.requestFocusInWindow();
    }

    /**
     * Add new node in the frame attributes tree.
     */
    private void addNewFrameNode() {
        // do not add a node if it is wanted to be the child of the root node or if it is the default invalid attribute
        if (frameTreePanel.getSelection().equalsIgnoreCase(Constants.FRAME_ATTRIBUTES_TEXT)) {
            // the root was selected and it should not be permited   
            Toolkit.getDefaultToolkit().beep();
            return;
        }

        frameTreePanel.addObject(jTFFrameAttribName.getText());

        jTFFrameAttribName.setText("");
        jTFFrameAttribName.requestFocusInWindow();
    }

    /**
     * Add the tree panel to the view and configure other gui components.
     */
    private void configureObjAttribTree() {
        // set the position of the tree panel
        java.awt.GridBagConstraints gbc = new java.awt.GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = java.awt.GridBagConstraints.BOTH;
        gbc.weightx = 0.01;
        gbc.weighty = 0.01;

        jPObjAttribPreviewTree.add(objTreePanel, gbc);
        objTreePanel.addObserver(this);

        // set the title of the panel
        TitledBorder titledBorder = BorderFactory.createTitledBorder(Constants.OBJECT_ATTRIBUTES_TEXT);
        jPObjAttribButtons.setBorder(titledBorder);

        objTreePanel.selectRoot();

        objTreePanel.expandRows();
    }

    /**
     * Save the defined attributes.
     */
    private void saveAttributes() {
        // write the user attributes to file
        saveObjectAttributes();
        saveFrameAttributes();

        closeWindow();
    }

    /**
     * Saves into the object attributes file, the latest wishes of the user.
     */
    public void saveObjectAttributes() {
        try (FileOutputStream fout = new FileOutputStream(Constants.OBJECT_ATTRIBUTES_PATH);
                ObjectOutputStream oos = new ObjectOutputStream(fout)) {

            // write the user preferences object
            oos.writeObject(objTreePanel.getRootNode());

        } catch (IOException ex) {
            String msg = "Error while writing the object attributes to the file!";
            log.error(msg);
            log.debug("{} {}", msg, ex);
        }
    }

    /**
     * Saves into the object attributes file, the latest wishes of the user.
     */
    public void saveFrameAttributes() {
        try (BufferedWriter bwFrame = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(Constants.FRAME_ATTRIBUTES_PATH), Charset.forName("UTF-8")))) {
            bwFrame.write(getFrameAttributesString());
            bwFrame.flush();
        } catch (IOException ex) {
            String msg = "Error while writing the frame attributes to the file!";
            log.error(msg);
            log.debug("{} {}", msg, ex);
        }
    }

    /**
     * Set the object attributes from the application configuration.
     */
    private void loadObjectAttributes() {
        // create and add the components to the view
        populateObjTree();

        // display the tree in the proper place
        configureObjAttribTree();
    }

    /**
     * Add the tree panel to the view and configure other gui components.
     */
    private void configureFrameAttribTree() {
        // set the position of the tree panel
        java.awt.GridBagConstraints gbc = new java.awt.GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.fill = java.awt.GridBagConstraints.BOTH;
        gbc.weightx = 0.01;
        gbc.weighty = 0.01;

        jPFrameAttribPreviewTree.add(frameTreePanel, gbc);
        frameTreePanel.addObserver(this);

        // set the title of the panel
        TitledBorder titledBorder = BorderFactory.createTitledBorder(Constants.FRAME_ATTRIBUTES_TEXT);
        jPFrameAttribButtons.setBorder(titledBorder);

        frameTreePanel.selectRoot();

        frameTreePanel.expandRows();
    }

    /**
     * Remove the frame attributes but keep the basic structure.
     */
    private void clearFrameAttributes() {
        // remove all the nodes of the root
        frameTreePanel.clear();

        // add the basic structure of the tree
        populateFrameTree(false);

        // expand the frame
        frameTreePanel.expandRows();
    }

    /**
     * Request the focus of the application for the input text component of the
     * active tab.
     */
    private void requestFocusedComponent() {
        int index = jTPAttributes.getSelectedIndex();

        // set the focus to the needed component
        switch (jTPAttributes.getTitleAt(index)) {
            case TAB_FRAME:
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        jTFFrameAttribName.requestFocusInWindow();
                    }
                });
                break;

            case TAB_OBJECT:
                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        jTFObjAttribName.requestFocusInWindow();
                    }
                });
                break;

            default:
                jTFFrameAttribName.requestFocusInWindow();
                break;
        }
    }

    /**
     * Fill in the tree with predefined data, or load the existing data.
     */
    public void populateObjTree() {
        if (objectAttributes != null) {
            // if the object attributes is not null, load the existing data
            objTreePanel = new DynamicTree(objectAttributes, OBJ_ATTRIB_TREE_DEPTH);
        } else {
            // create a new predefined tree
            objectAttributes = new CustomTreeNode(Constants.OBJECT_ATTRIBUTES_TEXT);
            objTreePanel = new DynamicTree(objectAttributes, OBJ_ATTRIB_TREE_DEPTH);

            String p1Name = "Vehicles";
            String p2Name = "Traffic signs";
            String c11Name = "LKW";
            String c12Name = "PKW";
            String c21Name = "Prohibitory";
            String c22Name = "Speed limits";
            String c23Name = "Attention";

            CustomTreeNode p1 = objTreePanel.addObject(null, p1Name);
            CustomTreeNode p2 = objTreePanel.addObject(null, p2Name);

            objTreePanel.addObject(p1, c11Name);
            objTreePanel.addObject(p1, c12Name);

            objTreePanel.addObject(p2, c21Name);
            objTreePanel.addObject(p2, c22Name);
            objTreePanel.addObject(p2, c23Name);
        }
    }

    /**
     * Fill in the tree with predefined data, or load the existing data.
     *
     * @param addFrameAttrib true if the tree should be filled in with the
     * predefined frame attributes or false if just the structure of the tree
     * should be created
     */
    private void populateFrameTree(boolean addFrameAttrib) {
        if ((frameTreePanel == null) || (frameAttributes == null)) {
            frameAttributes = new CustomTreeNode(Constants.FRAME_ATTRIBUTES_TEXT);
            frameTreePanel = new DynamicTree(frameAttributes, FRAME_ATTRIB_TREE_DEPTH);
        }

        CustomTreeNode<String> illuminationNode = new CustomTreeNode<>(FRAME_ATTRIBUTE_NAMES[1]);
        CustomTreeNode<String> weatherNode = new CustomTreeNode<>(FRAME_ATTRIBUTE_NAMES[2]);
        CustomTreeNode<String> roadTypeNode = new CustomTreeNode<>(FRAME_ATTRIBUTE_NAMES[3]);
        CustomTreeNode<String> roadEventNode = new CustomTreeNode<>(FRAME_ATTRIBUTE_NAMES[4]);
        CustomTreeNode<String> countryNode = new CustomTreeNode<>(FRAME_ATTRIBUTE_NAMES[5]);

        frameAttributes.addChild(illuminationNode);
        frameAttributes.addChild(weatherNode);
        frameAttributes.addChild(roadTypeNode);
        frameAttributes.addChild(roadEventNode);
        frameAttributes.addChild(countryNode);

        // do not add the frame attributes if not wanted or if the data is invalid
        if ((frameAttribString == null) || (!addFrameAttrib)) {
            return;
        }

        // add the new attributes
        frameAttribString.getIlluminationList().stream()
                .forEach(illumination -> illuminationNode.addChild(illumination));

        frameAttribString.getWeatherList().stream()
                .forEach(weather -> weatherNode.addChild(weather));

        frameAttribString.getRoadTypeList().stream()
                .forEach(roadType -> roadTypeNode.addChild(roadType));

        frameAttribString.getRoadEventList().stream()
                .forEach(roadEvent -> roadEventNode.addChild(roadEvent));

        frameAttribString.getCountryList().stream()
                .forEach(country -> countryNode.addChild(country));
    }

    /**
     * Get the frame attributes in order to save them in a file.
     *
     * @return the string to be saved in the file
     */
    private String getFrameAttributesString() {
        StringBuilder frameAttrib = new StringBuilder();

        // put all the frame specific attributes
        frameAttrib.append("illumination=");
        frameAttrib.append(getFrameAttributes(frameTreePanel.getRootNode().getChild(FRAME_ATTRIBUTE_NAMES[1]).getChildren()));
        frameAttrib.append("weather=");
        frameAttrib.append(getFrameAttributes(frameTreePanel.getRootNode().getChild(FRAME_ATTRIBUTE_NAMES[2]).getChildren()));
        frameAttrib.append("road_type=");
        frameAttrib.append(getFrameAttributes(frameTreePanel.getRootNode().getChild(FRAME_ATTRIBUTE_NAMES[3]).getChildren()));
        frameAttrib.append("road_event=");
        frameAttrib.append(getFrameAttributes(frameTreePanel.getRootNode().getChild(FRAME_ATTRIBUTE_NAMES[4]).getChildren()));
        frameAttrib.append("country=");
        frameAttrib.append(getFrameAttributes(frameTreePanel.getRootNode().getChild(FRAME_ATTRIBUTE_NAMES[5]).getChildren()));

        return frameAttrib.toString();
    }

    /**
     * Build a string with the attributes of the frame, defined in the input
     * list.
     *
     * @param attributesList the list of attributes which has to be converted to
     * a string.
     * @return the string representing the frame attributes, separated by comma
     */
    private static String getFrameAttributes(List<Object> attributesList) {
        StringBuilder frameAttrib = new StringBuilder();
        for (Object attribute : attributesList) {
            frameAttrib.append(attribute);
            frameAttrib.append(",");
        }
        return frameAttrib.substring(0, frameAttrib.length() - 1) + "\r\n";
    }

    /**
     * Close the edit attributes window.
     */
    private void closeWindow() {
        // notify other components to reload attributes
        observable.notifyObservers(ObservedActions.Action.RELOAD_ATTRIBUTES);

        // close the frame
        dispose();
    }
} public class GUILabelingTool extends javax.swing.JFrame implements Observer {

    /**
     * The resolution of the current screen where the application is showed.
     */
    private transient ScreenResolution screenRes;

    /**
     * User preferences related to the application.
     */
    private transient UserPreferences userPrefs;

    /**
     * The file chooser used to open files.
     */
    private JFileChooser chooser;

    /**
     * Allows the user to activate different functionalities from the graphic
     * tablet.
     */
    private transient JPenFunctions penFunctions;

    /**
     * An instance of the gui controller, which is implementing and managing all
     * the functionalities of the application.
     */
    private transient GUIController gc;

    /**
     * The event dispatcher for the keyboard in order to execute specific tasks
     * for the gui window.
     */
    private transient GUIKeyEventDispatcher guiKeyEventDispatch;

    /**
     * The text to be displayed on the new object for scribbles.
     */
    private static final String NEW_OBJ_BUTTON_TEXT = "+";

    /**
     * The text to be displayed for saving a newly created scribble object.
     */
    private static final String SAVE_OBJ_BUTTON_TEXT = "S";

    /**
     * The text to be displayed on the play button.
     */
    private static final String PLAY_BUTTON_TEXT = "Play";

    /**
     * The text to be displayed on the pause button.
     */
    private static final String PAUSE_BUTTON_TEXT = "Pause";

    /**
     * The information regarding the frame currently displayed.
     */
    private transient FrameInfo currentFrame = new FrameInfo();

    /**
     * Shows if the application is in video mode, with all the videos coming
     * from the server, or in a different mode, like offline where the data is
     * loaded from and stored to, locally.
     */
    private static final transient Constants.AppConfigMode APP_CFG_MODE = Constants.AppConfigMode.PREMIUM_EDITION;

    /**
     * The default value of the DPI.
     */
    private int defaultDPIValue = 96;

    /**
     * If it is set to true, Pixie will run in online mode; it will connect to
     * Behemoth for requesting video files or sending labels.
     *
     * If it is set to false, Pixie will run in offline mode; as standalone
     * application
     */
    private boolean runningOnline;

    /**
     * logger instance
     */
    private final transient Logger log;

    /**
     * Creates new form GUILabelingTool
     *
     * @param log the logger where the actions and exceptions are logged
     */
    public GUILabelingTool(Logger log) {
        this.log = log;

        initComponents();
        initLogger();
        initGUI();
    }

    private void initGUI() {
        // set the app to start in maximized window mode
        this.setExtendedState(javax.swing.JFrame.MAXIMIZED_BOTH);

        // enable fields, set configurations
        configureComponents();

        // get resolution
        initResolutions();

        // warning for using smaller than HD resolution
        checkResolution();

        // reads the user preferences
        userPrefs = new UserPreferences();

        jCBPlayBackward.setSelected(userPrefs.isPlayBackward());
        jCBFlipVerticallyImg.setSelected(userPrefs.isFlipVertically());
        jCBMirrorImg.setSelected(userPrefs.isMirrorImage());
        jCBShowCurrentObj.setSelected(userPrefs.isShowJustCurrentObj());

        // initialise the properties of the file chooser
        initFileChooser();

        // init the listener for the pen for using the graphic tablet
        penFunctions = new JPenFunctions();
        // add observer to be notified when there are changes from the pen
        penFunctions.addObserver(this);

        // add keyboard listener to be able to set events on the wanted keys
        initKeyEventDispatch();

        initApplicationMode();

        chooseLoadFile();
    }

    /**
     * Enable/Disable fields; set icons and all the needed configurations of the
     * components.
     */
    private void configureComponents() {
        //disable the scribble objects; they will be enabled after loading an image
        jRBBoundingBox.setEnabled(false);
        jRBScribble.setEnabled(false);
        jRBEditMode.setEnabled(false);

        // do not show the objects info unles there are objects
        jPObjInfo.setVisible(false);

        // erase the text of the labels which will be writen later
        jLFrameNo.setText("");

        // show the icons of the menus
        setMenuItemsIcons();
    }

    /**
     * Initialize the logger, in order to display all the log messages in a text
     * logger on the gui.
     */
    private void initLogger() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

        // find the appender which is an instance of the text area appender and set the text area where it should put the text
        for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
            // iterate the appenders in order to find the wanted one
            for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext();) {
                Appender<ILoggingEvent> appender = index.next();
                // just the TextAreaAppender type is needed to be correctly initialised
                if (appender instanceof TextAreaAppender) {
                    ((TextAreaAppender) appender).setTextArea(jTALog, jTAShortLog);
                }
            }
        }
    }

    /**
     * Based on the user preferences, init the application in online/offline
     * mode. Load all the mode specific data: attributes etc.
     */
    private void initApplicationMode() {
        // init the current frame info object
        initCurrentFrameInfo();

        // instantiate the gui controller offline
        gc = new GUIController(userPrefs, currentFrame);

        // load the predefined data on the comboboxes
        loadCBdata();

        // init the screen resolution in the controller
        gc.setAvailableDrawSize(computeDrawingSize(getAvailableScreenSize(userPrefs.getPreferredDPI())));

        gc.addObserver(this);
    }

    /**
     * Defines events for the wanted keys, dependent by the labeling type.
     */
    private void initKeyEventDispatch() {
        guiKeyEventDispatch = new GUIKeyEventDispatcher();
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(guiKeyEventDispatch);
    }

    /**
     * Initialize the screen resolution.
     */
    private void initResolutions() {
        screenRes = new ScreenResolution(this);

        defaultDPIValue = Toolkit.getDefaultToolkit().getScreenResolution();
    }

    /**
     * Check if the used resolution is better than the advised one. If not, warn
     * the user.
     */
    private void checkResolution() {
        Dimension dim = screenRes.getScreenResolution();
        if (dim.width < 1920 || dim.height < 1080) {
            String title = "Resolution";
            String message = "The current resolution of your screen is not recomanded for labeling purposes!\nPlease use a resolution of minimum 1920x1080!!";
            Messages.showWarningMessage(this, message, title);
            log.info(message);
        }
    }

    /**
     * Initialise some important features of the file chooser used to load the
     * wanted files.
     */
    private void initFileChooser() {
        chooser = new JFileChooser();
        chooser.setCurrentDirectory(new File(userPrefs.getLastDirectory()));
        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

        LTFileFilter filter = new LTFileFilter();

        // add the list of extensions to the filter
        Constants.IMG_EXTENSION_LIST.stream().forEach(extension -> filter.addExtension(extension));

        filter.setDescription("Image and Video Files");
        chooser.setFileFilter(filter);
    }

    /**
     * Initialize the current frame info with the application defaults.
     */
    private void initCurrentFrameInfo() {
        currentFrame = new FrameInfo(jCBIllumination.getItemAt(jCBIllumination.getSelectedIndex()),
                jCBWeather.getItemAt(jCBWeather.getSelectedIndex()),
                jCBRoadType.getItemAt(jCBRoadType.getSelectedIndex()),
                jCBRoadEvent.getItemAt(jCBRoadEvent.getSelectedIndex()),
                jCBCountry.getItemAt(jCBCountry.getSelectedIndex()),
                jCBWipersVisible.isSelected(),
                jCBDirtVisible.isSelected(),
                jCBImageDistorted.isSelected(),
                userPrefs.isSaveFrameObjMap());
    }

    /**
     * Get the predefined data for the combo boxes from the configuration and
     * display it.
     */
    private void loadCBdata() {
        LAttributesFrame frameAttributes = gc.getFrameAttributes();

        if (frameAttributes == null) {
            return;
        } else {
            // remove existing attributes
            removeExistingAttributes();

            // add the new attributes
            frameAttributes.getIlluminationList().stream()
                    .forEach(illumination -> jCBIllumination.addItem(Utils.capitalize(illumination)));

            frameAttributes.getWeatherList().stream()
                    .forEach(weather -> jCBWeather.addItem(Utils.capitalize(weather)));

            frameAttributes.getRoadTypeList().stream()
                    .forEach(roadType -> jCBRoadType.addItem(Utils.capitalize(roadType)));

            frameAttributes.getRoadEventList().stream()
                    .forEach(roadEvent -> jCBRoadEvent.addItem(Utils.capitalize(roadEvent)));

            frameAttributes.getCountryList().stream()
                    .forEach(country -> jCBCountry.addItem(Utils.capitalize(country)));
        }
    }

    /**
     * Remove the existing attributes of the frame.
     */
    private void removeExistingAttributes() {
        // remove all items
        jCBIllumination.removeAllItems();
        jCBWeather.removeAllItems();
        jCBRoadType.removeAllItems();
        jCBRoadEvent.removeAllItems();
        jCBCountry.removeAllItems();

        if (userPrefs.isCheckFrameAnnotations()) {
            // fill in with the invalid data
            jCBIllumination.addItem(Utils.capitalize(Constants.INVALID_ATTRIBUTE_TEXT));
            jCBWeather.addItem(Utils.capitalize(Constants.INVALID_ATTRIBUTE_TEXT));
            jCBRoadType.addItem(Utils.capitalize(Constants.INVALID_ATTRIBUTE_TEXT));
            jCBRoadEvent.addItem(Utils.capitalize(Constants.INVALID_ATTRIBUTE_TEXT));
            jCBCountry.addItem(Utils.capitalize(Constants.INVALID_ATTRIBUTE_TEXT));
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jBGLabelType = new javax.swing.ButtonGroup();
        jPBackground = new javax.swing.JPanel();
        jTPLabelingOptions = new javax.swing.JTabbedPane();
        jPLabeling = new javax.swing.JPanel();
        jPView = new javax.swing.JPanel();
        jPViewResults = new javax.swing.JPanel();
        jPDevelopment = new javax.swing.JPanel();
        jLJumpFrame = new javax.swing.JLabel();
        jTFJumpFrame = new javax.swing.JTextField();
        jBJumpFrame = new javax.swing.JButton();
        jLNoFrames = new javax.swing.JLabel();
        jSPShortLog = new javax.swing.JScrollPane();
        jTAShortLog = new javax.swing.JTextArea();
        jPViewResultsImg = new javax.swing.JPanel();
        jPPrevResultImg = new javax.swing.JPanel();
        jPImgOpt = new javax.swing.JPanel();
        jPVLabelType = new javax.swing.JPanel();
        jRBScribble = new javax.swing.JRadioButton();
        jRBBoundingBox = new javax.swing.JRadioButton();
        jBNewScribbleObj = new javax.swing.JButton();
        jRBEditMode = new javax.swing.JRadioButton();
        jRBPolygon = new javax.swing.JRadioButton();
        jBNewPolygonObj = new javax.swing.JButton();
        jSPObjList = new javax.swing.JScrollPane();
        jPObjList = new javax.swing.JPanel();
        jLAlignNorth = new javax.swing.JLabel();
        jPObjInfo = new javax.swing.JPanel();
        jLTotalNoObjs = new javax.swing.JLabel();
        jLNoBBoxObjs = new javax.swing.JLabel();
        jLNoScribbleObjs = new javax.swing.JLabel();
        jLTotalNoObjsText = new javax.swing.JLabel();
        jLNoBBoxObjsText = new javax.swing.JLabel();
        jLNoScribbleObjsText = new javax.swing.JLabel();
        jLNoPolygonObjsText = new javax.swing.JLabel();
        jLNoPolygonObjs = new javax.swing.JLabel();
        jPVideoPrev = new javax.swing.JPanel();
        jPVImgToLabel = new javax.swing.JPanel();
        jPNavigation = new javax.swing.JPanel();
        jBPrevFrame = new javax.swing.JButton();
        jBNextFrame = new javax.swing.JButton();
        jLFrameNo = new javax.swing.JLabel();
        jSVideoPlayer = new javax.swing.JSlider();
        jBPlay = new javax.swing.JButton();
        jCBPlayBackward = new javax.swing.JCheckBox();
        jCBFlipVerticallyImg = new javax.swing.JCheckBox();
        jCBMirrorImg = new javax.swing.JCheckBox();
        jPOptions = new javax.swing.JPanel();
        jPSceneDescription = new javax.swing.JPanel();
        jLIllumination = new javax.swing.JLabel();
        jCBIllumination = new javax.swing.JComboBox<>();
        jLWeather = new javax.swing.JLabel();
        jLRoadType = new javax.swing.JLabel();
        jLRoadEvent = new javax.swing.JLabel();
        jCBWeather = new javax.swing.JComboBox<>();
        jCBRoadType = new javax.swing.JComboBox<>();
        jCBRoadEvent = new javax.swing.JComboBox<>();
        jLCountry = new javax.swing.JLabel();
        jCBCountry = new javax.swing.JComboBox<>();
        jCBWipersVisible = new javax.swing.JCheckBox();
        jCBDirtVisible = new javax.swing.JCheckBox();
        jCBImageDistorted = new javax.swing.JCheckBox();
        jPLoadSource = new javax.swing.JPanel();
        jBLoadFile = new javax.swing.JButton();
        jPCropOpt = new javax.swing.JPanel();
        jCBShowCurrentObj = new javax.swing.JCheckBox();
        jPLog = new javax.swing.JPanel();
        jSPLog = new javax.swing.JScrollPane();
        jTALog = new javax.swing.JTextArea();
        jMBMenu = new javax.swing.JMenuBar();
        jMFile = new javax.swing.JMenu();
        jMILoadFile = new javax.swing.JMenuItem();
        jMIExit = new javax.swing.JMenuItem();
        jMEditObject = new javax.swing.JMenu();
        jMIMoveLeft = new javax.swing.JMenuItem();
        jMIMoveRight = new javax.swing.JMenuItem();
        jMIMoveUp = new javax.swing.JMenuItem();
        jMIMoveDown = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        jMIDecreaseLeft = new javax.swing.JMenuItem();
        jMIDecreaseRight = new javax.swing.JMenuItem();
        jMIDecreaseTop = new javax.swing.JMenuItem();
        jMIDecreaseBottom = new javax.swing.JMenuItem();
        jSeparator3 = new javax.swing.JPopupMenu.Separator();
        jMIIncreaseLeft = new javax.swing.JMenuItem();
        jMIIncreaseRight = new javax.swing.JMenuItem();
        jMIIncreaseTop = new javax.swing.JMenuItem();
        jMIIncreaseBottom = new javax.swing.JMenuItem();
        jSeparator2 = new javax.swing.JPopupMenu.Separator();
        jMIDecreaseBox = new javax.swing.JMenuItem();
        jMIIncreaseBox = new javax.swing.JMenuItem();
        jSeparator4 = new javax.swing.JPopupMenu.Separator();
        jMICancelObject = new javax.swing.JMenuItem();
        jMIDeleteObject = new javax.swing.JMenuItem();
        jMNavigation = new javax.swing.JMenu();
        jMINextFrame = new javax.swing.JMenuItem();
        jMIPrevFrame = new javax.swing.JMenuItem();
        jMOptions = new javax.swing.JMenu();
        jMIUserConfig = new javax.swing.JMenuItem();
        jSAttribDef = new javax.swing.JPopupMenu.Separator();
        jMIAttributes = new javax.swing.JMenuItem();
        jSeparator7 = new javax.swing.JPopupMenu.Separator();
        jMILanguage = new javax.swing.JMenuItem();
        jMExport = new javax.swing.JMenu();
        jMIExportOrigImg = new javax.swing.JMenuItem();
        jMIExportWorkingImg = new javax.swing.JMenuItem();
        jMIExportSemanticImg = new javax.swing.JMenuItem();
        jMIExportJoinedImg = new javax.swing.JMenuItem();
        jSeparator5 = new javax.swing.JPopupMenu.Separator();
        jMIExportConfig = new javax.swing.JMenuItem();
        jMHelp = new javax.swing.JMenu();
        jMIAbout = new javax.swing.JMenuItem();
        jMIHotkeys = new javax.swing.JMenuItem();
        jMIManualApp = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("Pixie");
        setBackground(new java.awt.Color(255, 255, 255));
        setName("LabelingApplication"); // NOI18N
        addWindowStateListener(new java.awt.event.WindowStateListener() {
            public void windowStateChanged(java.awt.event.WindowEvent evt) {
                formWindowStateChanged(evt);
            }
        });
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });
        getContentPane().setLayout(new java.awt.GridBagLayout());

        jPBackground.setBackground(new java.awt.Color(255, 255, 255));
        jPBackground.setLayout(new java.awt.GridBagLayout());

        jTPLabelingOptions.setBackground(new java.awt.Color(255, 255, 255));
        jTPLabelingOptions.setToolTipText("");
        jTPLabelingOptions.setFocusable(false);

        jPLabeling.setBackground(new java.awt.Color(255, 255, 255));
        jPLabeling.setToolTipText("Create bounding box label.");
        jPLabeling.setLayout(new java.awt.GridBagLayout());

        jPView.setBackground(new java.awt.Color(255, 255, 255));
        jPView.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPView.setLayout(new java.awt.GridBagLayout());

        jPViewResults.setBackground(new java.awt.Color(255, 255, 255));
        jPViewResults.setLayout(new java.awt.GridBagLayout());

        jPDevelopment.setLayout(new java.awt.GridBagLayout());

        jLJumpFrame.setText("Frame");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPDevelopment.add(jLJumpFrame, gridBagConstraints);

        jTFJumpFrame.setText("100");
        jTFJumpFrame.setToolTipText("");
        jTFJumpFrame.setPreferredSize(new java.awt.Dimension(50, 22));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPDevelopment.add(jTFJumpFrame, gridBagConstraints);

        jBJumpFrame.setText("Jump");
        jBJumpFrame.setToolTipText("Jump to the specified number");
        jBJumpFrame.setEnabled(false);
        jBJumpFrame.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBJumpFrameActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPDevelopment.add(jBJumpFrame, gridBagConstraints);

        jLNoFrames.setText("No Frames");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPDevelopment.add(jLNoFrames, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPViewResults.add(jPDevelopment, gridBagConstraints);

        jSPShortLog.setAutoscrolls(true);
        jSPShortLog.setMaximumSize(new java.awt.Dimension(400, 96));
        jSPShortLog.setMinimumSize(new java.awt.Dimension(400, 96));
        jSPShortLog.setOpaque(false);
        jSPShortLog.setPreferredSize(new java.awt.Dimension(400, 100));

        jTAShortLog.setEditable(false);
        jTAShortLog.setColumns(20);
        jTAShortLog.setLineWrap(true);
        jTAShortLog.setRows(5);
        jTAShortLog.setWrapStyleWord(true);
        jSPShortLog.setViewportView(jTAShortLog);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.5;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPViewResults.add(jSPShortLog, gridBagConstraints);

        jPViewResultsImg.setBackground(new java.awt.Color(255, 255, 255));
        jPViewResultsImg.setLayout(new java.awt.GridBagLayout());

        jPPrevResultImg.setBackground(new java.awt.Color(255, 255, 255));
        jPPrevResultImg.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPPrevResultImg.setPreferredSize(new java.awt.Dimension(400, 300));
        jPPrevResultImg.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jPPrevResultImgMouseClicked(evt);
            }
        });
        jPPrevResultImg.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 0, 0));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPViewResultsImg.add(jPPrevResultImg, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPViewResults.add(jPViewResultsImg, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPView.add(jPViewResults, gridBagConstraints);

        jPImgOpt.setBackground(new java.awt.Color(255, 255, 255));
        jPImgOpt.setLayout(new java.awt.GridBagLayout());

        jPVLabelType.setBackground(new java.awt.Color(255, 255, 255));
        jPVLabelType.setBorder(javax.swing.BorderFactory.createTitledBorder("Label Type"));
        jPVLabelType.setLayout(new java.awt.GridBagLayout());

        jRBScribble.setBackground(new java.awt.Color(255, 255, 255));
        jBGLabelType.add(jRBScribble);
        jRBScribble.setText("Scribble");
        jRBScribble.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBScribbleActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPVLabelType.add(jRBScribble, gridBagConstraints);

        jRBBoundingBox.setBackground(new java.awt.Color(255, 255, 255));
        jBGLabelType.add(jRBBoundingBox);
        jRBBoundingBox.setSelected(true);
        jRBBoundingBox.setText("Bounding Box");
        jRBBoundingBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBBoundingBoxActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPVLabelType.add(jRBBoundingBox, gridBagConstraints);

        jBNewScribbleObj.setBackground(new java.awt.Color(255, 255, 255));
        jBNewScribbleObj.setText("+");
        jBNewScribbleObj.setEnabled(false);
        jBNewScribbleObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBNewScribbleObjActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPVLabelType.add(jBNewScribbleObj, gridBagConstraints);
        jBNewScribbleObj.getAccessibleContext().setAccessibleName("NewObject");

        jRBEditMode.setBackground(new java.awt.Color(255, 255, 255));
        jBGLabelType.add(jRBEditMode);
        jRBEditMode.setText("Edit");
        jRBEditMode.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBEditModeActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 7;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPVLabelType.add(jRBEditMode, gridBagConstraints);

        jRBPolygon.setBackground(new java.awt.Color(255, 255, 255));
        jBGLabelType.add(jRBPolygon);
        jRBPolygon.setText("Polygon");
        jRBPolygon.setEnabled(false);
        jRBPolygon.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jRBPolygonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPVLabelType.add(jRBPolygon, gridBagConstraints);

        jBNewPolygonObj.setBackground(new java.awt.Color(255, 255, 255));
        jBNewPolygonObj.setText("+");
        jBNewPolygonObj.setEnabled(false);
        jBNewPolygonObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBNewPolygonObjActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 5;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPVLabelType.add(jBNewPolygonObj, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        jPImgOpt.add(jPVLabelType, gridBagConstraints);

        jSPObjList.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        jSPObjList.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        jSPObjList.setAutoscrolls(true);
        jSPObjList.setPreferredSize(new java.awt.Dimension(130, 100));

        jPObjList.setBackground(new java.awt.Color(255, 255, 255));
        jPObjList.setBorder(javax.swing.BorderFactory.createTitledBorder("Objects List"));
        jPObjList.setAutoscrolls(true);
        jPObjList.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 100;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weighty = 0.5;
        jPObjList.add(jLAlignNorth, gridBagConstraints);

        jSPObjList.setViewportView(jPObjList);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
        jPImgOpt.add(jSPObjList, gridBagConstraints);

        jPObjInfo.setBackground(new java.awt.Color(255, 255, 255));
        jPObjInfo.setBorder(javax.swing.BorderFactory.createTitledBorder("Objects Info"));
        jPObjInfo.setLayout(new java.awt.GridBagLayout());

        jLTotalNoObjs.setText("0");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 5, 2, 2);
        jPObjInfo.add(jLTotalNoObjs, gridBagConstraints);

        jLNoBBoxObjs.setText("0");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.weighty = 0.05;
        gridBagConstraints.insets = new java.awt.Insets(2, 5, 2, 2);
        jPObjInfo.add(jLNoBBoxObjs, gridBagConstraints);

        jLNoScribbleObjs.setText("0");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.weighty = 0.05;
        gridBagConstraints.insets = new java.awt.Insets(2, 5, 2, 2);
        jPObjInfo.add(jLNoScribbleObjs, gridBagConstraints);

        jLTotalNoObjsText.setText("Total Objects");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjInfo.add(jLTotalNoObjsText, gridBagConstraints);

        jLNoBBoxObjsText.setText("Box Obj");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjInfo.add(jLNoBBoxObjsText, gridBagConstraints);

        jLNoScribbleObjsText.setText("Scribble Obj");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjInfo.add(jLNoScribbleObjsText, gridBagConstraints);

        jLNoPolygonObjsText.setText("Polygon Obj");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPObjInfo.add(jLNoPolygonObjsText, gridBagConstraints);

        jLNoPolygonObjs.setText("0");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.weighty = 0.05;
        gridBagConstraints.insets = new java.awt.Insets(2, 5, 2, 2);
        jPObjInfo.add(jLNoPolygonObjs, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 5, 0);
        jPImgOpt.add(jPObjInfo, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 0, 0);
        jPView.add(jPImgOpt, gridBagConstraints);

        jPVideoPrev.setBackground(new java.awt.Color(255, 255, 255));
        jPVideoPrev.setLayout(new java.awt.GridBagLayout());

        jPVImgToLabel.setBackground(new java.awt.Color(255, 255, 255));
        jPVImgToLabel.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        jPVImgToLabel.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        jPVideoPrev.add(jPVImgToLabel, gridBagConstraints);

        jPNavigation.setBackground(new java.awt.Color(255, 255, 255));
        jPNavigation.setBorder(javax.swing.BorderFactory.createTitledBorder("Navigation"));
        jPNavigation.setLayout(new java.awt.GridBagLayout());

        jBPrevFrame.setIcon(Icons.PREVIOUS_ICON);
        jBPrevFrame.setEnabled(false);
        jBPrevFrame.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBPrevFrameActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPNavigation.add(jBPrevFrame, gridBagConstraints);

        jBNextFrame.setIcon(Icons.NEXT_ICON);
        jBNextFrame.setEnabled(false);
        jBNextFrame.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBNextFrameActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPNavigation.add(jBNextFrame, gridBagConstraints);

        jLFrameNo.setText("F: 000001");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
        jPNavigation.add(jLFrameNo, gridBagConstraints);

        jSVideoPlayer.setBackground(new java.awt.Color(255, 255, 255));
        jSVideoPlayer.setValue(0);
        jSVideoPlayer.setEnabled(false);
        jSVideoPlayer.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                jSVideoPlayerMouseReleased(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 8;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        jPNavigation.add(jSVideoPlayer, gridBagConstraints);

        jBPlay.setIcon(Icons.PLAY_ICON);
        jBPlay.setEnabled(false);
        jBPlay.setName("Play"); // NOI18N
        jBPlay.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBPlayActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPNavigation.add(jBPlay, gridBagConstraints);

        jCBPlayBackward.setBackground(new java.awt.Color(255, 255, 255));
        jCBPlayBackward.setText("Play Backward");
        jCBPlayBackward.setEnabled(false);
        jCBPlayBackward.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBPlayBackwardActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5);
        jPNavigation.add(jCBPlayBackward, gridBagConstraints);

        jCBFlipVerticallyImg.setBackground(new java.awt.Color(255, 255, 255));
        jCBFlipVerticallyImg.setText("Flip Vertically");
        jCBFlipVerticallyImg.setEnabled(false);
        jCBFlipVerticallyImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBFlipVerticallyImgActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.weightx = 0.02;
        jPNavigation.add(jCBFlipVerticallyImg, gridBagConstraints);

        jCBMirrorImg.setBackground(new java.awt.Color(255, 255, 255));
        jCBMirrorImg.setText("Mirror");
        jCBMirrorImg.setEnabled(false);
        jCBMirrorImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBMirrorImgActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPNavigation.add(jCBMirrorImg, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 0, 0);
        jPVideoPrev.add(jPNavigation, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.1;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(10, 5, 5, 0);
        jPView.add(jPVideoPrev, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPLabeling.add(jPView, gridBagConstraints);

        jPOptions.setBackground(new java.awt.Color(255, 255, 255));
        jPOptions.setLayout(new java.awt.GridBagLayout());

        jPSceneDescription.setBackground(new java.awt.Color(255, 255, 255));
        jPSceneDescription.setBorder(javax.swing.BorderFactory.createTitledBorder("Scene Description"));
        jPSceneDescription.setLayout(new java.awt.GridBagLayout());

        jLIllumination.setText("Illumination");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 5);
        jPSceneDescription.add(jLIllumination, gridBagConstraints);

        jCBIllumination.setPreferredSize(new java.awt.Dimension(100, 26));
        jCBIllumination.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBIlluminationActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 10);
        jPSceneDescription.add(jCBIllumination, gridBagConstraints);

        jLWeather.setText("Weather");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 5);
        jPSceneDescription.add(jLWeather, gridBagConstraints);

        jLRoadType.setText("Road Type");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 4;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 5);
        jPSceneDescription.add(jLRoadType, gridBagConstraints);

        jLRoadEvent.setText("Road Event");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 6;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 5);
        jPSceneDescription.add(jLRoadEvent, gridBagConstraints);

        jCBWeather.setPreferredSize(new java.awt.Dimension(100, 26));
        jCBWeather.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBWeatherActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 10);
        jPSceneDescription.add(jCBWeather, gridBagConstraints);

        jCBRoadType.setPreferredSize(new java.awt.Dimension(100, 26));
        jCBRoadType.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBRoadTypeActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 5;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 10);
        jPSceneDescription.add(jCBRoadType, gridBagConstraints);

        jCBRoadEvent.setPreferredSize(new java.awt.Dimension(100, 26));
        jCBRoadEvent.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBRoadEventActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 7;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 10);
        jPSceneDescription.add(jCBRoadEvent, gridBagConstraints);

        jLCountry.setBackground(new java.awt.Color(255, 255, 255));
        jLCountry.setLabelFor(jCBCountry);
        jLCountry.setText("Country");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 8;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 5);
        jPSceneDescription.add(jLCountry, gridBagConstraints);

        jCBCountry.setMinimumSize(new java.awt.Dimension(140, 32));
        jCBCountry.setPreferredSize(new java.awt.Dimension(100, 26));
        jCBCountry.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBCountryActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 9;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 10);
        jPSceneDescription.add(jCBCountry, gridBagConstraints);

        jCBWipersVisible.setBackground(new java.awt.Color(255, 255, 255));
        jCBWipersVisible.setText("wipers");
        jCBWipersVisible.setToolTipText("The wipers are visible in the image");
        jCBWipersVisible.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBWipersVisibleActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 10;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPSceneDescription.add(jCBWipersVisible, gridBagConstraints);

        jCBDirtVisible.setBackground(new java.awt.Color(255, 255, 255));
        jCBDirtVisible.setText("dirt");
        jCBDirtVisible.setToolTipText("There is dirt in the image");
        jCBDirtVisible.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBDirtVisibleActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 11;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPSceneDescription.add(jCBDirtVisible, gridBagConstraints);

        jCBImageDistorted.setBackground(new java.awt.Color(255, 255, 255));
        jCBImageDistorted.setText("distortion");
        jCBImageDistorted.setToolTipText("The image is distorted");
        jCBImageDistorted.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBImageDistortedActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 12;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPSceneDescription.add(jCBImageDistorted, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 3, 3, 3);
        jPOptions.add(jPSceneDescription, gridBagConstraints);

        jPLoadSource.setBackground(new java.awt.Color(255, 255, 255));
        jPLoadSource.setBorder(javax.swing.BorderFactory.createTitledBorder("Load"));
        jPLoadSource.setLayout(new java.awt.GridBagLayout());

        jBLoadFile.setBackground(new java.awt.Color(255, 255, 255));
        jBLoadFile.setIcon(Icons.LOAD_FILE_ICON);
        jBLoadFile.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jBLoadFileActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPLoadSource.add(jBLoadFile, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new java.awt.Insets(5, 3, 3, 3);
        jPOptions.add(jPLoadSource, gridBagConstraints);

        jPCropOpt.setBackground(new java.awt.Color(255, 255, 255));
        jPCropOpt.setBorder(javax.swing.BorderFactory.createTitledBorder("Object Options"));
        jPCropOpt.setLayout(new java.awt.GridBagLayout());

        jCBShowCurrentObj.setBackground(new java.awt.Color(255, 255, 255));
        jCBShowCurrentObj.setText("Show Current Obj");
        jCBShowCurrentObj.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jCBShowCurrentObjActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
        jPCropOpt.add(jCBShowCurrentObj, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.insets = new java.awt.Insets(5, 3, 3, 3);
        jPOptions.add(jPCropOpt, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPLabeling.add(jPOptions, gridBagConstraints);

        jTPLabelingOptions.addTab("Labeling", jPLabeling);

        jPLog.setBackground(new java.awt.Color(255, 255, 255));
        jPLog.setLayout(new java.awt.GridBagLayout());

        jTALog.setEditable(false);
        jTALog.setColumns(20);
        jTALog.setRows(5);
        jTALog.setToolTipText("Contains the recording of all actions.");
        jTALog.setWrapStyleWord(true);
        jSPLog.setViewportView(jTALog);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
        jPLog.add(jSPLog, gridBagConstraints);

        jTPLabelingOptions.addTab("Log", jPLog);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.01;
        gridBagConstraints.weighty = 0.01;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        jPBackground.add(jTPLabelingOptions, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.05;
        gridBagConstraints.weighty = 0.05;
        getContentPane().add(jPBackground, gridBagConstraints);

        jMFile.setText("File");

        jMILoadFile.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F, java.awt.event.InputEvent.CTRL_MASK));
        jMILoadFile.setText("Load File");
        jMILoadFile.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMILoadFileActionPerformed(evt);
            }
        });
        jMFile.add(jMILoadFile);

        jMIExit.setText("Exit");
        jMIExit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExitActionPerformed(evt);
            }
        });
        jMFile.add(jMIExit);

        jMBMenu.add(jMFile);

        jMEditObject.setText("Edit");
        jMEditObject.setEnabled(false);

        jMIMoveLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, 0));
        jMIMoveLeft.setText("Move Left");
        jMIMoveLeft.setToolTipText("Move the whole object, one pixel, left");
        jMIMoveLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveLeft);

        jMIMoveRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, 0));
        jMIMoveRight.setText("Move Right");
        jMIMoveRight.setToolTipText("Move the whole object, one pixel, right");
        jMIMoveRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveRight);

        jMIMoveUp.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, 0));
        jMIMoveUp.setText("Move Up");
        jMIMoveUp.setToolTipText("Move the whole object, one pixel, up");
        jMIMoveUp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveUpActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveUp);

        jMIMoveDown.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, 0));
        jMIMoveDown.setText("Move Down");
        jMIMoveDown.setToolTipText("Move the whole object, one pixel, down");
        jMIMoveDown.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIMoveDownActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIMoveDown);
        jMEditObject.add(jSeparator1);

        jMIDecreaseLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseLeft.setText("Decrease Left");
        jMIDecreaseLeft.setToolTipText("Remove one pixel from the left side of the object");
        jMIDecreaseLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseLeft);

        jMIDecreaseRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseRight.setText("Decrease Right");
        jMIDecreaseRight.setToolTipText("Remove one pixel from the right side of the object");
        jMIDecreaseRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseRight);

        jMIDecreaseTop.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseTop.setText("Decrease Top");
        jMIDecreaseTop.setToolTipText("Remove one pixel from the top side of the object");
        jMIDecreaseTop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseTopActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseTop);

        jMIDecreaseBottom.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        jMIDecreaseBottom.setText("Decrease Bottom");
        jMIDecreaseBottom.setToolTipText("Remove one pixel from the bottom side of the object");
        jMIDecreaseBottom.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseBottomActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseBottom);
        jMEditObject.add(jSeparator3);

        jMIIncreaseLeft.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_A, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseLeft.setText("Increase Left");
        jMIIncreaseLeft.setToolTipText("Add a pixel on the left side of the object");
        jMIIncreaseLeft.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseLeftActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseLeft);

        jMIIncreaseRight.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_D, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseRight.setText("Increase Right");
        jMIIncreaseRight.setToolTipText("Add a pixel on the right side of the object");
        jMIIncreaseRight.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseRightActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseRight);

        jMIIncreaseTop.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_W, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseTop.setText("Increase Top");
        jMIIncreaseTop.setToolTipText("Add a pixel on the top side of the object");
        jMIIncreaseTop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseTopActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseTop);

        jMIIncreaseBottom.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseBottom.setText("Increase Bottom");
        jMIIncreaseBottom.setToolTipText("Add a pixel on the bottom side of the object");
        jMIIncreaseBottom.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseBottomActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseBottom);
        jMEditObject.add(jSeparator2);

        jMIDecreaseBox.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, java.awt.event.InputEvent.ALT_MASK));
        jMIDecreaseBox.setText("Decrease Box");
        jMIDecreaseBox.setToolTipText("Remove one pixel from all sides of the object");
        jMIDecreaseBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDecreaseBoxActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDecreaseBox);

        jMIIncreaseBox.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, java.awt.event.InputEvent.ALT_MASK));
        jMIIncreaseBox.setText("Increase Box");
        jMIIncreaseBox.setToolTipText("Add one pixel to all sides of the object");
        jMIIncreaseBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIIncreaseBoxActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIIncreaseBox);
        jMEditObject.add(jSeparator4);

        jMICancelObject.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0));
        jMICancelObject.setText("Cancel Object");
        jMICancelObject.setToolTipText("Cancel the object in progress / Remove the selection for an existing object");
        jMICancelObject.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMICancelObjectActionPerformed(evt);
            }
        });
        jMEditObject.add(jMICancelObject);

        jMIDeleteObject.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DELETE, 0));
        jMIDeleteObject.setText("Delete Object");
        jMIDeleteObject.setToolTipText("Delete the selected object");
        jMIDeleteObject.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIDeleteObjectActionPerformed(evt);
            }
        });
        jMEditObject.add(jMIDeleteObject);

        jMBMenu.add(jMEditObject);

        jMNavigation.setText("Navigation");
        jMNavigation.setEnabled(false);

        jMINextFrame.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_RIGHT, 0));
        jMINextFrame.setText("Next Frame/Image");
        jMINextFrame.setToolTipText("Save data and go to the next frame/image");
        jMINextFrame.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMINextFrameActionPerformed(evt);
            }
        });
        jMNavigation.add(jMINextFrame);

        jMIPrevFrame.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_LEFT, 0));
        jMIPrevFrame.setText("Previous Frame/Image");
        jMIPrevFrame.setToolTipText("Save data and go to the previous frame/image");
        jMIPrevFrame.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIPrevFrameActionPerformed(evt);
            }
        });
        jMNavigation.add(jMIPrevFrame);

        jMBMenu.add(jMNavigation);

        jMOptions.setText("Options");

        jMIUserConfig.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, java.awt.event.InputEvent.CTRL_MASK));
        jMIUserConfig.setText("Configuration");
        jMIUserConfig.setEnabled(false);
        jMIUserConfig.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIUserConfigActionPerformed(evt);
            }
        });
        jMOptions.add(jMIUserConfig);
        jMOptions.add(jSAttribDef);

        jMIAttributes.setText("Attributes Definition");
        jMIAttributes.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIAttributesActionPerformed(evt);
            }
        });
        jMOptions.add(jMIAttributes);
        jMOptions.add(jSeparator7);

        jMILanguage.setText("Language");
        jMILanguage.setEnabled(false);
        jMOptions.add(jMILanguage);

        jMBMenu.add(jMOptions);

        jMExport.setText("Exports");
        jMExport.setEnabled(false);

        jMIExportOrigImg.setText(Constants.ORIGINAL_IMG);
        jMIExportOrigImg.setToolTipText("Exports the clear, original, image");
        jMIExportOrigImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExportOrigImgActionPerformed(evt);
            }
        });
        jMExport.add(jMIExportOrigImg);

        jMIExportWorkingImg.setText(Constants.SEGMENTED_IMG);
        jMIExportWorkingImg.setToolTipText("Exports the image with the segmentation done");
        jMIExportWorkingImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExportWorkingImgActionPerformed(evt);
            }
        });
        jMExport.add(jMIExportWorkingImg);

        jMIExportSemanticImg.setText(Constants.SEMANTIC_SEGMENTATION_IMG);
        jMIExportSemanticImg.setToolTipText("Exports the results image (side panel)");
        jMIExportSemanticImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExportSemanticImgActionPerformed(evt);
            }
        });
        jMExport.add(jMIExportSemanticImg);

        jMIExportJoinedImg.setText(Constants.JOINED_IMGS);
        jMIExportJoinedImg.setToolTipText("Exports the original image and the result one, together");
        jMIExportJoinedImg.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExportJoinedImgActionPerformed(evt);
            }
        });
        jMExport.add(jMIExportJoinedImg);
        jMExport.add(jSeparator5);

        jMIExportConfig.setText("Export Configuration");
        jMIExportConfig.setToolTipText("Go to the export configurations");
        jMIExportConfig.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIExportConfigActionPerformed(evt);
            }
        });
        jMExport.add(jMIExportConfig);

        jMBMenu.add(jMExport);

        jMHelp.setText("Help");

        jMIAbout.setText("About");
        jMIAbout.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIAboutActionPerformed(evt);
            }
        });
        jMHelp.add(jMIAbout);

        jMIHotkeys.setText("Hotkeys");
        jMIHotkeys.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIHotkeysActionPerformed(evt);
            }
        });
        jMHelp.add(jMIHotkeys);

        jMIManualApp.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F1, 0));
        jMIManualApp.setText("Manual");
        jMIManualApp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMIManualAppActionPerformed(evt);
            }
        });
        jMHelp.add(jMIManualApp);

        jMBMenu.add(jMHelp);

        setJMenuBar(jMBMenu);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void jMIExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExitActionPerformed
        closeApplication();
    }//GEN-LAST:event_jMIExitActionPerformed

    private void jBLoadFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBLoadFileActionPerformed
        loadNewFile();
    }//GEN-LAST:event_jBLoadFileActionPerformed

    private void jRBScribbleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBScribbleActionPerformed
        setScribbleMode();
    }//GEN-LAST:event_jRBScribbleActionPerformed

    private void jRBBoundingBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBBoundingBoxActionPerformed
        setBoundingBoxMode();
    }//GEN-LAST:event_jRBBoundingBoxActionPerformed

    private void jMILoadFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMILoadFileActionPerformed
        loadNewFile();
    }//GEN-LAST:event_jMILoadFileActionPerformed

    private void formWindowStateChanged(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowStateChanged
        // compute the screen resolution (the user might have dragged the application on another screen)
        screenRes.setScreenResolution(this);
        defaultDPIValue = Toolkit.getDefaultToolkit().getScreenResolution();

        adjustWindowDimensions(evt.getNewState(), getAvailableScreenSize(userPrefs.getPreferredDPI()));
        log.info("**screen resolution: {}x{}", screenRes.getScreenResolution().width, screenRes.getScreenResolution().height);
    }//GEN-LAST:event_formWindowStateChanged

    private void jBPrevFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBPrevFrameActionPerformed
        getFrame(Constants.PREV_FRAME);
    }//GEN-LAST:event_jBPrevFrameActionPerformed

    private void jBNextFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBNextFrameActionPerformed
        // get the next frame; save the data and load other data if there exists any
        getFrame(Constants.NEXT_FRAME);
    }//GEN-LAST:event_jBNextFrameActionPerformed

    private void jBJumpFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBJumpFrameActionPerformed
        long frame;
        frame = Long.parseLong(jTFJumpFrame.getText());

        jSVideoPlayer.setValue((int) frame);

        getFrame(Constants.JUMP_TO_FRAME);
    }//GEN-LAST:event_jBJumpFrameActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        closeApplication();
    }//GEN-LAST:event_formWindowClosing

    private void jBPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBPlayActionPerformed
        // if the video is not playing, create a new thread and play the video
        if ((PLAY_BUTTON_TEXT).equals(jBPlay.getName())) {
            // save the current work (if wanted) and remove all the drawings from the image
            if (Constants.GTSaveInterruptCodes.FRAME_ATTRIB_NOT_SET == removeObjsOverlays()) {
                // the user wants to correct the frame attributes, do not play the video
                return;
            }

            jBPlay.setName(PAUSE_BUTTON_TEXT);
            jBPlay.setIcon(Icons.PAUSE_ICON);
            // stop the drawing
            gc.setLabelingType(DrawConstants.DrawType.DO_NOT_DRAW);

            gc.playVideo(this);
        } else if ((PAUSE_BUTTON_TEXT).equals(jBPlay.getName())) {
            jBPlay.setName(PLAY_BUTTON_TEXT);
            jBPlay.setIcon(Icons.PLAY_ICON);

            // activate the drawing
            if (jRBScribble.isSelected()) {
                gc.setLabelingType(DrawConstants.DrawType.DRAW_CROP);
            } else if (jRBBoundingBox.isSelected()) {
                gc.setLabelingType(DrawConstants.DrawType.DRAW_BOUNDING_BOX);
            }

            gc.pausePlayVideo();
        }
    }//GEN-LAST:event_jBPlayActionPerformed

    private void jSVideoPlayerMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jSVideoPlayerMouseReleased
        if (jSVideoPlayer.isEnabled()) {
            getFrame(Constants.JUMP_TO_FRAME);
        }
    }//GEN-LAST:event_jSVideoPlayerMouseReleased

    private void jMIAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIAboutActionPerformed
        new About(new javax.swing.JFrame(), true, "About Pixie").setVisible(true);
    }//GEN-LAST:event_jMIAboutActionPerformed

    private void jMIManualAppActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIManualAppActionPerformed
        if (Desktop.isDesktopSupported()) {
            try {
                File myFile = new File(Constants.APPLICATION_MANUAL);
                if (myFile.exists()) {
                    Desktop.getDesktop().open(myFile);
                } else {
                    log.error("Application manual not found!");
                }
            } catch (IOException ex) {
                log.error("Application manual not found or not accessible");
                log.debug("Application manual not found or not accessible {}", ex);
            }
        }
    }//GEN-LAST:event_jMIManualAppActionPerformed

    private void jBNewScribbleObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBNewScribbleObjActionPerformed
        // new object is available for scribble purposes and edit mode
        if (jRBScribble.isSelected()) {
            if (jBNewScribbleObj.getText().equals(NEW_OBJ_BUTTON_TEXT)) {
                jRBBoundingBox.setEnabled(true);
                jRBScribble.setEnabled(true);

                gc.setLabelingType(DrawConstants.DrawType.DRAW_CROP);

                // do not draw transparent box
                gc.resetSelectedObject();

                // init the new object
                gc.initCurrentObject();

                // start counting the time for the scribble object
                gc.startCurrentObjectTimer();

                // trigger the regeneration of the display lists based on the user options
                gc.refreshDisplayList();

                jBNewScribbleObj.setText(SAVE_OBJ_BUTTON_TEXT);

            } else if (jBNewScribbleObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {
                // save the current object in the object list
                addScribbleObjToObjList();

                // no drawing can be done while there is no new object
                gc.setLabelingType(DrawConstants.DrawType.DO_NOT_DRAW);

                jBNewScribbleObj.setText(NEW_OBJ_BUTTON_TEXT);
            }

            // do not draw a box with what was drawn before
            gc.resetBBox();
        } else if (jRBEditMode.isSelected()) {
            // enable the drawing of one crop, one time only
            gc.setAddCropToObj();
        }
    }//GEN-LAST:event_jBNewScribbleObjActionPerformed

    private void jRBEditModeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBEditModeActionPerformed
        setEditMode();
    }//GEN-LAST:event_jRBEditModeActionPerformed

    private void jCBPlayBackwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBPlayBackwardActionPerformed
        if (jCBPlayBackward.isSelected()) {
            gc.setPlayMode(Constants.PLAY_MODE_BACKWARD);
        } else {
            gc.setPlayMode(Constants.PLAY_MODE_FORWARD);
        }

        userPrefs.setPlayBackward(jCBPlayBackward.isSelected());

        getFrame(Constants.JUMP_TO_FRAME);
    }//GEN-LAST:event_jCBPlayBackwardActionPerformed

    private void jMIHotkeysActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIHotkeysActionPerformed
        new About(new javax.swing.JFrame(), true, "Pixie Hotkeys").setVisible(true);
    }//GEN-LAST:event_jMIHotkeysActionPerformed

    private void jRBPolygonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRBPolygonActionPerformed
        setPolygonMode();
    }//GEN-LAST:event_jRBPolygonActionPerformed

    private void jBNewPolygonObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jBNewPolygonObjActionPerformed
        // new object is available for scribble purposes and edit mode
        if (jRBPolygon.isSelected()) {
            if (jBNewPolygonObj.getText().equals(NEW_OBJ_BUTTON_TEXT)) {
                gc.setLabelingType(DrawConstants.DrawType.DRAW_POLYGON);

                // do not draw transparent box
                gc.resetSelectedObject();

                // init the new object
                gc.initCurrentObject();

                // start counting the time for the scribble object
                gc.startCurrentObjectTimer();

                // trigger the regeneration of the display lists based on the user options
                gc.refreshDisplayList();

                // initialise the polygon object
                gc.initPolygon();

                jBNewPolygonObj.setText(SAVE_OBJ_BUTTON_TEXT);

            } else if (jBNewPolygonObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {
                // no drawing can be done while there is no new object
                gc.setLabelingType(DrawConstants.DrawType.DO_NOT_DRAW);

                // open the preview of the object
                gc.openPolyPrevWin();

                // set the text of the new/save button
                jBNewPolygonObj.setText(NEW_OBJ_BUTTON_TEXT);
            }

            // do not draw a box with what was drawn before
            gc.resetBBox();
        }
    }//GEN-LAST:event_jBNewPolygonObjActionPerformed

    private void jCBFlipVerticallyImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBFlipVerticallyImgActionPerformed
        gc.setFlipVerticallyImage(jCBFlipVerticallyImg.isSelected());

        // save the user preferences
        userPrefs.setFlipVertically(jCBFlipVerticallyImg.isSelected());
    }//GEN-LAST:event_jCBFlipVerticallyImgActionPerformed

    private void jCBMirrorImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBMirrorImgActionPerformed
        gc.setMirrorImage(jCBMirrorImg.isSelected());

        // save the user preferences
        userPrefs.setMirrorImage(jCBMirrorImg.isSelected());
    }//GEN-LAST:event_jCBMirrorImgActionPerformed

    private void jMIUserConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIUserConfigActionPerformed
        createConfigWindow().setVisible(true);
    }//GEN-LAST:event_jMIUserConfigActionPerformed

    private void jCBShowCurrentObjActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBShowCurrentObjActionPerformed
        userPrefs.setShowJustCurrentObj(jCBShowCurrentObj.isSelected());

        gc.refreshDisplayList();
    }//GEN-LAST:event_jCBShowCurrentObjActionPerformed

    private void jCBIlluminationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBIlluminationActionPerformed
        currentFrame.setIllumination(jCBIllumination.getItemAt(jCBIllumination.getSelectedIndex()));
    }//GEN-LAST:event_jCBIlluminationActionPerformed

    private void jCBWeatherActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBWeatherActionPerformed
        currentFrame.setWeather(jCBWeather.getItemAt(jCBWeather.getSelectedIndex()));
    }//GEN-LAST:event_jCBWeatherActionPerformed

    private void jCBRoadTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBRoadTypeActionPerformed
        currentFrame.setRoadType(jCBRoadType.getItemAt(jCBRoadType.getSelectedIndex()));
    }//GEN-LAST:event_jCBRoadTypeActionPerformed

    private void jCBRoadEventActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBRoadEventActionPerformed
        currentFrame.setRoadEvent(jCBRoadEvent.getItemAt(jCBRoadEvent.getSelectedIndex()));
    }//GEN-LAST:event_jCBRoadEventActionPerformed

    private void jCBCountryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBCountryActionPerformed
        currentFrame.setCountry(jCBCountry.getItemAt(jCBCountry.getSelectedIndex()));
    }//GEN-LAST:event_jCBCountryActionPerformed

    private void jCBWipersVisibleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBWipersVisibleActionPerformed
        currentFrame.setWipersVisible(jCBWipersVisible.isSelected());
    }//GEN-LAST:event_jCBWipersVisibleActionPerformed

    private void jCBDirtVisibleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBDirtVisibleActionPerformed
        currentFrame.setDirtVisible(jCBDirtVisible.isSelected());

    }//GEN-LAST:event_jCBDirtVisibleActionPerformed

    private void jCBImageDistortedActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCBImageDistortedActionPerformed
        currentFrame.setImageDistorted(jCBImageDistorted.isSelected());
    }//GEN-LAST:event_jCBImageDistortedActionPerformed

    private void jMIAttributesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIAttributesActionPerformed
        displayAttributesEditWin(AttributesDefinition.TAB_FRAME);
    }//GEN-LAST:event_jMIAttributesActionPerformed

    private void jPPrevResultImgMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jPPrevResultImgMouseClicked
        BufferedImage resultImg = gc.getImageResult();
        if (resultImg != null) {
            new ImagePreview(this, true, resultImg, "Image Result").setVisible(true);
        }
    }//GEN-LAST:event_jPPrevResultImgMouseClicked

    private void jMIExportOrigImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExportOrigImgActionPerformed
        ExportImage.exportImage(gc.getdPImgToLabel().getOrigImg(),
                userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.ORIGINAL_IMG.split(" ")[0]),
                userPrefs.getImgExportExtension());
    }//GEN-LAST:event_jMIExportOrigImgActionPerformed

    private void jMIExportWorkingImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExportWorkingImgActionPerformed
        ExportImage.exportImage(getSegmentedImage(),
                userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.SEGMENTED_IMG.split(" ")[0]),
                userPrefs.getImgExportExtension());
    }//GEN-LAST:event_jMIExportWorkingImgActionPerformed

    private void jMIExportSemanticImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExportSemanticImgActionPerformed
        ExportImage.exportImage(gc.getImageResult(),
                userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.SEMANTIC_SEGMENTATION_IMG.split(" ")[0]),
                userPrefs.getImgExportExtension());
    }//GEN-LAST:event_jMIExportSemanticImgActionPerformed

    private void jMIExportJoinedImgActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExportJoinedImgActionPerformed
        // send the the original image, the segmented one and the result image
        ExportImagesGUI eiGUI = new ExportImagesGUI(this, gc.getdPImgToLabel().getOrigImg(),
                getSegmentedImage(),
                gc.getImageResult(),
                userPrefs.getImgExportPath(),
                createExportImgFileName("JoinedImages"),
                userPrefs.getImgExportExtension(),
                gc.isExistsScribbleObj());
        // resize the segmented preview image in order to avoid loosing the edges of the boxes due to the default resize
        Dimension fitSize = PanelResolution.computeOptimalPanelSize(gc.getdPImgToLabel().getOrigImgSize(), eiGUI.getPreviewSize());
        eiGUI.setSegmentedImgResized(getSegmentedImage(fitSize));

        eiGUI.setVisible(true);
    }//GEN-LAST:event_jMIExportJoinedImgActionPerformed

    private void jMIMoveLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveLeftActionPerformed
        // move the object to the left
        gc.moveSelection(-1, 0);
    }//GEN-LAST:event_jMIMoveLeftActionPerformed

    private void jMIMoveRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveRightActionPerformed
        // move the object to the right
        gc.moveSelection(1, 0);
    }//GEN-LAST:event_jMIMoveRightActionPerformed

    private void jMIMoveUpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveUpActionPerformed
        // move the object up
        gc.moveSelection(0, -1);
    }//GEN-LAST:event_jMIMoveUpActionPerformed

    private void jMIMoveDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIMoveDownActionPerformed
        // move the object down
        gc.moveSelection(0, 1);
    }//GEN-LAST:event_jMIMoveDownActionPerformed

    private void jMIDecreaseLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseLeftActionPerformed
        // decrease left
        gc.changeSizeSelection(1, 0, 0, 0);
    }//GEN-LAST:event_jMIDecreaseLeftActionPerformed

    private void jMIDecreaseRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseRightActionPerformed
        // decrease right
        gc.changeSizeSelection(0, 0, -1, 0);
    }//GEN-LAST:event_jMIDecreaseRightActionPerformed

    private void jMIDecreaseTopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseTopActionPerformed
        // decrease top
        gc.changeSizeSelection(0, 1, 0, 0);
    }//GEN-LAST:event_jMIDecreaseTopActionPerformed

    private void jMIDecreaseBottomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseBottomActionPerformed
        // decrease bottom
        gc.changeSizeSelection(0, 0, 0, -1);
    }//GEN-LAST:event_jMIDecreaseBottomActionPerformed

    private void jMIIncreaseLeftActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseLeftActionPerformed
        // increase left
        gc.changeSizeSelection(-1, 0, 0, 0);
    }//GEN-LAST:event_jMIIncreaseLeftActionPerformed

    private void jMIIncreaseRightActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseRightActionPerformed
        // increase right
        gc.changeSizeSelection(0, 0, 1, 0);
    }//GEN-LAST:event_jMIIncreaseRightActionPerformed

    private void jMIIncreaseTopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseTopActionPerformed
        // increase top
        gc.changeSizeSelection(0, -1, 0, 0);
    }//GEN-LAST:event_jMIIncreaseTopActionPerformed

    private void jMIIncreaseBottomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseBottomActionPerformed
        // increase bottom
        gc.changeSizeSelection(0, 0, 0, 1);
    }//GEN-LAST:event_jMIIncreaseBottomActionPerformed

    private void jMIDecreaseBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDecreaseBoxActionPerformed
        // decrease box
        gc.changeSizeSelection(1, 1, -1, -1);
    }//GEN-LAST:event_jMIDecreaseBoxActionPerformed

    private void jMIIncreaseBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIIncreaseBoxActionPerformed
        // increase box
        gc.changeSizeSelection(-1, -1, 1, 1);
    }//GEN-LAST:event_jMIIncreaseBoxActionPerformed

    private void jMICancelObjectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMICancelObjectActionPerformed
        cancelCurrentObject();
    }//GEN-LAST:event_jMICancelObjectActionPerformed

    private void jMINextFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMINextFrameActionPerformed
        // get the next frame; save the data and load other data if there exists any
        getFrame(Constants.NEXT_FRAME);
    }//GEN-LAST:event_jMINextFrameActionPerformed

    private void jMIPrevFrameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIPrevFrameActionPerformed
        getFrame(Constants.PREV_FRAME);
    }//GEN-LAST:event_jMIPrevFrameActionPerformed

    private void jMIDeleteObjectActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIDeleteObjectActionPerformed
        // delete the selected object
        gc.removeSelection();
    }//GEN-LAST:event_jMIDeleteObjectActionPerformed

    private void jMIExportConfigActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMIExportConfigActionPerformed
        UserConfigs configWin = createConfigWindow();
        configWin.switchToTab(UserConfigs.TAB_IMAGE_EXPORT);
        configWin.setVisible(true);
    }//GEN-LAST:event_jMIExportConfigActionPerformed

    /**
     * Save the previous work and load a new file. For the new file load the
     * ground truth and refresh data structures.
     */
    private void loadNewFile() {
        /* Check if there was another file open and segmentation already done. Ask the user to save its work.*/
        if (Constants.GTSaveInterruptCodes.FRAME_ATTRIB_NOT_SET == saveCurrentWork()) {
            // the frame attributes are not correct and the user wants to fix them
            return;
        }

        // open the file loader and choose the wanted file
        chooseLoadFile();

        // if the window is not maximized, pack the window and set a new min size
        if (this.getExtendedState() == javax.swing.JFrame.NORMAL) {
            packGUI();
        }
    }

    /**
     * Open the chooser window and allow the user to open the wanted file. After
     * choosing the wanted file, load all the needed data: image, ground truth
     * etc.
     */
    private void chooseLoadFile() {
        String newFile;

//        if (runningOnline) {
//            List<VideoFileInfo> videoList = gc.getVideoFileList();
//
//            // open the custom file chooser
//            CustomFileChooser fileChooser = new CustomFileChooser(this, true, videoList);
//            fileChooser.setVisible(true);
//
//            // get the index of the selected file
//            int idxSelection = fileChooser.getSelectedIndex();
//
//            // do not go further if the selection is not valid
//            if ((idxSelection < 0) || (idxSelection >= videoList.size())) {
//                return;
//            }
//
//            // open the selected file
//            newFile = videoList.get(idxSelection).getVideoPath();
//        } else {
            newFile = loadFileChooser();
//        }

        // make sure a valid path is returned
        if (!("").equals(newFile)) {
            // set the name of the chosen file to be able to open it
            gc.setChosenFile(newFile);

            // load the image on panel and all the other data available
            loadFile();

            // start counting the time spent in the current frame for the log table
//            frameTimer.start();
        }
    }

    /**
     * Shows the load file window and allows the user to select the wanted file.
     * The file chooser offers a series of advantages for loading files. Allows
     * the user to choose only specific files; it opens in the last used
     * directory etc.
     */
    private String loadFileChooser() {
        try {
            int returnVal = chooser.showOpenDialog(this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                userPrefs.setLastDirectory(chooser.getSelectedFile().getParent());
                return chooser.getSelectedFile().getCanonicalPath();
            }
        } catch (IOException ex) {
            log.error("File not found or not accessible");
            log.debug("File not found or not accessible {}", ex);
        }
        return "";
    }

    /**
     * Load the selected file, together with its ground truth. Refresh data
     * structures and the gui.
     */
    private void loadFile() {
        // reset the characteristics of the parent panel
        redrawViewPanel();

        // remove the old image if it exists
        jPVImgToLabel.removeAll();

        gc.loadImageOnPanel(getDrawType());

        // add the drawing panel to the gui
        jPVImgToLabel.add(gc.getdPImgToLabel());

        // initialise the characteristics of the video slider
        initVideoSlider();

        // display video info in gui
        updateGUIVideo();

        // show the total number of frames
        jLNoFrames.setText(gc.getNoFrames() + " frames");

        // generate the result image only with background
        initResultImage();

        // add pen listener to be able to use some features of the graphic tablet
        AwtPenToolkit.addPenListener(gc.getdPImgToLabel(), penFunctions);

        // enable and disable some components
        enableComponentsLoadImg();

        // run the matting algorithm if there are scribble objects
        gc.runMattingForObjList();

        // make the current object null
        gc.cancelCurrentObject();

        // reinit the list of used colors
        gc.reinitObjectColorsList();

    }

    /**
     * Load the segmented result image to a visualisation panel.
     */
    private void loadImgResult(BufferedImage resultImg) {
        // remove the old image if it exists

        jPPrevResultImg.removeAll();

        gc.loadImgResult(resultImg);

        jPPrevResultImg.setPreferredSize(new Dimension(Constants.RESULT_PANEL_WIDTH, Constants.RESULT_PANEL_HEIGHT));

        jPPrevResultImg.add(gc.getdPImgResult());

        this.validate();
        this.repaint();
        log.info("Loaded the result image.");
    }

    /**
     * Implementation for getting a red image from the algorithm.
     */
    private void initResultImage() {
        // draw the resulting image
        loadImgResult(gc.getdPImgToLabel().getOrigImg());
    }

    /**
     * Get the selected drawing type.
     */
    private DrawConstants.DrawType getDrawType() {
        if (jRBBoundingBox.isSelected()) {
            // if it is the bounding box selected, draw; else do not
            return DrawConstants.DrawType.DRAW_BOUNDING_BOX;
        }

        return DrawConstants.DrawType.DO_NOT_DRAW;
    }

    /**
     * Enables and disables the needed components when a video/image is loaded.
     */
    private void enableComponentsLoadImg() {
        // enable the user configuration window
        jMIUserConfig.setEnabled(true);
        jMExport.setEnabled(true);

        //// Enable functionalityes
        jRBBoundingBox.setEnabled(true);
        jRBScribble.setEnabled(true);
        jRBEditMode.setEnabled(true);
        jRBPolygon.setEnabled(true);

        // enable the video player buttons
        jBPlay.setEnabled(runningOnline);
        jBPrevFrame.setEnabled(true);
        jBNextFrame.setEnabled(true);
        jBJumpFrame.setEnabled(true);
        jSVideoPlayer.setEnabled(true);
        jCBPlayBackward.setEnabled(true);
        jCBFlipVerticallyImg.setEnabled(true);
        jCBMirrorImg.setEnabled(true);

        // enable navigation menu
        jMNavigation.setEnabled(true);

        // disable the new scribble object button
        jBNewScribbleObj.setEnabled(false);
        setShowSemanticOptions(false);

        // disaable the new polygon object button
        jBNewScribbleObj.setEnabled(false);

        if (jRBScribble.isSelected()) {
            // enable the new scribble object button
            jBNewScribbleObj.setEnabled(true);

            // show the results panel
            setShowSemanticOptions(true);

        } else if (jRBPolygon.isSelected()) {
            // enable the new polygon object button
            jBNewScribbleObj.setEnabled(true);
        }

        if (jBNewScribbleObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {
            jBNewScribbleObj.setText(NEW_OBJ_BUTTON_TEXT);
        }

        if (jBNewPolygonObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {
            jBNewPolygonObj.setText(NEW_OBJ_BUTTON_TEXT);
        }

        // if the load was done during the play of the video, set the button back to play, from pause
        if (jBPlay.getName().equals(PAUSE_BUTTON_TEXT)) {
            jBPlay.setName(PLAY_BUTTON_TEXT);
            jBPlay.setIcon(Icons.PLAY_ICON);

            // pause the previous thread (a new one will be created if the user plays the current video)
            gc.pausePlayVideo();

            // activate the drawing
            if (jRBScribble.isSelected()) {
                gc.setLabelingType(DrawConstants.DrawType.DRAW_CROP);
            } else if (jRBBoundingBox.isSelected()) {
                gc.setLabelingType(DrawConstants.DrawType.DRAW_BOUNDING_BOX);
            }
        }
    }

    /**
     * Save the result image in a file and the segmented objects in the ground
     * truth storage.
     */
    private void saveImgResultsAndData() {

        // set the annotation time
        currentFrame.setFrameDuration("0");

        // save the data in the ground truth storage
        gc.saveDataAsGroundTruth();
    }

    /**
     * Chooses which actions to happen based on the feedback from the observers.
     */
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof ObservedActions.Action) {
            ObservedActions.Action type = ((ObservedActions.Action) arg);

            switch (type) {
                case REFRESH_FRAME_NO:
                    // while the video is playing, refresh the frame index and the slider
                    updateGUIVideo();
                    break;

                case ADD_OBJECT_ON_PANEL:
                    // update the objects list because a new object was saved
                    addObjButton(gc.getLastSavedObj());
                    break;

                case REFRESH_OBJ_LIST_PANEL:
                    refreshObjPanelList();
                    break;

                case OPEN_POLYGON_SEGMENTATION:
                    savePolygonObject();
                    break;

                case ADD_GUI_KEY_EVENT_DISPATCHER:
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(guiKeyEventDispatch);
                    break;

                case REMOVE_GUI_KEY_EVENT_DISPATCHER:
                    KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(guiKeyEventDispatch);
                    break;

                case HIGHLIGHT_OBJECT:
                    highlightSelectedObject();
                    break;

                case EDIT_OBJECT_ACTION:
                    gc.editObject();
                    break;

                case LOAD_FRAME_ANNOTATION:
                    loadFrameAnnotation();

                    break;

                case REFRESH_DISPLAY:
                    gc.refreshUserConfigs();
                    gc.refreshDisplayList();
                    loadCBdata();
                    break;

                case REFRESH_PANEL_OPTIONS:
                    setShowSemanticOptions(gc.isExistsScribbleObj());
                    break;

                case RELOAD_ATTRIBUTES:
                    // reload frame attributes and reload combo boxes
                    loadCBdata();
                    break;

                case REFRESH_APPLICATION_VIEW:
                    previewScreenCalibration(userPrefs.getPreferredDPI(), false);
                    break;

                case DISPLAY_EDIT_ATTRIBUTES_WIN:
                    // the user chose to edit the attributes; display the dialog and allow it
                    displayAttributesEditWin(AttributesDefinition.TAB_OBJECT);
                    break;

                default:
                    break;
            }
        }
    }

    /**
     * Add a new object on the panel with saved objects.
     *
     * @param obj the object for which an entry is created
     */
    private void addObjButton(Objects obj) {
        // prepare the position of the button
        GridBagConstraints gbc = new GridBagConstraints();
        // on the panel there is a label already
        gbc.gridy = jPObjList.getComponentCount() - 1;
        gbc.gridx = 0;
        gbc.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gbc.weightx = 0.01;
        gbc.insets = new java.awt.Insets(10, 2, 2, 2);

        // get the color of the object based on the labeling type: scribble, bbox
        java.awt.Color labelTypeColor = gc.getLabelTypeColor(obj);

        /*--------------------1. add object button----------------------------*/
        CustomColorButton jBObj = new CustomColorButton(labelTypeColor, Color.black, "id: " + obj.getObjectId());
        jBObj.setName("id: " + obj.getObjectId());
        jBObj.addActionListener(new ObjListButtons());
        jBObj.addObserver(this);

        /*--------------------2. add labels with object attributes------------*/
        // create the labels which keep the info related to the object: type, class, value, occluded
        String text = obj.getObjectType();
        JLabel jLType = new JLabel(text);

        text = obj.getObjectClass();
        JLabel jLClass = new JLabel(text);

        text = obj.getObjectValue();
        JLabel jLValue = new JLabel(text);

        text = obj.getOccluded();
        JLabel jLOccluded = new JLabel(text);

        /*--------------------3. create the panel with all the components-----*/
        // create the panel which keeps the object and its attributes
        JPanel jPObjPrev = new JPanel(new java.awt.GridLayout(5, 1));
        jPObjPrev.add(jBObj);

        jPObjPrev.add(jLType);
        jPObjPrev.add(jLClass);
        jPObjPrev.add(jLValue);
        jPObjPrev.add(jLOccluded);

        jPObjPrev.setBackground(Color.white);

        jPObjList.add(jPObjPrev, gbc);

        // refresh the object relatel info in the panel info (no of objects, how many of each type)
        refreshObjectInfo();
    }

    /**
     * Adds a label which is not needed for active purposes. It just aligns
     * north all the other components.
     */
    private void addButtonsAlignComponent() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 100;        // put the label as low as possible
        gbc.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gbc.weighty = 0.5;
        jPObjList.add(jLAlignNorth, gbc);
    }

    /**
     * Enable/Disable the fields in order to get to edit mode.
     */
    private void setEditMode() {
        gc.setLabelingType(DrawConstants.DrawType.EDIT_MODE);

        // scribble obj: ask to save the current object if it is not empty
        saveWorkScribbleObject();

        // polygon obj: ask to save the current object if it is not empty
        saveWorkPolygonObject();

        // enable the new object button to be able to add crop to object, in edit mode
        jBNewScribbleObj.setEnabled(true);

        // do not show the guide lines, because there is no segmentation going on
        gc.setShowGuideShape(false);
    }

    /**
     * Enable/Disable the fields in order to get to scribble mode.
     */
    private void setScribbleMode() {
        gc.setLabelingType(DrawConstants.DrawType.DO_NOT_DRAW);

        // polygon obj: ask to save the current object if it is not empty
        saveWorkPolygonObject();

        // display the results panel
        setShowSemanticOptions(true);

        // enable the new object button
        jBNewScribbleObj.setEnabled(true);

        // disable the polygon new object button
        jBNewPolygonObj.setEnabled(false);

        // reset the mouse position in order to avoid to draw a box which is not wanted
        gc.resetBBox();

        // do not draw transparent box
        gc.resetSelectedObject();

        // do not highlight any object in the objects list
        selectObjectButton(0L);
    }

    /**
     * Enable/Disable the fields in order to get to bounding box mode.
     */
    private void setBoundingBoxMode() {
        gc.setLabelingType(DrawConstants.DrawType.DRAW_BOUNDING_BOX);

        // scribble obj: ask to save the current object if it is not empty
        saveWorkScribbleObject();

        // polygon obj: ask to save the current object if it is not empty
        saveWorkPolygonObject();

        // do not draw transparent box
        gc.resetSelectedObject();

        // do not highlight any object in the objects list
        selectObjectButton(0L);

        // reset the mouse position in order to avoid to draw a box which is not wanted
        gc.resetBBox();
    }

    /**
     * Enable/Disable the fields in order to get to polygon mode.
     */
    private void setPolygonMode() {
        gc.setLabelingType(DrawConstants.DrawType.DO_NOT_DRAW);

        // scribble obj: ask to save the current object if it is not empty
        saveWorkScribbleObject();

        // enable the new polygon object button
        jBNewPolygonObj.setEnabled(true);

        // do not draw transparent box
        gc.resetSelectedObject();

        // do not highlight any object in the objects list
        selectObjectButton(0L);

        // reset the mouse position in order to avoid to draw a box which is not wanted
        gc.resetBBox();
    }

    /**
     * Asks the user to save the object in progress to the list of objects. If
     * the user agrees, the object is saved; else it is discarded. Disables the
     * new object button and removes the results panel.
     */
    private void saveWorkScribbleObject() {
        if (jBNewScribbleObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {

            int response = Messages.showWarningYesNoMessager(this,
                    "There is an object in progress.\nThe work will be lost if you choose no!\nDo you want to save it?",
                    "Object not saved!!!");

            if (JOptionPane.YES_OPTION == response) {
                // save the object
                addScribbleObjToObjList();

            } else {
                // reset the scribbles object if it was started but the user does not want to finish it

                // remove the object from the object map
                if (gc.getCurrentObject() != null) {
                    gc.cancelObjectMap(gc.getCurrentObject(), gc.getCurrentObject().getOuterBBox());
                }

                // cancel the object
                gc.cancelCurrentObject();
            }

            // change text to new object
            jBNewScribbleObj.setText(NEW_OBJ_BUTTON_TEXT);
        }

        // remove the results panel of the scribbles
        setShowSemanticOptions(gc.isExistsScribbleObj());

        // disable the new scribble object button
        jBNewScribbleObj.setEnabled(false);

        // disable the new polygon object button
        jBNewPolygonObj.setEnabled(false);
    }

    /**
     * Adds the current scribble object to the list of objects.
     */
    private void addScribbleObjToObjList() {
        // make sure the object is an instance of scribbles
        if (!(gc.getCurrentObject() instanceof ObjectScribble)) {
            return;
        }

        if (!((ObjectScribble) gc.getCurrentObject()).getCropList().isEmpty()) {
            // set the type and source of labeling used for the segmentation of the object
            gc.setSegmentationType(ConstantsLabeling.LABEL_SCRIBBLE);
            gc.setSegmentationSource(ConstantsLabeling.LABEL_SOURCE_MANUAL);

            // save the object in the objects list and erase the current object
            gc.addObjToObjectList();

            // update the result image with the new object
            gc.updateResultPanel();

            // mark the fact that a scribble object was drawn on the interface
            gc.setExistsScribbleObj(true);
            setShowSemanticOptions(true);
        }
    }

    /**
     * Adds the current scribble object to the list of objects.
     */
    private void addPolygonObjToObjList() {
        if (gc.getCurrentObject() instanceof ObjectPolygon) {
            // set the type and source of labeling used for the segmentation of the object
            gc.setSegmentationType(ConstantsLabeling.LABEL_POLYGON);
            gc.setSegmentationSource(ConstantsLabeling.LABEL_SOURCE_MANUAL);

            // save the object in the objects list and erase the current object
            gc.addObjToObjectList();
        }

    }

    /**
     * Ask the user to save the current work. Else erase it.
     *
     * @return an error/interruption code showing if there was something wrong
     * in the saving of data process or all went smooth
     */
    private Constants.GTSaveInterruptCodes saveCurrentWork() {
        if (gc.getObjectsListSize() > 0) {
            int response = Messages.showWarningYesNoMessager(this,
                    "There are objects which were not saved in the ground truth storage.\nThe work will be lost if you choose no!\nDo you want to save them?",
                    "Objects not saved!!!");
            if (JOptionPane.YES_OPTION == response) {
                //make sure the frame attributes are set; warn the user if not and stop the changing of frame
                if (!isFrameAttribSet()) {
                    return Constants.GTSaveInterruptCodes.FRAME_ATTRIB_NOT_SET;
                }

                // save the list of objects
                saveImgResultsAndData();
            }
        }
        // everything seems to be correct
        return Constants.GTSaveInterruptCodes.NO_ERROR;
    }

    /**
     * Get the frame specified by the action parameter: next, prev, jumpTo.
     *
     * @param action - shows which frame shall be retrieved: next, prev, jumpTo
     */
    private void getFrame(int action) {
        // make sure the video is loaded
        if (gc.isDataLoaded()) {

            //make sure the frame attributes are set; warn the user if not and stop the changing of frame
            if (!isFrameAttribSet()) {
                // the attributes are not saved correctly, the user wants to correct them
                return;
            }

            // save the results and the objects in the ground truth storage
            saveImgResultsAndData();

            // reset the timings of the objects because new ones have to be computed
            gc.resetAllObjsDuration();

            switch (action) {
                case Constants.NEXT_FRAME:
                    // automatic export of images if the user wants it
                    autoExportImages();

                    // if the frame is not the last one, load the next frame
                    Timings timer = new Timings();
                    timer.start();
                    gc.nextFrame();
                    timer.stop("next frame");
                    break;

                case Constants.PREV_FRAME:
                    gc.prevFrame();
                    break;

                case Constants.JUMP_TO_FRAME:
                    gc.jumpToFrame(jSVideoPlayer.getValue());
                    break;

                default:
                    log.warn("Unknown command. Known: next, prev, jumpTo!");
                    break;
            }
            // display video info in gui
            updateGUIVideo();

            // generate the result image only with background
            initResultImage();

            // reinit the list of used colors
            gc.reinitObjectColorsList();

            // switch to edit mode because the user has higher chances to correct a BBox/Scribble rather than creating a new one
            jRBEditMode.setSelected(true);
            setEditMode();

            // cancel the current object in order to avoid the carry of the bounding box in the next frames
            gc.cancelCurrentObject();

            // do not select any object
            resetSelectedObj();

            // run the matting algorithm if there are scribble objects
            gc.runMattingForObjList();
        }
    }

    /**
     * Checks if the frame attributes are correctly set and asks the user to
     * correct them. If the user chooses to correct them, the application does
     * not continue with the saving of data; it will cancel the process and go
     * back to the normal state.
     *
     * @return true if the user wants to save the data as it is and false if the
     * saving of data shall not be done yet, because the user wants to correct
     * it
     */
    private boolean isFrameAttribSet() {
        // make sure the user wants to check the frame attributes
        if (!userPrefs.isCheckFrameAnnotations()) {
            return true;
        }

        StringBuilder invalidAttribute = new StringBuilder();

        // check each frame attribute and create a list of missing frame attributes
        Utils.checkAttributeValid(invalidAttribute, jCBIllumination.getItemAt(jCBIllumination.getSelectedIndex()), jLIllumination.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBWeather.getItemAt(jCBWeather.getSelectedIndex()), jLWeather.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBRoadType.getItemAt(jCBRoadType.getSelectedIndex()), jLRoadType.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBRoadEvent.getItemAt(jCBRoadEvent.getSelectedIndex()), jLRoadEvent.getText());
        Utils.checkAttributeValid(invalidAttribute, jCBCountry.getItemAt(jCBCountry.getSelectedIndex()), jLCountry.getText());

        // if missing attributes were found, display the message window to warn the user to correct the attributes
        if (invalidAttribute.length() > 0) {
            // remove the last comma and the last space from the string
            invalidAttribute.replace(invalidAttribute.length() - 2, invalidAttribute.length(), "");

            // ask the user if the current attributes shall be saved or not; if the user wants to correct them, stop the application and let him fix them
            int userChoice = Utils.messageAttributesSet(invalidAttribute.toString(), !runningOnline, this);

            if (userChoice == JOptionPane.CANCEL_OPTION) {
                // the user chose to edit the attributes; display the dialog and allow it
                displayAttributesEditWin(AttributesDefinition.TAB_FRAME);
            }

            // return false if the yes or edit attributes option were chosen
            return ((JOptionPane.YES_OPTION != userChoice) && (JOptionPane.CANCEL_OPTION != userChoice));
        }

        return true;
    }

    /**
     * Reset the selected object. Remove the highlights from the button and box.
     */
    private void resetSelectedObj() {
        // remove the highlight of the object
        gc.resetSelectedObject();

        // remove the highlight of the button
        selectObjectButton(0L);
    }

    /**
     * Remove the list of objects from the side panel and create it again.
     */
    private void refreshObjPanelList() {
        // refresh all the buttons from the panel

        // remove all buttons
        jPObjList.removeAll();

        // add the label which is aligning all the buttons north
        addButtonsAlignComponent();

        // create all the buttons again, from the list of ids
        List<Objects> objIdsList = gc.getObjectsList();

        objIdsList.forEach(obj -> addObjButton(obj));

        jPObjList.repaint();
        jPObjList.validate();

        // refresh the object related info in the panel info (no of objects, how many of each type)
        refreshObjectInfo();
    }

    /**
     * Refresh the information regarding the number of objects displayed on the
     * objects info panel: how many scribble objects, how many bounding box
     * objects etc.
     */
    private void refreshObjectInfo() {
        int noTotalObjs = gc.getObjectsListSize();
        int noBBoxObjs = 0;
        int noScribbleObjs = 0;
        int noPolygonObjs = 0;

        if (gc.getObjectsList() != null) {
            for (Objects obj : gc.getObjectsList()) {
                if (obj instanceof ObjectBBox) {
                    noBBoxObjs++;
                } else if (obj instanceof ObjectScribble) {
                    noScribbleObjs++;
                } else if (obj instanceof ObjectPolygon) {
                    noPolygonObjs++;
                }
            }
        }

        // show the panel only if there are objects
        if (noTotalObjs > 0) {
            jPObjInfo.setVisible(true);

            // set the number of objects
            jLTotalNoObjs.setText(Integer.toString(noTotalObjs));

            // set the number of bounding box objects
            jLNoBBoxObjsText.setVisible(noBBoxObjs != 0);
            jLNoBBoxObjs.setVisible(noBBoxObjs != 0);
            jLNoBBoxObjs.setText(Integer.toString(noBBoxObjs));

            // set the number of scribble objects
            jLNoScribbleObjsText.setVisible(noScribbleObjs != 0);
            jLNoScribbleObjs.setVisible(noScribbleObjs != 0);
            jLNoScribbleObjs.setText(Integer.toString(noScribbleObjs));

            // set the number of scribble objects
            jLNoPolygonObjsText.setVisible(noPolygonObjs != 0);
            jLNoPolygonObjs.setVisible(noPolygonObjs != 0);
            jLNoPolygonObjs.setText(Integer.toString(noPolygonObjs));

        } else {
            jPObjInfo.setVisible(false);
        }
    }

    /**
     * Save the segmented polygon object.
     */
    private void savePolygonObject() {
        // save the current object in the object list
        addPolygonObjToObjList();

        // reset the polygon object
        gc.resetPolygon();

        gc.setPolygonToDisplay();
        gc.setBBoxToDisplay();
    }

    /**
     * Asks the user to save the object in progress to the list of objects. If
     * the user agrees, the object is saved; else it is discarded. Disables the
     * new object button and removes the results panel.
     */
    private void saveWorkPolygonObject() {

        if (jBNewPolygonObj.getText().equals(SAVE_OBJ_BUTTON_TEXT)) {

            int response = Messages.showWarningYesNoMessager(this,
                    "There is an object in progress.\nThe work will be lost if you choose no!\nDo you want to save it?",
                    "Object not saved!!!");

            if (JOptionPane.YES_OPTION == response) {
                // save the current object in the object list
                addPolygonObjToObjList();

            } else {
                // reset the polygon object if it was started but the user does not want to finish it
                // cancel the object
                gc.cancelCurrentObject();
            }

            // change text to new object
            jBNewPolygonObj.setText(NEW_OBJ_BUTTON_TEXT);
        }

        // reset the polygon object
        gc.resetPolygon();

        gc.setPolygonToDisplay();
        gc.setBBoxToDisplay();
    }

    /**
     * Load the frame annotations to the combo boxes on the gui.
     */
    private void loadFrameAnnotation() {
        jCBIllumination.setSelectedItem(Utils.capitalize(currentFrame.getIllumination()));
        jCBWeather.setSelectedItem(Utils.capitalize(currentFrame.getWeather()));
        jCBRoadType.setSelectedItem(Utils.capitalize(currentFrame.getRoadType()));
        jCBRoadEvent.setSelectedItem(Utils.capitalize(currentFrame.getRoadEvent()));
        jCBCountry.setSelectedItem(Utils.capitalize(currentFrame.getCountry()));
        jCBWipersVisible.setSelected(currentFrame.isWipersVisible());
        jCBDirtVisible.setSelected(currentFrame.isDirtVisible());
        jCBImageDistorted.setSelected(currentFrame.isImageDistorted());
    }

    /**
     * Get the selected object and highlight it for the user to see it better.
     */
    private void highlightSelectedObject() {
        Objects selectedObj = gc.getSelectedObject();

        // if no object is selected, cancel the object and reurn
        if (selectedObj == null) {
            // if no object is selected, remove highlight
            selectObjectButton(-1L);

            // if no object is selected, cancel the current object
            gc.cancelCurrentObject();

            return;
        }

        // if there is an object selected, highlight it
        long idObj = selectedObj.getObjectId();

        // highlight the button
        selectObjectButton(idObj);

        // enable the edit buttons (if they have to be disabled, they will be later on)
        enableEditButtons(true);

        // display the preview results panel if it is a scribble object
        if (selectedObj instanceof ObjectScribble) {
            setShowSemanticOptions(true);

            // if the scribble object is selected, disable some edit options
            disableEditOptScribbleObj(selectedObj);
        } else {
            setShowSemanticOptions(gc.isExistsScribbleObj());
        }
    }

    /**
     * When selecting the scribble object (not one of its crops), the user
     * cannot edit its size by increasing/decreasing options, therefore they are
     * deactivated from the menu.
     *
     * @param selectedObj the object selected by the user
     */
    private void disableEditOptScribbleObj(Objects selectedObj) {
        if (selectedObj == null) {
            return;
        }

        // get the box which was selected
        DisplayBBox selectedBox = gc.getdPImgToLabel().getSelectedBox();

        if (selectedBox != null) {
            // convert coordinates to panel coordinates to be able to compare
            Rectangle objBoxPanel = gc.getdPImgToLabel().getResize().originalToResized(selectedObj.getOuterBBox());

            // if the outer box of the obejct and the selection match, it means the scribble object is selected (not a crop)
            if (objBoxPanel.equals(selectedBox.getPanelBox())
                    && (((ObjectScribble) selectedObj).getCrop(selectedBox.getPanelBox(), gc.getdPImgToLabel().getResize()) == null)) {
                // the selected object is the scribble obejct itself; disable some edit options
                enableEditButtons(false);
            }
        }
    }

    /**
     * Enable/Disable some of the menu options, which do not apply to all types
     * of objects, all the time.
     *
     * @param enable true if the fields should be enabled and false otherwise
     */
    private void enableEditButtons(boolean enable) {
        // the decrease object buttons
        jMIDecreaseLeft.setEnabled(enable);
        jMIDecreaseTop.setEnabled(enable);
        jMIDecreaseRight.setEnabled(enable);
        jMIDecreaseBottom.setEnabled(enable);

        // the increase object buttons
        jMIIncreaseLeft.setEnabled(enable);
        jMIIncreaseTop.setEnabled(enable);
        jMIIncreaseRight.setEnabled(enable);
        jMIIncreaseBottom.setEnabled(enable);

        // the increase/decrease box buttons
        jMIDecreaseBox.setEnabled(enable);
        jMIIncreaseBox.setEnabled(enable);
    }

    /**
     * Close application in a safe way. Close all the open resources and stop
     * the application.
     */
    private void closeApplication() {
        // ask the user to save its work if there are objects unsaved

        if (gc.getObjectsListSize() > 0) {
            int response = Messages.showQuestionYesNoCancelMessage(this,
                    "There are objects which were not saved in the ground truth storage.\nThe work will be lost if you choose no!\nDo you want to save them?",
                    "Objects not saved!!!");
            if (JOptionPane.YES_OPTION == response) {
                // make sure the frame attributes are saved and ask the user to correct them otherwise
                if (!isFrameAttribSet()) {
                    // the user wants to correct the data, do not close the application!
                    return;
                }

                // save the list of objects
                saveImgResultsAndData();
            } else if (JOptionPane.CANCEL_OPTION == response) {
                // do not close the application
                return;
            }
        }

        userPrefs.setPlayBackward(jCBPlayBackward.isSelected());

        userPrefs.saveUserPreferences();

        gc.terminateThreads();

        System.exit(0);
    }

    /**
     * Show different screen configurations to the user and let him choose the
     * best one, fitting to its screen configuration. The calibration shall
     * render the gui based on the 98 DPI, 144 DPI and 192 DPI. These DPIs are
     * used by most of the screens.
     */
    private void previewScreenCalibration(int targetDPI, boolean showMessage) {

        adjustWindowDimensions(getExtendedState(), getAvailableScreenSize(targetDPI));

        if (showMessage) {
            Messages.showOkMessage(this, "This is " + targetDPI + " DPI!", targetDPI + " DPI");
        }
    }

    /**
     * Compute the real available screen size, based on DPIs.
     *
     * @param targetDPI the DPI wanted by the user
     * @return the real screen dimension, based on the input DPI.
     */
    public Dimension getAvailableScreenSize(int targetDPI) {
        int realWidth = scalePXonDPI(targetDPI, defaultDPIValue, screenRes.getScreenWidth());
        int realHeight = scalePXonDPI(targetDPI, defaultDPIValue, screenRes.getScreenHeight());

        log.info("real size {} DPI: {}x{}", targetDPI, realWidth, realHeight);

        return new Dimension(realWidth, realHeight);
    }

    /**
     * Scale the given pixel value, based on the default and target values of
     * the DPI.
     *
     * @param targetDPI the used DPI for the application
     * @param defaultDPI the default DPI of the screen
     * @param pxValue the value to be transformed from default DPI screen size
     * to target DPI screen size
     * @return
     */
    private static int scalePXonDPI(int targetDPI, int defaultDPI, int pxValue) {
        // compute how many inches has the screen, based on its default DPI
        float inchValue = pxValue / (float) defaultDPI;

        // compute how many pixels are in the target DPI
        int supposedPXValue = (int) (inchValue * targetDPI);

        // get the resize factor between the wanted and the actual values
        float resizeFactor = supposedPXValue / (float) pxValue;

        // rescale the actual value with the rescale factor in order to get the real value, in target DPI system
        return ((int) (pxValue / resizeFactor));
    }

    /**
     * Compute the amount of space left, after drawing all the components, for
     * the image.
     *
     * @param usedResolution the resolution based on which the computation is
     * done
     * @return the maximum size on which the image to be labeled can be
     * displayed without affecting other gui components
     */
    private Dimension computeDrawingSize(Dimension usedResolution) {
        // get the width of the panels from the left and right of the drawing panel
        int resultImgWidth = Math.max(jPViewResults.getPreferredSize().width, jPViewResults.getWidth());
        int segmentOptWidth = Math.max(jPImgOpt.getPreferredSize().width, jPImgOpt.getWidth());

        // get the heights of the panels from the top and bottom of the drawing panel
        int videoNavigHeight = Math.max(jPNavigation.getPreferredSize().height, jPNavigation.getHeight());
        int annotOptHeight = Math.max(jPOptions.getPreferredSize().height, jPOptions.getHeight());
        int tabHeaderHeight = jTPLabelingOptions.getHeight() - jPLabeling.getHeight();
        int menuHeight = jMBMenu.getHeight();

        // compute the width and heights of the panels surrounding the drawing panel
        int widthPanels = segmentOptWidth + resultImgWidth;
        int heightPanels = videoNavigHeight + annotOptHeight + tabHeaderHeight + 2 * menuHeight; // consider 2 times the size of the menu bar due to the title bar which has similar height

        // based on the screen resolution, compute how mush space is left for the image to be displayed
        int maxWidth = usedResolution.width - widthPanels;
        int maxHeight = usedResolution.height - heightPanels;

        // allow some space for insets, OS toolbar etc.
        Dimension resolution = new Dimension((int) (maxWidth - (maxWidth * Constants.TOLERANCE_WIDTH)), (int) (maxHeight - (maxHeight * Constants.TOLERANCE_HEIGHT)));

        log.info("**hight: {}, {}, {}, {}", videoNavigHeight, annotOptHeight, tabHeaderHeight, menuHeight);
        log.info("**resolutions: {}x{}, width panels: {}, height panels: {}", maxWidth, maxHeight, widthPanels, heightPanels);
        log.info("**max resolution: {}x{}", resolution.width, resolution.height);

        return resolution;
    }

    /**
     * Adjust the size of the screen and its components in such way as to have
     * them all fitting to the screen.
     *
     * @param windowState the state of the window:<br>
     * <ul>
     * <li><code>NORMAL</code>
     * <br>Indicates that no state bits are set.
     * <li><code>ICONIFIED</code>
     * <li><code>MAXIMIZED_HORIZ</code>
     * <li><code>MAXIMIZED_VERT</code>
     * <li><code>MAXIMIZED_BOTH</code> - Concatenates
     * <code>MAXIMIZED_HORIZ</code> and <code>MAXIMIZED_VERT</code>.
     * </ul>
     */
    private void adjustWindowDimensions(int windowState, Dimension usedResolution) {
        // do not change anything if the application is in iconified state
        if (windowState == Frame.ICONIFIED) {
            return;
        }

        // when the drawing panel is valid, compute the
        if (gc.getdPImgToLabel() != null) {
            // compute the drawing size based on the gui components
            gc.setAvailableDrawSize(computeDrawingSize(usedResolution));

            // refresh the drawing panel and its image
            gc.refreshImage();

            // rearrange the view panel, to fit the new drawing panel size
            redrawViewPanel();
        }

        if (windowState == Frame.NORMAL) {
            // if the window is not maximized, pack the window and set a new min size
            packGUI();
        }
    }

    /**
     * Pack the window and set a new min size in order to avoid the resize to a
     * value where the components do no longer fit.
     */
    private void packGUI() {
        // avoid a min size greater than the new arrangement of panel size
        this.setMinimumSize(new Dimension(0, 0));

        this.pack();

        // make the screen the minimum size when the components still fit
        this.setMinimumSize(this.getSize());
    }

    /**
     * Ask the user to save the work; remove all the objects and all the
     * overlays. Cleans the display and the list of objects.
     *
     * @return an error/interruption code showing if there was something wrong
     * in the saving of data process or all went smooth
     */
    private Constants.GTSaveInterruptCodes removeObjsOverlays() {
        // ask user to save work
        if (Constants.GTSaveInterruptCodes.FRAME_ATTRIB_NOT_SET == saveCurrentWork()) {
            // the frame attributes are not correct and the user wants to fix them
            return Constants.GTSaveInterruptCodes.FRAME_ATTRIB_NOT_SET;
        }

        // remove objects
        gc.removeAllObjects();

        // refresh the display list (with nothing) in order to remove the overlays
        gc.refreshDisplayList();

        // everything seems fine
        return Constants.GTSaveInterruptCodes.NO_ERROR;
    }

    /**
     * Get the segmented image (containing the bounding boxes, crops and object
     * outer bounds) and resize it to the specified target dimension.
     *
     * @return the image containing the object bounds (what the user is drawing
     * while segmenting), having the specified size
     */
    private BufferedImage getSegmentedImage() {
        return getSegmentedImage(new Resize(1.0, 1.0));
    }

    /**
     * Get the segmented image (containing the bounding boxes, crops and object
     * outer bounds) and resize it to the specified target dimension.
     *
     * @return the image containing the object bounds (what the user is drawing
     * while segmenting), having the specified size
     */
    private BufferedImage getSegmentedImage(Dimension targetDimension) {
        Dimension origImgDim = gc.getdPImgToLabel().getOrigImgSize();
        Resize resize = new Resize(origImgDim.width, origImgDim.height, targetDimension.width, targetDimension.height);
        return getSegmentedImage(resize);
    }

    /**
     * Get the segmented image (containing the bounding boxes, crops and object
     * outer bounds).
     *
     * @param resize the resize ratio used for the drawing of the objects
     * @return the image containing the object bounds (what the user is drawing
     * while segmenting)
     */
    private BufferedImage getSegmentedImage(Resize resize) {
        Dimension origImgDim = gc.getdPImgToLabel().getOrigImgSize();

        BufferedImage segmentedImage = resize.resizeImage(gc.getdPImgToLabel().getOrigImg());

        Graphics2D g2d = segmentedImage.createGraphics();

        for (Objects object : gc.getObjectsList()) {
            if (object instanceof ObjectPolygon) {
                // draw the polygon edges and points
                drawPolygonObject(object, g2d, resize);
            } else {
                Color alphaColor = new Color(object.getColor().getRed(), object.getColor().getGreen(), object.getColor().getBlue(), userPrefs.getObjAlphaVal());
                // draw the outer box of the object
                DrawOptions.drawBBox(g2d, resize.originalToResized(object.getOuterBBox()), object.getColor(), userPrefs.isShowObjHighlight(), alphaColor, false);
            }

            // display the object id on top of the object
            DrawOptions.displayObjectId(g2d, resize.originalToResized(object.getOuterBBox()), Long.toString(object.getObjectId()), object.getColor(), resize.originalToResized(origImgDim));
        }

        g2d.dispose();

        return segmentedImage;
    }

    /**
     * Draw the polygon object: draw its edges and points.
     *
     * @param object the object for which the data should be extracted and drawn
     * @param g2d the graphics object
     * @param resize the resize ratio used for the drawing of the polygon
     */
    private void drawPolygonObject(Objects object, Graphics2D g2d, Resize resize) {
        Polygon polygon = resize.originalToResized(((ObjectPolygon) object).getPolygon());
        Color alphaColor = new Color(object.getColor().getRed(), object.getColor().getGreen(), object.getColor().getBlue(), userPrefs.getObjAlphaVal());
        DrawOptions.drawPolygon(g2d, polygon, object.getColor(), userPrefs.isShowObjHighlight(), alphaColor, false);
    }

    /**
     * Open the configuration window for the user to adjust preferences.
     *
     * @return the configuration window
     */
    private UserConfigs createConfigWindow() {
        // instantiate the config window
        UserConfigs configWin = new UserConfigs(this, true, userPrefs, defaultDPIValue, gc.isExistsScribbleObj());

        // add observer
        configWin.addObserver(this);

        return configWin;
    }

    /**
     * Export the user specified images for demo purposes.
     */
    private void autoExportImages() {
        // if the user does not want to save the images, stop here
        if (!userPrefs.isImgAutoExportEveryFrame()) {
            return;
        }

        // parse the types of images in order to export all of them
        String[] types = userPrefs.getImgTypeForExport().split(",");

        // make sure the path exists
        Utils.createFolderPath(userPrefs.getImgExportPath());

        for (String type : types) {
            switch (type) {
                case Constants.ORIGINAL_IMG:
                    ExportImage.exportImage(gc.getdPImgToLabel().getOrigImg(),
                            userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.ORIGINAL_IMG.split(" ")[0]),
                            userPrefs.getImgExportExtension());
                    break;

                case Constants.SEGMENTED_IMG:
                    ExportImage.exportImage(getSegmentedImage(),
                            userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.SEGMENTED_IMG.split(" ")[0]),
                            userPrefs.getImgExportExtension());
                    break;

                case Constants.SEMANTIC_SEGMENTATION_IMG:
                    ExportImage.exportImage(gc.getImageResult(),
                            userPrefs.getImgExportPath() + File.separator + createExportImgFileName(Constants.SEMANTIC_SEGMENTATION_IMG.split(" ")[0]),
                            userPrefs.getImgExportExtension());
                    break;

                case Constants.JOINED_IMGS:
                    exportJoinedImages();
                    break;

                default:
                    break;
            }
        }
    }

    /**
     * Show the Attributes edit window and allow the user to edit the attributes
     * of the object or frame.
     *
     * @param tabChoice the tab on which the dialog should open
     */
    private void displayAttributesEditWin(String tabChoice) {
        // create a new instance of the attributs definition window
        AttributesDefinition attributes = gc.editAttributes(this);

        // switch to the specified tab
        attributes.switchToTab(tabChoice);

        // show the frame
        attributes.setVisible(true);
    }

    /**
     * A class for implementing the behaviour of the buttons for the object
     * list. When a click happens on one of the buttons, the object should be
     * selected and the application should allow edit mode.
     */
    private class ObjListButtons implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            // get the text of the button
            String buttonName = e.getActionCommand();

            if (buttonName.startsWith("id:")) {
                // set to edit mode
                jRBEditMode.setSelected(true);
                setEditMode();

                // parse the text in order to get the id of the object
                long objId = Long.parseLong(buttonName.replace("id: ", ""));

                // select the object with the id
                String objType = gc.selectObject(objId);

                // select the button of the object
                selectObjectButton(objId);

                // enable the edit buttons (if they have to be disabled, they will be later on)
                enableEditButtons(true);

                // display the preview results panel if it is a scribble object
                if (objType.equals(ConstantsLabeling.LABEL_SCRIBBLE)) {
                    setShowSemanticOptions(true);

                    // if the scribble object is selected, disable some edit options
                    disableEditOptScribbleObj(gc.getCurrentObject());
                } else {
                    setShowSemanticOptions(gc.isExistsScribbleObj());
                }
            }
        }
    }

    /**
     * Select the pressed button representing an object. The background of the
     * object has to correspond to the labeling type (bbox, scribble etc.).
     */
    private void selectObjectButton(long objId) {
        java.awt.Color labelTypeColor;
        java.awt.Component[] components = jPObjList.getComponents();

        // enable the edit menu when an object is selected and disable it when not
        jMEditObject.setEnabled(objId > 0);

        // go over all the list components
        for (java.awt.Component comp : components) {

            // if the component is a panel, get the subcomponents
            if (!(comp instanceof JPanel)) {
                continue;
            }

            java.awt.Component[] subCompList = ((JPanel) comp).getComponents();

            // parse every panel and get the button, in order to highlight it
            for (java.awt.Component subComp : subCompList) {

                if (!(subComp instanceof CustomColorButton)) {
                    continue;
                }

                if (((CustomColorButton) subComp).getText().equals("id: " + objId)) {
                    // get the color of the object based on the labeling type: scribble, bbox
                    labelTypeColor = gc.getLabelTypeColor(objId);

                    ((CustomColorButton) subComp).setBkgColor(labelTypeColor.darker());
                } else if (((CustomColorButton) subComp).getText().startsWith("id:")) {
                    // parse the text in order to get the id of the object
                    long id = Long.parseLong(((CustomColorButton) subComp).getText().replace("id: ", ""));

                    // get the color of the object based on the labeling type: scribble, bbox
                    labelTypeColor = gc.getLabelTypeColor(id);

                    ((CustomColorButton) subComp).setBkgColor(labelTypeColor);
                }
            }
        }
        jPObjList.repaint();
    }

    /**
     * The key event dispatcher for listening the keys and the implementation of
     * the actions for some defined keys.
     */
    private class GUIKeyEventDispatcher implements KeyEventDispatcher {

        /**
         * Check if there is another window opened on top of the current one.
         *
         * @return - true if the current frame is the focus owner
         */
        private boolean frameIsFocusOwner() {
            return (GUILabelingTool.this.getFocusOwner() != null);
        }

        /**
         * Check if the log panel is the focus owner.
         *
         * @return - true if the log panel is the focus owner
         */
        private boolean logIsFocusOwner() {
            Component focusOwner = GUILabelingTool.this.getFocusOwner();

            return (jTALog == focusOwner) || (jSPLog == focusOwner);
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            if (frameIsFocusOwner() && (!logIsFocusOwner())) {

                if (gc.getdPImgToLabel() == null) {
                    return false;
                }

                // general implementation of keys avaliable all the time in the application
                dispatchKeyGeneral(e);
            }
            return false;
        }

        /**
         * Handles the keys which have the same functionality for all the types
         * of labeling.
         */
        private void dispatchKeyGeneral(KeyEvent e) {
            int eventId = e.getID();
            int key = e.getKeyCode();

            // the mapping of the keys is based on the default assignment of keys  for UGEE graphic tablet
            if (eventId == KeyEvent.KEY_PRESSED) {
                if (e.isControlDown()) {
                    handleCtrlKey(key);
                } else if (e.isAltDown()) {
                    handleAltKey(key);
                } else {
                    handleNormalKey(key);
                }
            }
        }

        /**
         * Handling of the keyboard events when Control key is pressed.
         *
         * @param key the key code
         */
        private void handleCtrlKey(int key) {
            switch (key) {
                case KeyEvent.VK_PLUS:
                    log.info("Key pressed: Ctrl NumPad+ =  Zoom in");
                    break;

                case KeyEvent.VK_MINUS:
                    log.info("Key pressed: Ctrl NumPad- =  Zoom out");
                    break;

                case KeyEvent.VK_A:
                    // decrease left
                    gc.changeSizeSelection(1, 0, 0, 0);
                    break;

                case KeyEvent.VK_W:
                    // decrease top
                    gc.changeSizeSelection(0, 1, 0, 0);
                    break;

                case KeyEvent.VK_D:
                    // decrease right
                    gc.changeSizeSelection(0, 0, -1, 0);
                    break;

                case KeyEvent.VK_S:
                    // decrease bottom
                    gc.changeSizeSelection(0, 0, 0, -1);
                    break;

                default:
                    // do nothing
                    break;
            }
        }

        /**
         * Handling of keyboard events when Alt key is pressed.
         *
         * @param key the key code
         */
        private void handleAltKey(int key) {
            switch (key) {
                case KeyEvent.VK_A:
                    // increase left
                    gc.changeSizeSelection(-1, 0, 0, 0);
                    break;

                case KeyEvent.VK_W:
                    // increase top
                    gc.changeSizeSelection(0, -1, 0, 0);
                    break;

                case KeyEvent.VK_D:
                    // increase right
                    gc.changeSizeSelection(0, 0, 1, 0);
                    break;

                case KeyEvent.VK_S:
                    // increase bottom
                    gc.changeSizeSelection(0, 0, 0, 1);
                    break;

                case KeyEvent.VK_Z:
                    // decrease box
                    gc.changeSizeSelection(1, 1, -1, -1);
                    break;

                case KeyEvent.VK_X:
                    // increase box
                    gc.changeSizeSelection(-1, -1, 1, 1);
                    break;

                default:
                    // do nothing
                    break;
            }
        }

        /**
         * Handling of the keyboard events.
         *
         * @param key the key code
         */
        private void handleNormalKey(int key) {
            switch (key) {
                case KeyEvent.VK_LEFT:
                    if (!jTFJumpFrame.isFocusOwner()) {
                        getFrame(Constants.PREV_FRAME);
                    }
                    break;

                case KeyEvent.VK_RIGHT:
                    if (!jTFJumpFrame.isFocusOwner()) {
                        // get the next frame; save the data and load other data if there exists any
                        getFrame(Constants.NEXT_FRAME);
                    }
                    break;

                case KeyEvent.VK_A:
                    // move the crop to the left                            
                    gc.moveSelection(-1, 0);
                    break;

                case KeyEvent.VK_D:
                    // move the crop to the right
                    gc.moveSelection(1, 0);
                    break;

                case KeyEvent.VK_W:
                    // move the crop up
                    gc.moveSelection(0, -1);
                    break;

                case KeyEvent.VK_S:
                    // move the crop down
                    gc.moveSelection(0, 1);
                    break;

                case KeyEvent.VK_DELETE:
                    gc.removeSelection();
                    break;

                case KeyEvent.VK_ESCAPE:
                    cancelCurrentObject();
                    break;

                default:
                    // do nothing
                    break;
            }
        }
    }

    /**
     * Cancel the current object and remove its selection from the interfaces.
     */
    private void cancelCurrentObject() {
        if (gc.getCurrentObject() instanceof ObjectBBox) {
            cancelBBoxObject();
        } else if (gc.getCurrentObject() instanceof ObjectScribble) {
            cancelScribbleObject();
        } else if (gc.getCurrentObject() instanceof ObjectPolygon) {
            cancelPolygonObject();
        }

        gc.resetSelectedObject();
        selectObjectButton(0L);
    }

    /**
     * Implement the cancel of a bounding box object.
     */
    private void cancelBBoxObject() {
        gc.cancelCurrentObject();
    }

    /**
     * Implement the cancel of a scribble object.
     */
    private void cancelScribbleObject() {
        // remove the object from the object map if the obj is scribble
        if (gc.getCurrentObject() != null) {
            gc.cancelObjectMap(gc.getCurrentObject(), gc.getCurrentObject().getOuterBBox());
        }

        if (gc.cancelCurrentObject()) {
            jBNewScribbleObj.setText(NEW_OBJ_BUTTON_TEXT);

            // no drawing can be done while there is no new object
            gc.setLabelingType(jRBScribble.isSelected() ? DrawConstants.DrawType.DO_NOT_DRAW : DrawConstants.DrawType.EDIT_MODE);
        }
    }

    /**
     * Implement the cancel of a polygon object.
     */
    private void cancelPolygonObject() {
        gc.cancelCurrentObject();

        jBNewPolygonObj.setText(NEW_OBJ_BUTTON_TEXT);

        // no drawing can be done while there is no new object
        gc.setLabelingType(jRBPolygon.isSelected() ? DrawConstants.DrawType.DO_NOT_DRAW : DrawConstants.DrawType.EDIT_MODE);
    }

    /**
     * Show/Hide options related to the semantic segmentation: result panel,
     * export semantic image etc.
     *
     * @param visible true if the components should be visible; false otherwise
     */
    private void setShowSemanticOptions(boolean visible) {
        jPPrevResultImg.setVisible(visible);
        jMIExportSemanticImg.setEnabled(visible);
    }

    /**
     * Update all the graphical components related to the video player.
     */
    private void updateGUIVideo() {
        long frame;

        frame = gc.getFrameNo();

        jLFrameNo.setText("F: " + gc.getFrameNo());
        jSVideoPlayer.setValue((int) frame);
    }

    /**
     * Initialise the characteristics of the video slider based on the loaded
     * video.
     */
    private void initVideoSlider() {
        jSVideoPlayer.setMinimum(1);    //1 is the first frame
        jSVideoPlayer.setMaximum((int) gc.getNoFrames());
        jSVideoPlayer.setValue((int) gc.getFrameNo());
    }

    /**
     * Return true if the application is running online.
     *
     * @return true if the application is online; false if not
     */
    public boolean isRunningOnline() {
        return runningOnline;
    }

    /**
     * Allow the user to select which two images he wants to export: original,
     * segmented, result.
     */
    private void exportJoinedImages() {
        String[] joinedTypes = userPrefs.getImgTypeJoinedExport().split(",");

        // stop if the string is not correctly specified
        if (joinedTypes.length != 2) {
            log.error("Auto export for joined images: the types string for left and right image has length {}!!", joinedTypes.length);
            return;
        }

        // get the left selection
        BufferedImage leftBI = selectImage(joinedTypes[0]);

        // get the left selection
        BufferedImage rigthBI = selectImage(joinedTypes[1]);

        String fileName = createExportImgFileName(joinedTypes[0].split(" ")[0] + "_" + joinedTypes[1].split(" ")[0]);

        // export the image
        Utils.exportTwoImages(leftBI, rigthBI, userPrefs.getImgExportPath() + File.separator + fileName, userPrefs.getImgExportExtension(), Color.white);

        // inform the user about the export
        log.info("Exported the joined images {}!", fileName);
    }

    /**
     * Create the name of the exported file like in the following template:<br>
     * inputFileName_frameNumber_exportType.extension<br>
     * where:<br>
     * inputFileName - the chosen file to be labeled<br>
     * frameNumber - the frame number and it applies just for videos<br>
     * export type - the type of exported image: original, joined etc.<br>
     * extension - the chosen file type to export images: png, jpeg, bmp
     *
     * @param exportName the name to be appended to the file name, defining the
     * type of export: original, segmented, original_segmented etc.
     * @return a string representing the name of the file, the one used to be
     * saved on the disk
     */
    private String createExportImgFileName(String exportName) {
        // create the name of the file being saved
        StringBuilder fileName = new StringBuilder();

        // get the name of the loaded file
        String name = Utils.getFileName(gc.getFileToLabelName(), false);

        fileName.append(name).append("_");

        // add the frame number if the file is a video
        if (Utils.isVideoFile(gc.getFileToLabelName())) {
            fileName.append(gc.getFrameNo()).append("_");
        }

        // add the name of the file types
        fileName.append(exportName);

        // add the point before the extension
        fileName.append(".");

        // add the file extension
        fileName.append(userPrefs.getImgExportExtension());

        return fileName.toString();
    }

    /**
     * Return the image specified by the input text.
     *
     * @param text the name of the image to be returned
     * @return the image corresponding to the input text
     */
    private BufferedImage selectImage(String text) {
        switch (text) {
            case Constants.ORIGINAL_IMG:
                return gc.getdPImgToLabel().getOrigImg();

            case Constants.SEGMENTED_IMG:
                return getSegmentedImage();

            case Constants.SEMANTIC_SEGMENTATION_IMG:
                return gc.getImageResult();

            default:
                return gc.getdPImgToLabel().getOrigImg();
        }
    }

    /**
     * The app starts with the panels for viewing the images filling the image.
     * This is no longer required after an image is added. The panel has to have
     * the same size as the image.
     */
    private void redrawViewPanel() {
        // remove and add again the drawing panel
        jPVideoPrev.remove(jPVImgToLabel);
        java.awt.GridBagConstraints gbc = new java.awt.GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.fill = java.awt.GridBagConstraints.NONE;
        gbc.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gbc.weightx = 0.01;
        gbc.weighty = 0.01;
        jPVideoPrev.add(jPVImgToLabel, gbc);

        // remove and add again the video preview panel
        jPView.remove(jPVideoPrev);
        gbc = new java.awt.GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.fill = java.awt.GridBagConstraints.NONE;
        gbc.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gbc.weightx = 0.0;
        gbc.weighty = 0.01;
        gbc.insets = new java.awt.Insets(10, 5, 5, 0);
        jPView.add(jPVideoPrev, gbc);

        // remove and add again the panel with the results
        jPView.remove(jPViewResults);
        gbc.gridx = 2;
        gbc.gridy = 0;
        gbc.fill = java.awt.GridBagConstraints.VERTICAL;
        gbc.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gbc.weightx = 0.5;
        gbc.weighty = 0.01;
        gbc.insets = new java.awt.Insets(5, 5, 5, 5);
        jPView.add(jPViewResults, gbc);

        jPView.revalidate();
    }

    /**
     * Trigger the steps for configuring the application in such way as to be
     * usable (image size etc.)
     */
    public void firstStartInitialization() {
        if (userPrefs.isFirstRun()) {
            // open the config window in a "first run" mode
            UserConfigs configWin = createConfigWindow();

            // for the case of the first run, add some special configuration to the config window
            if (userPrefs.isFirstRun()) {
                configWin.setConfigFirstRun();
                // disable the first run because it is no longer needed
                userPrefs.setFirstRun(false);
            }
            // show the config window
            configWin.setVisible(true);

            // open the attributes configuration window
            gc.editAttributes(this);
        }
    }

    /**
     * Set the icons for the menu items which have one.
     */
    private void setMenuItemsIcons() {
        jMILoadFile.setIcon(Icons.FOLDER_ICON_16X16);
        jMIExit.setIcon(Icons.EXIT_APPLICATION_ICON_16X16);

        jMINextFrame.setIcon(Icons.NEXT_ICON_16X16);
        jMIPrevFrame.setIcon(Icons.PREVIOUS_ICON_16X16);

        jMIUserConfig.setIcon(Icons.SETTINGS_ICON_16X16);
        jMIAttributes.setIcon(Icons.LIST_ICON_16X16);
        jMILanguage.setIcon(Icons.LANGUAGE_ICON_16X16);

        jMIExportOrigImg.setIcon(Icons.EXPORT_IMAGE_ICON_16X16);
        jMIExportWorkingImg.setIcon(Icons.EXPORT_IMAGE_ICON_16X16);
        jMIExportSemanticImg.setIcon(Icons.EXPORT_IMAGE_ICON_16X16);
        jMIExportJoinedImg.setIcon(Icons.EXPORT_IMAGE_ICON_16X16);
        jMIExportConfig.setIcon(Icons.SETTINGS_ICON_16X16);

        jMIAbout.setIcon(Icons.ABOUT_ICON_16X16);
        jMIHotkeys.setIcon(Icons.KEY_ICON_16X16);
        jMIManualApp.setIcon(Icons.HELP_BOOK_ICON_16X16);
    }

    /**
     * The entry point of application.
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(GUILabelingTool.class);
        System.getProperty("java.library.path");

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                //  SplashScreen.startSplash()

                GUILabelingTool gui = new GUILabelingTool(logger);
                gui.setVisible(true);
                // for the first run, some special configuration have to be done
                gui.firstStartInitialization();

            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                logger.error("Create and display form");
                logger.debug("Create and display form {}", ex);
            }
        });
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.ButtonGroup jBGLabelType;
    private javax.swing.JButton jBJumpFrame;
    private javax.swing.JButton jBLoadFile;
    private javax.swing.JButton jBNewPolygonObj;
    private javax.swing.JButton jBNewScribbleObj;
    private javax.swing.JButton jBNextFrame;
    private javax.swing.JButton jBPlay;
    private javax.swing.JButton jBPrevFrame;
    private javax.swing.JComboBox<String> jCBCountry;
    private javax.swing.JCheckBox jCBDirtVisible;
    private javax.swing.JCheckBox jCBFlipVerticallyImg;
    private javax.swing.JComboBox<String> jCBIllumination;
    private javax.swing.JCheckBox jCBImageDistorted;
    private javax.swing.JCheckBox jCBMirrorImg;
    private javax.swing.JCheckBox jCBPlayBackward;
    private javax.swing.JComboBox<String> jCBRoadEvent;
    private javax.swing.JComboBox<String> jCBRoadType;
    private javax.swing.JCheckBox jCBShowCurrentObj;
    private javax.swing.JComboBox<String> jCBWeather;
    private javax.swing.JCheckBox jCBWipersVisible;
    private javax.swing.JLabel jLAlignNorth;
    private javax.swing.JLabel jLCountry;
    private javax.swing.JLabel jLFrameNo;
    private javax.swing.JLabel jLIllumination;
    private javax.swing.JLabel jLJumpFrame;
    private javax.swing.JLabel jLNoBBoxObjs;
    private javax.swing.JLabel jLNoBBoxObjsText;
    private javax.swing.JLabel jLNoFrames;
    private javax.swing.JLabel jLNoPolygonObjs;
    private javax.swing.JLabel jLNoPolygonObjsText;
    private javax.swing.JLabel jLNoScribbleObjs;
    private javax.swing.JLabel jLNoScribbleObjsText;
    private javax.swing.JLabel jLRoadEvent;
    private javax.swing.JLabel jLRoadType;
    private javax.swing.JLabel jLTotalNoObjs;
    private javax.swing.JLabel jLTotalNoObjsText;
    private javax.swing.JLabel jLWeather;
    private javax.swing.JMenuBar jMBMenu;
    private javax.swing.JMenu jMEditObject;
    private javax.swing.JMenu jMExport;
    private javax.swing.JMenu jMFile;
    private javax.swing.JMenu jMHelp;
    private javax.swing.JMenuItem jMIAbout;
    private javax.swing.JMenuItem jMIAttributes;
    private javax.swing.JMenuItem jMICancelObject;
    private javax.swing.JMenuItem jMIDecreaseBottom;
    private javax.swing.JMenuItem jMIDecreaseBox;
    private javax.swing.JMenuItem jMIDecreaseLeft;
    private javax.swing.JMenuItem jMIDecreaseRight;
    private javax.swing.JMenuItem jMIDecreaseTop;
    private javax.swing.JMenuItem jMIDeleteObject;
    private javax.swing.JMenuItem jMIExit;
    private javax.swing.JMenuItem jMIExportConfig;
    private javax.swing.JMenuItem jMIExportJoinedImg;
    private javax.swing.JMenuItem jMIExportOrigImg;
    private javax.swing.JMenuItem jMIExportSemanticImg;
    private javax.swing.JMenuItem jMIExportWorkingImg;
    private javax.swing.JMenuItem jMIHotkeys;
    private javax.swing.JMenuItem jMIIncreaseBottom;
    private javax.swing.JMenuItem jMIIncreaseBox;
    private javax.swing.JMenuItem jMIIncreaseLeft;
    private javax.swing.JMenuItem jMIIncreaseRight;
    private javax.swing.JMenuItem jMIIncreaseTop;
    private javax.swing.JMenuItem jMILanguage;
    private javax.swing.JMenuItem jMILoadFile;
    private javax.swing.JMenuItem jMIManualApp;
    private javax.swing.JMenuItem jMIMoveDown;
    private javax.swing.JMenuItem jMIMoveLeft;
    private javax.swing.JMenuItem jMIMoveRight;
    private javax.swing.JMenuItem jMIMoveUp;
    private javax.swing.JMenuItem jMINextFrame;
    private javax.swing.JMenuItem jMIPrevFrame;
    private javax.swing.JMenuItem jMIUserConfig;
    private javax.swing.JMenu jMNavigation;
    private javax.swing.JMenu jMOptions;
    private javax.swing.JPanel jPBackground;
    private javax.swing.JPanel jPCropOpt;
    private javax.swing.JPanel jPDevelopment;
    private javax.swing.JPanel jPImgOpt;
    private javax.swing.JPanel jPLabeling;
    private javax.swing.JPanel jPLoadSource;
    private javax.swing.JPanel jPLog;
    private javax.swing.JPanel jPNavigation;
    private javax.swing.JPanel jPObjInfo;
    private javax.swing.JPanel jPObjList;
    private javax.swing.JPanel jPOptions;
    private javax.swing.JPanel jPPrevResultImg;
    private javax.swing.JPanel jPSceneDescription;
    private javax.swing.JPanel jPVImgToLabel;
    private javax.swing.JPanel jPVLabelType;
    private javax.swing.JPanel jPVideoPrev;
    private javax.swing.JPanel jPView;
    private javax.swing.JPanel jPViewResults;
    private javax.swing.JPanel jPViewResultsImg;
    private javax.swing.JRadioButton jRBBoundingBox;
    private javax.swing.JRadioButton jRBEditMode;
    private javax.swing.JRadioButton jRBPolygon;
    private javax.swing.JRadioButton jRBScribble;
    private javax.swing.JPopupMenu.Separator jSAttribDef;
    private javax.swing.JScrollPane jSPLog;
    private javax.swing.JScrollPane jSPObjList;
    private javax.swing.JScrollPane jSPShortLog;
    private javax.swing.JSlider jSVideoPlayer;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    private javax.swing.JPopupMenu.Separator jSeparator2;
    private javax.swing.JPopupMenu.Separator jSeparator3;
    private javax.swing.JPopupMenu.Separator jSeparator4;
    private javax.swing.JPopupMenu.Separator jSeparator5;
    private javax.swing.JPopupMenu.Separator jSeparator7;
    private javax.swing.JTextArea jTALog;
    private javax.swing.JTextArea jTAShortLog;
    private javax.swing.JTextField jTFJumpFrame;
    private javax.swing.JTabbedPane jTPLabelingOptions;
    // End of variables declaration//GEN-END:variables
} public class NotifyObservers extends Observable {

    @Override
    public void notifyObservers() {
        setChanged();
        super.notifyObservers();
        clearChanged();
    }

    @Override
    public void notifyObservers(Object arg) {
        setChanged();
        super.notifyObservers(arg);
        clearChanged();
    }

} public class ObservedActions {

    /**
     * The enum Action.
     */
    public enum Action {
        /**
         * Do nothing.
         */
        DO_NOTHING,
        /**
         * A notification saying that a new crop was drawn and it has to be
         * displayed
         */
        OPEN_CROP_SEGMENTATION,
        /**
         * A bounding box was drawn and an window with the preview has to pop up
         * for further checks and attributes choosing.
         */
        OPEN_BBOX_SEGMENTATION,
        /**
         * A polygon was drawn and an window with the preview has to pop up for
         * further checks and attributes choosing.
         */
        OPEN_POLYGON_SEGMENTATION,
        /**
         * The attributes of the bounding box were chosen and the object has to
         * be saved in the object list.
         */
        SAVE_BOUNDING_BOX,
        /**
         * A new crop was segmented, which belongs to an object. Add it to the
         * parent object.
         */
        ADD_CROP_TO_OBJECT,
        /**
         * Notify that an object was saved and it has to be displayed on the
         * panel of the application.
         */
        ADD_OBJECT_ON_PANEL,
        /**
         * The segmentation for the opened crop was done and the result image is
         * available. Refresh the display in order to have the latest image.
         */
        REFRESH_CROP_RESULT,
        /**
         * The number of the frame changed, refresh the display.
         */
        REFRESH_FRAME_NO,
        /**
         * The size of the brush changed. Refresh the display.
         */
        REFRESH_BRUSH_SIZE,
        /**
         * An object has been removed from the list of objects; remove it from
         * the objects list as well.
         */
        REFRESH_OBJ_LIST_PANEL,
        /**
         * Remove the key event dispatcher because another one has to be used.
         */
        REMOVE_GUI_KEY_EVENT_DISPATCHER,
        /**
         * Remove the object, one crop of the object or the bounding box object.
         */
        REMOVE_SELECTED_OBJECT,
        /**
         * A crop was selected for edit. Notify in order to open a window and
         * edit the crop.
         */
        EDIT_OBJECT_ACTION,
        /**
         * A bounding box object is/was in edit mode and has to be saved.
         */
        UPDATE_BOUNDING_BOX,
        /**
         * A complete scribble object is/was in edit mode and it has to be
         * saved.
         */
        UPDATE_OBJECT_SCRIBBLE,
        /**
         * A crop was edited and now it has to be updated inside the parent
         * object.
         */
        UPDATE_CROP_OF_OBJECT,
        /**
         * A polygon object was edited and now it has to be updated.
         */
        UPDATE_POLYGON,
        /**
         * Add the key event dispatcher.
         */
        ADD_GUI_KEY_EVENT_DISPATCHER,
        /**
         * An object which is under edit mode moved and the operation has to be
         * reflected in the GUI.
         */
        SELECTED_OBJECT_MOVED,
        /**
         * One object was selected and now it is dragged/moved.
         */
        MOVE_OBJECT_DRAG,
        /**
         * An object was selected and the corresponding button on the interface
         * has to be highlighted.
         */
        HIGHLIGHT_OBJECT,
        /**
         * A frame was loaded from the database and its annotations have to be
         * displayed on the gui.
         */
        LOAD_FRAME_ANNOTATION,
        /**
         * Notify that the current object was cancel.
         */
        CANCEL_CURRENT_OBJECT,
        /**
         * Shows that the output object map shall be further filter because
         * there are outliers.
         */
        FILTER_OBJECT_MAP,
        /**
         * The Ctrl + mouse released event happened. Choose how to treat it.
         */
        CTRL_MOUSE_EVENT,
        /**
         * Refresh the application with the current configuration.
         */
        REFRESH_DISPLAY,
        /**
         * There were some changes and the panels should be updated; show/hide
         * some panels based on the existence of a scribble object in the list.
         */
        REFRESH_PANEL_OPTIONS,
        /**
         * The object has to be saved and sent to the ground truth manager.
         */
        SAVE_LABEL,
        /**
         * The object or frame attributes were changed. Reload them.
         */
        RELOAD_ATTRIBUTES,
        /**
         * The user wants to see a preview of the application with another DPI
         * value.
         */
        REFRESH_APPLICATION_VIEW,
        /**
         * The window for the edit of attributes should be shown.
         */
        DISPLAY_EDIT_ATTRIBUTES_WIN,
        /**
         * The vertices of the polygon were changed and the original object has
         * to be updated.
         */
        UPDATE_POLYGON_VERTICES
    }
} public class DrawConstants {

    /**
     * Definition of the types of drawing the draw panel supports.
     */
    public enum DrawType {
        /**
         * Do not draw draw type.
         */
        DO_NOT_DRAW,        // panel used just to show image, not to draw
        /**
         * Draw point draw type.
         */
        DRAW_POINT,         // draw points on the panel
        /**
         * Draw line draw type.
         */
        DRAW_LINE,          // draw lines on the panel
        /**
         * Draw bounding box draw type.
         */
        DRAW_BOUNDING_BOX,  // draw bounding boxes on the panel
        /**
         * Draw scribble draw type.
         */
        DRAW_SCRIBBLE,      // draw scribbles on the panel
        /**
         * Draw crop draw type.
         */
        DRAW_CROP,          // crop image
        /**
         * Draw polygon draw type.
         */
        DRAW_POLYGON,       // draw a polygon made of n vertices
	/**
         * Switch to the edit polygon mode.
         */
        EDIT_POLYGON_VERTICES,
        /**
         * The Edit mode.
         */
        EDIT_MODE           // allow the user to edit the crops which were drawen at the previous step
    }
    
    /**
     * Utility classes, which are collections of static members, are not meant
     * to be instantiated. Even abstract utility classes, which can be extended,
     * should not have public constructors.
     *
     * Java adds an implicit public constructor to every class which does not
     * define at least one explicitly. Hence, at least one non-public
     * constructor should be defined.
     */
    private DrawConstants() {
        throw new IllegalStateException("Utility class DrawConstants! Do not instantiate!");
    }

} public final class DrawingPanel extends JPanel {

    /**
     * The original image loaded by the user.
     */
    private transient BufferedImage origImg;

    /**
     * The image drawn on the panel
     */
    private transient BufferedImage workImg;

    /**
     * The dimension of the panel
     */
    private Dimension panelSize;

    /**
     * Old position of the mouse in panel coordinates - dependent on click
     * action.
     */
    private Point oldMouse = new Point();

    /**
     * Current mouse position in panel coordinates - dependent on click action.
     */
    private Point currentMouse = new Point();

    /**
     * Previous mouse position in panel coordinates - dependent on click action.
     */
    private final Point prevMouse = new Point();

    /**
     * Shows whether the guide shape shall be drawn or not (the red rectangle
     * for object segmentation).
     */
    private boolean drawGuideShape;
    /**
     * The position of the mouse in the panel - where the mouse moves, without
     * click constrains.
     */
    private final Point mousePosition = new Point();

    /**
     * The type of drawing which is chosen by the user: bounding box, scribble,
     * line etc.
     */
    private DrawConstants.DrawType drawType;

    /**
     * The possible actions: -1 = erase scribbles; 0 = draw background; 1 = draw
     * object
     */
    private int actionType;

    /**
     * Keeps the user defined settings of the scribble.
     */
    private transient BrushOptions brushOpt = new BrushOptions();

    /**
     * Marks the points which were visited/segmented. For the scribble drawing
     * it is needed to know which points were already marked to avoid marking
     * them again or changing their initial value.
     */
    private boolean[][] visited;

    /**
     * The list of segmented points. It is kept for being able to save the
     * information regarding the segmented point and the object ids of each
     * point.
     */
    private List<ScribbleInfo> scribbleList;

    /**
     * The list of points to be displayed (no other purpose, just display). It
     * represents the list of scribbles which were drawn on the crops. point.
     */
    private transient List<DisplayScribbles> displayScribbles;

    /**
     * The list of segmented pixels as required by the cuda module. It contains
     * the list of pixels which represent scribbles.
     */
    private transient List<PixelMap>[] cudaPixelMap;

    /**
     * The ratio between the original image and the size it has to have to fit
     * in the image.
     */
    private transient Resize resize;

    /**
     * Stores the current bounding box drawn by the labeler.
     */
    private Rectangle curBBoxPanelCoord;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    private final transient NotifyObservers observable = new NotifyObservers();

    /**
     * Used to check the timings needed to run different peaces of code. Used
     * also for counting the needed time to label using bounding boxes.
     */
    private final transient Timings times = new Timings();

    /**
     * The label displaying the size of the crop.
     */
    private final JLabel jLBoxSize = new JLabel();

    /**
     * List of bounding boxes/crops to be displayed.
     */
    private transient List<DisplayBBox> bBoxList;

    /**
     * Enables the draw of the crops from the previous frame.
     */
    private boolean drawScribbleHistory;

    /**
     * Enables the draw of crop, to add it to an existent object, which is in
     * edit mode.
     */
    private boolean addCropToObj;

    /**
     * Keep the id of the box which should look selected when the user clicks on
     * it.
     */
    private int idSelectedBox;

    /**
     * The color of the object.
     */
    private Color objColor;

    /**
     * The current polygon being segmented  - panel coordinates.
     */
    private Polygon currentPolygon;
	
	/**
     * The current polygon being segmented - original coordinates. The double
     * data is kept in order to have a synchronization between the edit mode and
     * the GUI. The object on the GUI is updated automatically when the orignal
     * poly is modified on the drawing side.
     */
    private Polygon originalPolygon;

    /**
     * The list of polygon objects segmented, to be displayed.
     */
    private transient List<DisplayPolygon> polygonList;

    /**
     * Draw an alpha over the object for better visibility in the image.
     */
    private boolean drawAlphaObj;

    /**
     * The value of the alpha to be used for the drawing of the object
     * highlight.
     */
    private int objAlpha;

    /**
     * logger instance
     */
    private final transient Logger log = LoggerFactory.getLogger(DrawingPanel.class);

	/**
     * A vertex which was selected by the user.
     */
    private int selectedPolyIndex;

    /**
     * True if a point/points should be highlighted.
     */
    private boolean highlightPoints;
	
    /**
     * Load image when the image is given (used for crop).
     *
     * @param image    - the image to be displayed
     * @param panelRes - contains info related to the max resolution possible for the panel and allows the computation of a better resolution, based on the image to be displayed size
     * @param drawType - the current type of drawing for panel
     */
    public DrawingPanel(BufferedImage image, Dimension panelRes, DrawConstants.DrawType drawType) {
        this.origImg = image;
        this.drawType = drawType;

        this.panelSize = panelRes;

        // resize the image to the optimal size
        double ratioWidth = (double) origImg.getWidth() / (double) panelSize.width;
        double ratioHeight = (double) origImg.getHeight() / (double) panelSize.height;
        resize = new Resize(ratioWidth, ratioHeight);

        workImg = resize.resizeImage(origImg);

        log.info("**resized image {}x{}", workImg.getWidth(), workImg.getHeight());

        initPanel();
    }

	/**
     * Load image, with a user defined zoom, when the image is given (used for
     * edit mode).
     *
     * @param image - the image to be displayed
     * @param drawType - the current type of drawing for panel
     * @param userZoomedIndex user's prefer zoomed index
     */
    public DrawingPanel(BufferedImage image, DrawConstants.DrawType drawType, int userZoomedIndex) {
        this.origImg = image;
        this.drawType = drawType;

        resize = new Resize(origImg.getWidth(), origImg.getHeight(), userZoomedIndex);

        workImg = resize.resizeImage(origImg);

        this.panelSize = new Dimension(workImg.getWidth(), workImg.getHeight());

        log.info("**resized image {}x{}", workImg.getWidth(), workImg.getHeight());

        initPanel();
    }
	
    /**
     * Configure the basic characteristics of the panel.
     */
    private void initPanel() {
        // for any type of drawing, the mouse listener should be active
        addMouseListener();

        //characteristics of the panel
        this.setFocusable(true);

        setPanelFixedSize();

        this.repaint();
        this.setVisible(true);

        jLBoxSize.setOpaque(true);
        jLBoxSize.setForeground(Color.red);
        jLBoxSize.setBackground(new Color(0xFF, 0xF9, 0xCD, 180)); //red text + FEF9CD background - based on color contrast
        jLBoxSize.setVisible(false);
        add(jLBoxSize);

        // init the other variables
        initOtherVar();
    }

    /**
     * Initialise needed variable which were not initialised in the constructor.
     */
    private void initOtherVar() {
        // init the list of pixels which are drawn on the image
        scribbleList = new ArrayList<>();

        // init the list of pixels to be displayed in the panel
        displayScribbles = new ArrayList<>();

        // create a new map of the workImg to mark the visited points
        visited = new boolean[origImg.getHeight()][origImg.getWidth()];

        // set the id as invalid
        idSelectedBox = -1;
	resetPolygonIndex();

        // highlight points if any selected
        this.highlightPoints = true;
    }

    /**
     * The implementation of the mouse listener actions.
     */
    private void addMouseListener() {
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                mousePressedAction(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                mouseReleasedAction(e);
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                mouseEnteredAction(e);
            }

        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                mouseDraggedAction(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                mousePosition.setLocation(e.getX(), e.getY());
            }

        });
        
	addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                // highlight points if any selected
                highlightPoints = false;
            }
        });
    }

    /**
     * Implements the action which happens on mouse pressed action.
     *
     * @param e triggered mouse event
     */
    private void mousePressedAction(MouseEvent e) {
        // save coord x,y when mouse is pressed
        oldMouse.setLocation(e.getPoint());
        prevMouse.setLocation(e.getPoint());
        currentMouse.setLocation(e.getPoint());

        switch (drawType) {

            case DRAW_SCRIBBLE:
                // the erase object has id -1
                if (actionType > ConstantsLabeling.ACTION_TYPE_ERASE) {
                    generateScribble();
                } else {
                    eraseFromScribblelList(currentMouse);
                }
                break;

            case EDIT_MODE:
                // if edit mode and ctrl + mouse clicked => add crop to object
                if (((e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) || addCropToObj) {
                    // draw the shape of the object
                    drawGuideShape = true;

                } else {
                    // check if any object is selected
                    getSelectedBoxId();

                    // if double click, open the object in edit mode
                    if (e.getClickCount() == 2 && !e.isConsumed()) {
                        e.consume();
                        observable.notifyObservers(ObservedActions.Action.EDIT_OBJECT_ACTION);
                    }

                    // highlight the button of the selected object
                    observable.notifyObservers(ObservedActions.Action.HIGHLIGHT_OBJECT);
                }
                break;

            case DRAW_BOUNDING_BOX:
                // a mouse pressed event was notified and the timer for labeling has to start
                times.start();

            case DRAW_CROP:
                // draw the shape of the object
                drawGuideShape = true;
                break;

            case EDIT_POLYGON_VERTICES:
                switch (actionType) {
                    // edit vertex option
                    case ConstantsLabeling.ACTION_TYPE_BACKGROUND:
                        getClosestPolyPoint();
                        break;

                    case ConstantsLabeling.ACTION_TYPE_OBJECT:
                        // add vertex option
                        getClosestPolyLine();
                        splitPolyLine();
                        break;

                    case ConstantsLabeling.ACTION_TYPE_ERASE:
                        // remove vertex option
                        // on left click select a vertex
                        getClosestPolyPoint();

                        // on right click remove the vertex
                        if (SwingUtilities.isRightMouseButton(e)) {
                            // Right-click happened
                            removeSelectedPolyPoint();
                        }
                        break;

                    default:
                        break;
                }
                break;
            default:
                break;
        }
    }

    /**
     * Implements the action which happens on mouse released action.
     *
     * @param e triggered mouse event
     */
    private void mouseReleasedAction(MouseEvent e) {
        currentMouse.setLocation(e.getPoint());

        //notify the gui to update the cropped image
        switch (drawType) {
            case DRAW_CROP:
                saveCurrentBBox();
                observable.notifyObservers(ObservedActions.Action.OPEN_CROP_SEGMENTATION);
                break;

            case DRAW_BOUNDING_BOX:
                saveCurrentBBox();
                observable.notifyObservers(ObservedActions.Action.OPEN_BBOX_SEGMENTATION);
                break;

            case DRAW_POLYGON:
                currentPolygon.addPoint(currentMouse.x, currentMouse.y);
                break;

            case EDIT_MODE:
                // if edit mode and ctrl + mouse clicked => add crop to object
                if (((e.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) || addCropToObj) {
                    saveCurrentBBox();
                    observable.notifyObservers(ObservedActions.Action.CTRL_MOUSE_EVENT);

                    // deactivate the drawing features
                    addCropToObj = false;
                    drawGuideShape = false;
                    jLBoxSize.setVisible(drawGuideShape);
                }
                break;

		case EDIT_POLYGON_VERTICES:
                    observable.notifyObservers(ObservedActions.Action.UPDATE_POLYGON_VERTICES);
                break;
            default:
                break;
        }
    }

    /**
     * Implements the action which happens on mouse entered action.
     *
     * @param e triggered mouse event
     */
    private void mouseEnteredAction(MouseEvent e) {
        // change the mouse cursor when the drawing type is scribble
        if (drawType == DrawConstants.DrawType.DRAW_SCRIBBLE) {
            setCursor(Utils.generateCustomIcon(brushOpt, actionType, objColor));
        } else {
            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Implements the action which happens on mouse dragged action.
     *
     * @param e triggered mouse event
     */
    private void mouseDraggedAction(MouseEvent e) {
        prevMouse.setLocation(currentMouse.getLocation());

        // save coord x,y when mouse is dragged
        currentMouse.setLocation(e.getPoint());

                // implement the actions for mouse dragged and draw scribbles option
        draggDrawScribble();

        // implement the actions for mouse dragged and edit polygon
        draggPolyPoint();

        // if an object is selected and dragged, notify to move it
        if ((idSelectedBox > -1) && !drawGuideShape) {
            observable.notifyObservers(ObservedActions.Action.MOVE_OBJECT_DRAG);
        }
    }

    /**
     * Implement the functionality of the draw scribbles when the mouse is
     * dragged; draw or erase scribbles. If an object is chosen (background or
     * object), draw scribbles; else remove the selected scribbles.
     */
    private void draggDrawScribble() {
        // depending on the action, draw scribble or erase them
        if (drawType == DrawConstants.DrawType.DRAW_SCRIBBLE) {
            if (actionType > ConstantsLabeling.ACTION_TYPE_ERASE) {
                generateScribble();
            } else {
                eraseFromScribblelList(currentMouse);
            }

            oldMouse.setLocation(currentMouse.getLocation());
        }
    }

    /**
     * Edit the polygon points: allow the user to drag the points of the poly to the wanted position.
     */
    private void draggPolyPoint() {
        if (drawType == DrawConstants.DrawType.EDIT_POLYGON_VERTICES) {

            if ((ConstantsLabeling.ACTION_TYPE_BACKGROUND == actionType)
                    || (ConstantsLabeling.ACTION_TYPE_OBJECT == actionType)) {
                if ((-1 == selectedPolyIndex) || (0 == currentPolygon.npoints)) {
                    return;
                }
                // move the selected point with the mouse
                currentPolygon.xpoints[selectedPolyIndex] = currentMouse.x;
                currentPolygon.ypoints[selectedPolyIndex] = currentMouse.y;
            }
        }
    }

    // Override area
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2D = (Graphics2D) g;

        g2D.drawImage(workImg, 0, 0, panelSize.width, panelSize.height, this);          //draw image at coordinates (0,0)

        drawFigure(g2D);

        // draw the list of saved boxes
        drawBoxList(g2D);

        // draw the list of saved scribbles
        drawScribbleList(g2D);

        // draw the list of saved polygons
        drawPolygonList(g2D);

        // highlight the selected object
        highlightSelectedBox(g2D);
		
	// highlight the selected point of the polygon
        highlightPoint(g2D, selectedPolyIndex);									   
    }

    private void highlightSelectedBox(Graphics2D g2D) {
        if ((idSelectedBox > -1) && (!bBoxList.isEmpty()) && (idSelectedBox < bBoxList.size())) {
            g2D.setColor(new Color(0, 0, 0, 100));
            // if there is a selected box, draw it transparent
            g2D.fillRect(bBoxList.get(idSelectedBox).getPanelBox().x + 1,
                    bBoxList.get(idSelectedBox).getPanelBox().y + 1,
                    bBoxList.get(idSelectedBox).getPanelBox().width - 1,
                    bBoxList.get(idSelectedBox).getPanelBox().height - 1);
        }
    }

    /**
     * Draw a bounding box / rectangle for all the segmented/saved objects.
     */
    private void drawBBoxList(Graphics2D g2D) {
        // draw all the bounding boxes from the list
        bBoxList.stream().forEach(bBox
                -> DrawOptions.drawBBox(g2D, bBox.getPanelBox(), bBox.getColor(),
                        (drawAlphaObj && (!bBox.isUseDashedLine())), new Color(bBox.getColor().getRed(), bBox.getColor().getGreen(), bBox.getColor().getBlue(), objAlpha),
                        bBox.isUseDashedLine()));
    }

    /**
     * Draw the list of saved polygons.
     *
     * @param g2d the graphics object
     */
    private void drawPolygonObjList(Graphics2D g2D) {
        polygonList.stream().forEach(displayPoly -> {
            Color fillColor = new Color(displayPoly.getColor().getRed(), displayPoly.getColor().getGreen(), displayPoly.getColor().getBlue(), objAlpha);
            DrawOptions.drawPolygon(g2D, displayPoly.getPanelPolygon(), displayPoly.getColor(), drawAlphaObj, fillColor, false);
        });
    }

    /**
     * Draw the points existent in the list, using the defined color for each.
     */
    private void drawScribble(Graphics2D g2D) {
        for (ScribbleInfo si : scribbleList) {
            if ((Utils.checkBounds(si.getImgPos(), getOrigImgSize())) && (visited[si.getImgPosY()][si.getImgPosX()])) {
                DrawOptions.drawPoint(g2D, si.getPanelPos(), Utils.getDrawingColor(si.getDrawingType(), objColor));
            }
        }
    }

    /**
     * The method allows the user to draw freely the wanted shape. The shape is
     * saved and passed to another module which is processing the chosen points.
     */
    private void generateScribble() {

        //compute the workImg coordinates for the case when the panel is not the same size as the workImg
        Point currentImg = resize.resizedToOriginal(currentMouse);
        Point currentPanel = new Point(currentMouse.x, currentMouse.y);     //to avoid overriting all the other panel coordinates
        Point newImgPoint;
        Point newPanelPoint;
        int brushSizeImg = resize.resizedToOriginal(brushOpt.getBrushSize());
        int win2 = (int) (brushSizeImg * 0.5f);   //the window arround the point where the mouse is, where the brush will be created
        Random rand = new Random();
        byte[][] circleMask = Utils.getCircleMask(brushOpt.getBrushSize());  // the mask making the mouse pointer a circle

        //When the brush is theaker than 1 pixel, draw points as specified by the choosen options: brush size and density. 
        //When the brush has size 1 or 0, draw a line of 1 pixel.
        if (win2 > 0) {
            for (int x = -win2; x < brushSizeImg - win2; x++) {
                for (int y = -win2; y < brushSizeImg - win2; y++) {
                    newImgPoint = new Point(currentImg.x + x, currentImg.y + y);

                    float randFloat = rand.nextFloat();

                    //if the point is valid, not visited and chosen to be saved by the random generator, inside the circle brush, paint and save it
                    if (Utils.checkBounds(newImgPoint, getOrigImgSize())
                            && (randFloat < brushOpt.getBrushDensity())
                            && (circleMask[x + win2][y + win2] == (byte) 1)
                            && (!visited[newImgPoint.y][newImgPoint.x])) {
                        // mark the point as visited
                        visited[newImgPoint.y][newImgPoint.x] = true;

                        // compute the panel coordinates
                        newPanelPoint = resize.originalToResized(newImgPoint);

                        // add object in the list to be saved
                        addScribbleToList(newImgPoint, newPanelPoint);
                    }
                }
            }
        } else if (Utils.checkBounds(currentImg, getOrigImgSize()) && (!visited[currentImg.y][currentImg.x])) {
            // mark the point as visited
            visited[currentImg.y][currentImg.x] = true;

            // add object in the list to be saved
            addScribbleToList(currentImg, currentPanel);
        }
    }

    /**
     * Pack the image point and the panel point together with the object id and
     * add it to the list of pixels.
     */
    private void addScribbleToList(Point pointImg, Point pointPanel) {
        ScribbleInfo pi = new ScribbleInfo(actionType, pointImg, pointPanel);
        scribbleList.add(pi);
    }

    /**
     * The function does not erase the pixels, but marks them as free to be
     * chosen again.
     */
    private void eraseFromScribblelList(Point point) {
        //compute the workImg coordinates for the case when the panel is not the same size as the workImg
        Point currentImg = resize.resizedToOriginal(point);
        Point newImgPoint = new Point();

        byte[][] circleMask = Utils.getCircleMask(brushOpt.getBrushSize());  // the mask making the mouse pointer a circle

        int brushSizeImg = resize.resizedToOriginal(brushOpt.getBrushSize() - 1);
        int win2 = (int) (brushSizeImg * 0.5f);   //the window arround the point where the mouse is, where the brush will be created

        if (win2 > 0) {
            for (int x = -win2; x < win2; x++) {
                for (int y = -win2; y < win2; y++) {
                    // erase a circle of pixels, not a square
                    if (circleMask[x + win2][y + win2] == (byte) 1) {
                        // compute the position
                        newImgPoint.setLocation(currentImg.x + x, currentImg.y + y);

                        if (Utils.checkBounds(newImgPoint, getOrigImgSize())
                                && visited[newImgPoint.y][newImgPoint.x]) {
                            // make the point eligible again
                            visited[newImgPoint.y][newImgPoint.x] = false;
                        }
                    }
                }
            }
        }
    }

    /**
     * Write the list of selected pixels.
     *
     * @return - the number of scribbles which will be used for segmentation
     */
    public int flushPixelList() {
        int noScribbles = 0;

        // The map of objects to be saved - the not visited points have to have id -1.
        float[][] objMap = Utils.initMatrix(origImg.getWidth(), origImg.getHeight(), -1.0f);

        // create an empty array of arrays of points for the cuda module
        cudaPixelMap = new ArrayList[Constants.NUMBER_OF_OBJECTS];
        // init the array
        for (int index = 0; index < Constants.NUMBER_OF_OBJECTS; index++) {
            cudaPixelMap[index] = new ArrayList<>();
        }

        // get the list of segmented pixels and save only the valid and visited ones
        for (ScribbleInfo pi : scribbleList) {
            if (visited[pi.getImgPosY()][pi.getImgPosX()]) {
                objMap[pi.getImgPosY()][pi.getImgPosX()] = (float) pi.getDrawingType();
            }
        }

        // copy the whole list in the required format, line by line
        for (int y = 0; y < origImg.getHeight(); y++) {
            for (int x = 0; x < origImg.getWidth(); x++) {

                // fill in also the list of pixels for cuda
                if (objMap[y][x] > (-1)) {
                    // get the color of the pixel 
                    int[] RGB = Utils.getRGB(origImg.getRGB(x, y));

                    // create a new cuda pixel, specific to the matting algorithm
                    PixelMap cudaPixel = new PixelMap((short) x, (short) y, (byte) RGB[0], (byte) RGB[1], (byte) RGB[2]);

                    // save the new cuda pixel in the map
                    cudaPixelMap[(int) objMap[y][x]].add(cudaPixel);

                    // increment the scribble number
                    noScribbles++;
                }
            }
        }
        return noScribbles;
    }

    /**
     * Draw a figure selected by the user in the interface.
     */
    private void drawFigure(Graphics2D g2D) {
        switch (drawType) {
            case DO_NOT_DRAW:
                // do nothing
                break;

            case DRAW_POINT:
                DrawOptions.drawPoint(g2D, currentMouse);
                break;

            case DRAW_LINE:
                DrawOptions.drawLine(g2D, oldMouse, currentMouse);
                break;

            case DRAW_BOUNDING_BOX:
            case DRAW_CROP:
                drawBoxCrop(g2D);
                break;

            case DRAW_POLYGON:
                DrawOptions.drawMouseIndicator(g2D, true, mousePosition, panelSize);

                DrawOptions.drawPolygon(g2D, currentPolygon, Color.red, false);
                break;
				
            case EDIT_POLYGON_VERTICES:
                DrawOptions.drawPolygon(g2D, currentPolygon, objColor, false);
                break;

            case DRAW_SCRIBBLE:
                drawScribble(g2D);
                break;

            case EDIT_MODE:
                editMode(g2D);
                break;

            default:
                //do nothing
                break;
        }

        repaint();
    }

    /**
     * Display the drawn box on the screen (for crop or bounding box
     * segmentation).
     *
     * @param g2D the graphics used to draw on the panel
     */
    private void drawBoxCrop(Graphics2D g2D) {
        // draw the mouse guidings
        DrawOptions.drawMouseIndicator(g2D, (drawType == DrawConstants.DrawType.DRAW_CROP), mousePosition, panelSize);

        // save the drawn box and display it
        saveCurrentBBox();

        // if thebox is valid and  drawGuideShape is true, display the box and its size
        if ((drawGuideShape) && (curBBoxPanelCoord.width > 0) && (curBBoxPanelCoord.height > 0)) {
            DrawOptions.drawBBox(g2D, curBBoxPanelCoord, Color.red, (drawType == DrawConstants.DrawType.DRAW_CROP));

            displayBoxSize();
        }
    }

    /**
     * Allow the user to add a crop to an object in the case ctrl + click are
     * pressed together in edit mode.
     *
     * @param g2D the graphics used to draw on the panel
     */
    private void editMode(Graphics2D g2D) {
        // if edit mode and ctrl + mouse clicked => add crop to object
        if (drawGuideShape) {
            DrawOptions.drawMouseIndicator(g2D, true, mousePosition, panelSize);

            saveCurrentBBox();
            DrawOptions.drawBBox(g2D, curBBoxPanelCoord, Color.red, (drawType == DrawConstants.DrawType.DRAW_CROP));
        }
    }

    /**
     * Makes sure that the selected bounding box is inside the image and saves
     * it.
     */
    private void saveCurrentBBox() {
        int x = Math.min(oldMouse.x, currentMouse.x);
        int y = Math.min(oldMouse.y, currentMouse.y);
        int w = Math.abs(currentMouse.x - oldMouse.x);
        int h = Math.abs(currentMouse.y - oldMouse.y);

        // normalise the point to the image
        if (!Utils.checkBounds(new Point(x, y), getWorkImgSize())) {
            if (x < 0) {
                // the width of the crop is as well smaller
                w += x;
                x = 0;
            }
            if (y < 0) {
                // the height of the crop is as well smaller
                h += y;
                y = 0;
            }
            if (x >= workImg.getWidth()) {
                x = workImg.getWidth() - 1;
            }
            if (y >= workImg.getHeight()) {
                y = workImg.getHeight() - 1;
            }
        }

        if (!Utils.checkBounds(new Point(x + w, y + h), getWorkImgSize())) {
            if (x + w >= workImg.getWidth()) {
                w = workImg.getWidth() - x;
            }

            if (y + h >= workImg.getHeight()) {
                h = workImg.getHeight() - y;
            }
        }
        curBBoxPanelCoord = new Rectangle(x, y, w, h);
    }

    /**
     * Display the size of the bounding box in the top left corner.
     */
    private void displayBoxSize() {
        Rectangle imgPos = getCurBBoxImageCoords();
        jLBoxSize.setText(imgPos.width + "x" + imgPos.height);

        Point pos = DrawOptions.computeTextLocation(curBBoxPanelCoord, jLBoxSize.getSize(), new Dimension(workImg.getWidth(), workImg.getHeight()));
        jLBoxSize.setLocation(pos.x, pos.y);

        jLBoxSize.setVisible(true);
    }

    /**
     * Refresh the displayed image, considering the maximum available space on
     * the gui.
     *
     * @param image the image to be displayed
     * @param availableDrawSize the amount of space available on the gui for drawing the image
     */
    public void refreshImage(BufferedImage image, Dimension availableDrawSize) {
        // compute panel size based on maximum available space for it
        recomputePanelSize(new Dimension(image.getWidth(), image.getHeight()), availableDrawSize);

        // resize the displayed image, according to the new panel size
        updateImage(image);
    }

    /**
     * Change the frame with another one.
     *
     * @param image the new image to be displayed
     * @param availableDrawSize the amount of space available on the gui for drawing the image
     */
    public void newFrame(BufferedImage image, Dimension availableDrawSize) {
        // when the new image has a different size than the previous, the size of the panel has to be adjusted
        if ((image.getWidth() != origImg.getWidth()) || (image.getHeight() != origImg.getHeight())) {
            // compute panel size based on maximum available space for it
            recomputePanelSize(new Dimension(image.getWidth(), image.getHeight()), availableDrawSize);
        }

        // change the displayed image with the given one
        updateImage(image);

        // if the scribbles are not meant to be kept, erase all the related data
        cleanScribbleLists();
    }

    /**
     * Recompute the panel size, considering the maximum available space on the
     * gui.
     *
     * @param availableDrawSize the amount of space available on the gui for
     * drawing the image
     */
    private void recomputePanelSize(Dimension imgSize, Dimension availableDrawSize) {
        // recompute the size of the panel, according to the new image size
        this.panelSize = PanelResolution.computeOptimalPanelSize(new Dimension(imgSize.width, imgSize.height), availableDrawSize);

        setPanelFixedSize();

        // compute the resize based on the new image size and the updated panel size
        double ratioWidth = (double) imgSize.width / (double) panelSize.width;
        double ratioHeight = (double) imgSize.height / (double) panelSize.height;
        resize = new Resize(ratioWidth, ratioHeight);
    }

    /**
     * The displayed image is resized by the ratio between the original image
     * size and the panel size where the image will be displayed. When the
     * resize ratio is changed, the displayed image has to be changed as well.
     *
     * @param image the original image (in original size)
     */
    public void updateImage(BufferedImage image) {
        // update the image
        this.origImg = image;

        // create the work image, based on the resize 
        if (!resize.equals(new Resize(1.0, 1.0))) {
            times.start();
            workImg = resize.resizeImage(origImg);
            times.stopMS("resized image to " + workImg.getWidth() + "x" + workImg.getHeight() + " in ");
        } else {
            workImg = new BufferedImage(origImg.getWidth(), origImg.getHeight(), origImg.getType());
            Utils.copySrcIntoDstAt(origImg, workImg);
        }
		
	panelSize.width = workImg.getWidth();
        panelSize.height = workImg.getHeight();
        setPanelFixedSize();
    }

    /**
     * Clean the structures which save information related to the scribbles.
     */
    private void cleanScribbleLists() {
        //clean the pixels list
        scribbleList.clear();

        //set all points as not visited
        visited = new boolean[origImg.getHeight()][origImg.getWidth()];
    }

    /**
     * Set the chosen type of drawing which shall be performed on the current
     * panel. See DrawType.java for more details on the options.
     *
     * @param drawType - the predefined type of drawing.
     */
    public void setDrawType(DrawConstants.DrawType drawType) {
        this.drawType = drawType;
    }

    /**
     * Returns the chosen type of drawing which used on the current panel. See
     * DrawType.java for more details on the options.
     *
     * @return - the type of drawing used on the current panel
     */
    public DrawConstants.DrawType getDrawType() {
        return drawType;
    }
	
	/**
     * Allow the highlight of points; the points of a polygon for example.
     *
     * @param highlightPoints true if the selected point/points should be
     * highlighted
     */
    public void setHighlightPoints(boolean highlightPoints) {
        this.highlightPoints = highlightPoints;
    }

    /**
     * Set the user chosen options related to the way a scribble is drawn. See
     * ScribbleOptions.java for more info related on the options.
     *
     * @param scribbleOpt - the configuration wanted by the user
     */
    public void setBrushOptions(BrushOptions scribbleOpt) {
        this.brushOpt = scribbleOpt;
    }

    /**
     * Set the type of the action shall be taken for the object.
     *
     * @param actionType - the type of action: -1 = erase; 0 = background; 1 = object
     */
    public void setActionType(int actionType) {
        this.actionType = actionType;
    }

    /**
     * Sets work img.
     *
     * @param workImg the work img
     */
    public void setWorkImg(BufferedImage workImg) {
        this.workImg = workImg;
    }

    /**
     * Returns the description of the current bounding box drawn by the user in
     * panel coordinates.
     *
     * @return - the x and y coordinates of the upper-left corner, the width and the height of the box of the panel coordinates.
     */
    public Rectangle getCurBBoxPanelCoords() {
        return curBBoxPanelCoord;
    }

    /**
     * Returns the description of the current bounding box drawn by the user in
     * image coordinates.
     *
     * @return - the x and y coordinates of the upper-left corner, the width and the height of the box of the image coordinates.
     */
    public Rectangle getCurBBoxImageCoords() {
        // convert to img coordinates the top-left point
        Point tlPointPanel = new Point(curBBoxPanelCoord.x, curBBoxPanelCoord.y);
        Point tlPointImg = resize.resizedToOriginal(tlPointPanel);

        // convert to img coordinates the bottom-right point
        Point brPointPanel = new Point(curBBoxPanelCoord.x + curBBoxPanelCoord.width,
                curBBoxPanelCoord.y + curBBoxPanelCoord.height);
        Point brPointImg = resize.resizedToOriginal(brPointPanel);

        // compute the width and height of the box in the image coordinates
        int widthImg = brPointImg.x - tlPointImg.x;
        int heightImg = brPointImg.y - tlPointImg.y;

        // create an object which represents the selection, in the original image
        return (new Rectangle(tlPointImg.x, tlPointImg.y, widthImg, heightImg));
    }

    /**
     * Set the coordinates of the box in panel coordinates.
     *
     * @param curBBoxPanelCoord - the new bounding box
     */
    public void setCurBBoxPanelCoord(Rectangle curBBoxPanelCoord) {
        this.curBBoxPanelCoord = curBBoxPanelCoord;
    }

    /**
     * Return the list of valid scribbles.
     *
     * @return - the list of scribbles
     */
    public List<ScribbleInfo> getScribbleList() {
        List<ScribbleInfo> outputScribbleList = new ArrayList<>();

        // get the list of segmented pixels and save only the valid and visited ones
        for (ScribbleInfo pi : this.scribbleList) {
            if (visited[pi.getImgPosY()][pi.getImgPosX()]) {
                outputScribbleList.add(pi);
            }
        }

        return outputScribbleList;
    }

    /**
     * Load a predefined list of scribbles.
     *
     * @param scribbleList - the new list of scribbles
     */
    public void setScribbleList(List<ScribbleInfo> scribbleList) {
        this.scribbleList = scribbleList;

        for (ScribbleInfo pi : scribbleList) {
            visited[pi.getImgPosY()][pi.getImgPosX()] = true;
        }
    }

    /**
     * Reset the position of the places where the user executed a click
     * press-release action. Used to reset the bounding boxes and crops from the
     * gui.
     */
    public void resetMousePosition() {
        oldMouse = new Point();
        currentMouse = new Point();
    }

    /**
     * Add the object ids on labels and display them on the work panel.
     */
    private void displayObjectsIds(Graphics2D g2D) {
        // save the color of g2D to be able to restore it
        Color g2dColor = g2D.getColor();

        for (DisplayBBox bBox : bBoxList) {
            // only the outter crop containing the small crops has to have the id shown
            if (!bBox.isUseDashedLine()) {
                DrawOptions.displayObjectId(g2D, bBox.getPanelBox(), bBox.getText(), bBox.getColor(), new Dimension(workImg.getWidth(), workImg.getHeight()));
            }
        }

        g2D.setColor(g2dColor);
    }

    /**
     * Set the list of bounding boxes to be drawn on the interface.
     *
     * @param positions - list of positions where the bounding boxes have to be drawn
     */
    public void setBBoxList(List<DisplayBBox> positions) {
        this.bBoxList = positions;
    }

    /**
     * Set the list of polygons to be drawn on the interface.
     *
     * @param polygonList - list of polygons which have to be drawn
     */
    public void setPolygonDisplayList(List<DisplayPolygon> polygonList) {
        this.polygonList = polygonList;
    }

    /**
     * Get the id of the selected box, from the bBoxList. It is important for
     * being able to retrieve the position of the selected box.
     */
    private void getSelectedBoxId() {
        int cropWidth = workImg.getWidth();
        int cropHeight = workImg.getHeight();

        int idBox = -1;    // set as initial id, an invalide one
        int index = 0;

        for (DisplayBBox displayBox : bBoxList) {
            // if the mouse is positioned inside of one crop
            boolean validPosition = (mousePosition.x >= displayBox.getPanelBox().x)
                    && (mousePosition.x <= (displayBox.getPanelBox().x + displayBox.getPanelBox().width))
                    && (mousePosition.y >= displayBox.getPanelBox().y)
                    && (mousePosition.y <= (displayBox.getPanelBox().y + displayBox.getPanelBox().height));
            boolean validCrop = ((cropWidth > displayBox.getPanelBox().width)
                    && (cropHeight > displayBox.getPanelBox().height));

            if (validPosition && validCrop) {
                //take the index only if the size of the crop is smaller => crop inside crop
                idBox = index;
                cropWidth = displayBox.getPanelBox().width;
                cropHeight = displayBox.getPanelBox().height;
            }

            index++;
        }

        idSelectedBox = idBox;
    }

    /**
     * Sets as selected box, the object from the specified position.
     *
     * @param posImg - the position of the box, in image coordinates
     */
    public void setSelectedBox(Rectangle posImg) {
        Rectangle posPanel = resize.originalToResized(posImg);

        bBoxList.stream().filter(displayBox -> (displayBox.equalPanels(posPanel))).forEach(
                displayBox -> idSelectedBox = bBoxList.indexOf(displayBox));
    }

    /**
     * Sets draw scribble history.
     *
     * @param drawScribbleHistory the draw scribble history
     */
    public void setDrawScribbleHistory(boolean drawScribbleHistory) {
        this.drawScribbleHistory = drawScribbleHistory;
    }

    /**
     * Set the list of scribbles to be displayed. The have no usage in the
     * running of the algorithms.
     *
     * @param cropScribbles - the list of scribbles to be drawn
     */
    public void addScriblesToDisplay(List<DisplayScribbles> cropScribbles) {
        displayScribbles = cropScribbles;
    }

    /**
     * Get the crop which will be drawn as a selected box.
     *
     * @return - the object to be displayed as selected
     */
    public DisplayBBox getSelectedBox() {
        if (idSelectedBox > -1) {
            return bBoxList.get(idSelectedBox);
        }
        return null;
    }

    /**
     * Reset the selection. No box shall be selected.
     */
    public void resetIdSelectedBox() {
        this.idSelectedBox = -1;
    }

    /**
     * Returns the original image, in original size
     *
     * @return - the original image drawn on the current panel.
     */
    public BufferedImage getOrigImg() {
        return origImg;
    }

    /**
     * Returns the work image, in the recomputed size
     *
     * @return - the work image drawn on the current panel.
     */
    public BufferedImage getWorkImg() {
        return workImg;
    }

    /**
     * Reload the work image by recomputing it from the original image.
     */
    public void reloadWorkImg() {
        workImg = resize.resizeImage(origImg);
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

    /**
     * Returns the list of pixels needed by the cuda module, in the specific
     * format.
     *
     * @return - the list of pixels belonging to each object
     */
    public List<PixelMap>[] getCudaPixelMap() {
        return cudaPixelMap;
    }

    /**
     * Get the resize characteristics.
     *
     * @return - the resize parameter, encapsulating the resize ratios between the panel and the original image.
     */
    public Resize getResize() {
        return resize;
    }

    /**
     * Get the dimension of the original image.
     *
     * @return the dimension of the original image
     */
    public Dimension getOrigImgSize() {
        return new Dimension(origImg.getWidth(), origImg.getHeight());
    }

    /**
     * Get the dimension of the working image.
     *
     * @return the dimension of the working image
     */
    public Dimension getWorkImgSize() {
        return new Dimension(workImg.getWidth(), workImg.getHeight());
    }

    /**
     * Allow the user to draw a crop in order to add it to an existent object,
     * which is in edit mode.
     *
     * @param addCropToObj - true if the user should be able to draw a crop to add to an object, false otherwise
     */
    public void setAddCropToObj(boolean addCropToObj) {
        // enable only if there is a selected object
        this.addCropToObj = (addCropToObj && (idSelectedBox > -1));
    }

    /**
     * Returns the offset between the current position of the mouse and the
     * previous one.
     *
     * @return - the x and y offset of the mouse - panel coordinates
     */
    public Point getMouseMovementOffsetPanel() {
        Point offset = new Point();
        offset.setLocation(currentMouse.x - prevMouse.x, currentMouse.y - prevMouse.y);
        return offset;
    }

    /**
     * Returns the current position of the mouse.
     *
     * @return - the x and y of the position of the mouse - panel coordinates
     */
    public Point getMouseCurrentPosPanel() {
        return currentMouse;
    }

    /**
     * Get the characteristics of the brush.
     *
     * @return - the object encapsulating the size and the density of the brush
     */
    public BrushOptions getBrushOpt() {
        return brushOpt;
    }

    /**
     * Returns the time when the mouse pressed event happened, for the case of
     * bounding box drawing!
     *
     * @return - the time, in nanoseconds, when the mouse was pressed
     */
    public long getMousePressedTime() {
        return times.getStartTime();
    }

    /**
     * Shows/Removes the rectangle representing the preview shape of the
     * segmentation (the red rectangle for example).
     *
     * @param showGuideShape - true if the shape shall be shown and false otherwise
     */
    public void setShowGuideShape(boolean showGuideShape) {
        // do not draw the shape anymore
        drawGuideShape = showGuideShape;
        jLBoxSize.setVisible(drawGuideShape);
    }

    /**
     * Set the color of the object being segmented.
     *
     * @param objColor - the color of the object being segmented
     */
    public void setObjColor(Color objColor) {
        this.objColor = objColor;
    }

    /**
     * Return the current drawn polygon - image coordinates.
     *
     * @return the current drawn polygon - image coordinates
     */
    public Polygon getCurrentPolygonImg() {
        return resize.resizedToOriginal(currentPolygon);
    }

    /**
     * Initialise the current drawn polygon.
     */
    public void initCurrentPolygon() {
        this.currentPolygon = new Polygon();
    }
	
    /**
     * Set the current polygon with the specified one.
     *
     * @param poly the polygon which should be considered the current one
     * @param originalPoly the original polygon, which should not be resized
     */
    public void setCurrentPolygon(Polygon poly, Polygon originalPoly) {
        this.currentPolygon = resize.originalToResized(poly);
        this.originalPolygon = originalPoly;
    }

    /**
     * Reset the current drawn polygon.
     */
    public void resetCurrentPolygon() {
        this.currentPolygon = null;
    }

    private void drawBoxList(Graphics2D g2D) {
        // draw the crops and boxes from history
        if (bBoxList != null) {
            drawBBoxList(g2D);
            displayObjectsIds(g2D);
        }
    }

    /**
     * Draw the list of scribbles on the panel. Draw the points existent in the
     * list, with the purpose just to be displayed, using the defined color for
     * each.
     *
     * @param g2D the graphics object
     */
    private void drawScribbleList(Graphics2D g2D) {
        // draw the scribbles of the objects
        if (displayScribbles != null && drawScribbleHistory) {
            displayScribbles.stream().forEach(scribble
                    -> DrawOptions.drawPoint(g2D, scribble.getPanelPos(), scribble.getColor()));
        }
    }

    /**
     * Draw the list of polygons on the panel.
     *
     * @param g2D the graphics object
     */
    private void drawPolygonList(Graphics2D g2D) {
        // draw the polygon objects
        if (polygonList != null) {
            drawPolygonObjList(g2D);
        }
    }

    /**
     * Enable/Disable the drawing of a highlight of the object to be able to see
     * faster the segmented objects.
     *
     * @param drawAlphaObj true - draws an alpha on top of the object, of the color of the object; false - does not draw anything additional to the object borders
     */
    public void setDrawAlphaObj(boolean drawAlphaObj) {
        this.drawAlphaObj = drawAlphaObj;
    }

    /**
     * Set the value of the alpha used to highlight the object.
     *
     * @param objAlpha the value of the alpha to be used for highlighting the object (0-255)
     */
    public void setObjAlpha(int objAlpha) {
        this.objAlpha = objAlpha;
    }

    /**
     * Find the closest polygon point to the place where the user clicked.
     */
    private void getClosestPolyPoint() {
        // if a point is selected, do not compute the distances anymore
        if ((currentPolygon == null) || (0 == currentPolygon.npoints)) {
            return;
        }

        float minDist = Float.MAX_VALUE;
        int closestPointIndex = -1;

        for (int index = 0; index < currentPolygon.npoints; index++) {
            /// compute the distance between the points
            float distance = (float) Math.sqrt((currentMouse.x - currentPolygon.xpoints[index]) * (currentMouse.x - currentPolygon.xpoints[index])
                    + (double) (currentMouse.y - currentPolygon.ypoints[index]) * (currentMouse.y - currentPolygon.ypoints[index]));
            // if the distance is smaller, take the new point as closer
            if (distance < minDist) {
                minDist = distance;
                closestPointIndex = index;
            }
        }

        selectedPolyIndex = closestPointIndex;

        // highlight points if any selected
        highlightPoints = true;
    }

    /**
     * Find the closest polygon point to the place where the user clicked.
     */
    private void getClosestPolyLine() {
        // if a point is selected, do not compute the distances anymore
        if ((currentPolygon == null) || (0 == currentPolygon.npoints)) {
            return;
        }

        float minDist = Float.MAX_VALUE;
        int closestPointIndex = -1;

        for (int index = 1; index < currentPolygon.npoints; index++) {
            /// compute the distance between the points
            Point a = new Point(currentPolygon.xpoints[index - 1], currentPolygon.ypoints[index - 1]);
            Point b = new Point(currentPolygon.xpoints[index], currentPolygon.ypoints[index]);
            float distance = Utils.distPointToSegment(a, b, currentMouse);

            // if the distance is smaller, take the new point as closer
            if (distance < minDist) {
                minDist = distance;
                closestPointIndex = index;
            }

            System.out.println("dist = " + distance);
        }

        Point a = new Point(currentPolygon.xpoints[currentPolygon.npoints - 1], currentPolygon.ypoints[currentPolygon.npoints - 1]);
        Point b = new Point(currentPolygon.xpoints[0], currentPolygon.ypoints[0]);
        float distance = Utils.distPointToSegment(a, b, currentMouse);
        // if the distance is smaller, take the new point as closer
        if (distance < minDist) {
            minDist = distance;
            closestPointIndex = 0;
        }
        System.out.println("dist = " + distance);

        System.out.println("MIN dist: " + minDist + "  closestPointIndex: " + closestPointIndex);
        selectedPolyIndex = closestPointIndex;

        // highlight points if any selected
        highlightPoints = true;
    }

    /**
     * Highlight a specified point.
     *
     * @param g2d the graphics component
     * @param selectedPoint the point to be highlighted
     */
    private void highlightPoint(Graphics2D g2d, int selectedPointIndex) {
        if ((selectedPointIndex >= 0) && highlightPoints) {
            g2d.setColor(new Color(0, 0, 0, 200));
            // if there is a selected box, draw it transparent
            g2d.fillOval(currentPolygon.xpoints[selectedPointIndex] - 5, currentPolygon.ypoints[selectedPointIndex] - 5, 10, 10);
        }
    }

    /**
     * Set the panel size. Do not allow the panel to be resized.
     */
    private void setPanelFixedSize() {
        this.setPreferredSize(panelSize);
        this.setSize(panelSize);
        this.setMinimumSize(panelSize);
        this.setMaximumSize(panelSize);
    }

    /**
     * Remove the selected point from the polygon. If the point is found, shift
     * all the points to the left with one position (overwrite the current
     * point).
     */
    private void removeSelectedPolyPoint() {
        if ((-1 == selectedPolyIndex) || (0 == currentPolygon.npoints)
                || (selectedPolyIndex > currentPolygon.npoints - 1)) {
            return;
        }

        // shift the points to the left, in order to overwrite the unwanted point
        for (int pos = selectedPolyIndex; pos < currentPolygon.npoints - 1; pos++) {
            // remove the matching point by shifting the rest of the points to the left
            currentPolygon.xpoints[pos] = currentPolygon.xpoints[pos + 1];
            currentPolygon.ypoints[pos] = currentPolygon.ypoints[pos + 1];
        }

        // decrease the number of points with one
        currentPolygon.npoints -= 1;

        // do not show the point because it does not exist
        highlightPoints = false;
    }

    /**
     * Reset the polygon index because the old one is no longer valid.
     */
    public void resetPolygonIndex() {
        selectedPolyIndex = -1;
    }

    /**
     * Insert a new point in the polygon, represented by the current mouse
     * coordinates, at the position specified by selectedPolyIndex.
     */
    private void splitPolyLine() {
        if (-1 == selectedPolyIndex) {
            return;
        }
        Polygon poly = new Polygon();

        int index;
        if (0 == selectedPolyIndex) {
            poly.addPoint(currentMouse.x, currentMouse.y);
        }

        for (index = 0; index < selectedPolyIndex; index++) {
            poly.addPoint(currentPolygon.xpoints[index], currentPolygon.ypoints[index]);
        }
        if (0 != selectedPolyIndex) {
            poly.addPoint(currentMouse.x, currentMouse.y);
        }

        for (index = selectedPolyIndex; index < currentPolygon.npoints; index++) {
            poly.addPoint(currentPolygon.xpoints[index], currentPolygon.ypoints[index]);
        }

        currentPolygon = poly;
        observable.notifyObservers(ObservedActions.Action.UPDATE_POLYGON_VERTICES);
    }

    public void updateScribbleList() {
        for (ScribbleInfo pi : scribbleList) {
            pi.setPanelPos(resize.originalToResized(pi.getImgPos()));
        }
    }
} public class ResultPanel extends JPanel {

    /**
     * The original image loaded or cropped by the user.
     */
    private transient BufferedImage origImage;

    /**
     * The image drawn on the panel
     */
    private transient BufferedImage workImg;

    /**
     * The dimension of the panel
     */
    private final Dimension panelSize;

    /**
     * The map of objects segmented by the algorithm.
     */
    private byte[][] objMap;

    /**
     * The color of the object.
     */
    private Color objColor;

    /**
     * logger instance
     */
    private final transient Logger log = LoggerFactory.getLogger("paintpanels.ResultPanel");

    /**
     * Create an image panel which will display the image from the specified
     * path.
     *
     * @param origImg  - the image resulted after running the matting algorithm
     * @param panelRes - the maximum resolution of the panel where the image shall be displayed
     * @param objColor - the color of the object being segmented
     */
    public ResultPanel(BufferedImage origImg, Dimension panelRes, Color objColor) {
        origImage = origImg;

        // copy the original image in the work image
        workImg = new BufferedImage(origImage.getWidth(), origImage.getHeight(), origImage.getType());
        Utils.copySrcIntoDstAt(origImage, workImg);

        // set the object color
        this.objColor = objColor;

        // init object map
        objMap = new byte[origImg.getWidth()][origImg.getHeight()];

        //panel size
        this.panelSize = panelRes;

        initPanel();
    }

    /**
     * Configure the basic characteristics of the panel.
     */
    private void initPanel() {
        //characteristics of the panel
        this.setPreferredSize(panelSize);
        this.repaint();
        this.setVisible(true);

    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(workImg, 0, 0, panelSize.width, panelSize.height, this); // see javadoc for more info on the parameters            
    }

    /**
     * Change the displayed image with a new one.
     *
     * @param origImg the new image to be displayed
     */
    public void refreshImage(BufferedImage origImg) {
        origImage = origImg;

        // copy the original image in the work image
        workImg = new BufferedImage(origImage.getWidth(), origImage.getHeight(), origImage.getType());
        Utils.copySrcIntoDstAt(origImage, workImg);

        updateResultImg();

        repaint();
    }

    /**
     * Remove the image by making all the pixels of the specified color.
     *
     * @param color - the color which has to be on shown in the image
     */
    public void resetImage(Color color) {
        Graphics2D g2D = origImage.createGraphics();
        g2D.setColor(color);
        g2D.fillRect(0, 0, origImage.getWidth(), origImage.getHeight());
        g2D.dispose();
    }

    /**
     * Merges the given object map in the image object map, at the specified
     * coordinates.
     *
     * @param newObjMap - the object map that has to be merged in the result map
     * @param pos       - the position where the image has to be copied: info x - the top-left X coordinate where the image has to be copied info; y - the top-left Y coordinate where the image has to be copied info; width - the width of the image to be copied info; height - the height of the image to be copied
     * @param objId     - the object id as byte value (it is obtained by computing the image object id % 255)
     * @param mergeBkg  - true if both the background and object have to overwrite the existent content of the object map; false if only the object should be merged
     */
    public void mergeCrop(byte[][] newObjMap, Rectangle pos, long objId, boolean mergeBkg) {
        byte mapId = getByteObjId(objId);

        // update objectMap
        for (int y = 0; y < pos.getHeight(); y++) {
            for (int x = 0; x < pos.getWidth(); x++) {

                if (mergeBkg) {
                    // merge bakground in object map (overwrites the initial byte, no matter what was storred, with bkg)
                    objMap[x + pos.x][y + pos.y] = (newObjMap[x][y] > 0) ? mapId : (byte) 0;

                } else {
                    // merge just the pure object - when the pixel in the  object map is != 0
                    objMap[x + pos.x][y + pos.y] = ((objMap[x + pos.x][y + pos.y] == mapId) && (newObjMap[x][y] == 0)) ? 0 : objMap[x + pos.x][y + pos.y];
                    objMap[x + pos.x][y + pos.y] = (newObjMap[x][y] > 0) ? mapId : objMap[x + pos.x][y + pos.y];
                }
            }
        }
    }

    /**
     * Apply the object map to the original image in order to obtain the new
     * image with the preview of the segmentation.
     */
    public void updateResultImg() {
        // alpha blending: R = (foregroundRed*foregroundAlpha) + (backgroundRed*(1-foregroundAlpha))
        int[] bkg;  // backgroung
        int[] fg;   // foreground
        int[] RGB = new int[3];
        float alpha = 120.0f / 255.0f;

        if (objMap == null) {
            log.error("OBJECT MAP NULL!!!!!");
            return;
        }

        // apply background/object color on the image
        for (int y = 0; y < origImage.getHeight(); y++) {
            for (int x = 0; x < origImage.getWidth(); x++) {
                bkg = Utils.getRGB(origImage.getRGB(x, y));
                fg = Utils.getRGB(Utils.getDrawingColor((int) objMap[x][y], objColor).getRGB());

                RGB[0] = (int) ((fg[0] * alpha) + (bkg[0] * (1 - alpha)));
                RGB[1] = (int) ((fg[1] * alpha) + (bkg[1] * (1 - alpha)));
                RGB[2] = (int) ((fg[2] * alpha) + (bkg[2] * (1 - alpha)));

                workImg.setRGB(x, y, new Color(RGB[0], RGB[1], RGB[2]).getRGB());
            }
        }
    }

    /**
     * Update the result image based on the list of segmented objects.
     *
     * @param objList the list of segmented objects
     */
    public void updateResultImg(List<Objects> objList) {
        // alpha blending: R = (foregroundRed*foregroundAlpha) + (backgroundRed*(1-foregroundAlpha))
        int[] bkg;  // background
        int[] fg;   // foreground
        int[] RGB = new int[3];
        float alpha = 120.0f / 255.0f;

        // apply background/object color on the image
        for (int y = 0; y < origImage.getHeight(); y++) {
            for (int x = 0; x < origImage.getWidth(); x++) {
                bkg = Utils.getRGB(origImage.getRGB(x, y));

                if (objMap[x][y] != (byte) 0) {
                    fg = Utils.getRGB(getObjColor(objMap[x][y], objList).getRGB());
                } else {
                    fg = Utils.getRGB(Color.red.getRGB());
                }

                RGB[0] = (int) ((fg[0] * alpha) + (bkg[0] * (1 - alpha)));
                RGB[1] = (int) ((fg[1] * alpha) + (bkg[1] * (1 - alpha)));
                RGB[2] = (int) ((fg[2] * alpha) + (bkg[2] * (1 - alpha)));

                workImg.setRGB(x, y, new Color(RGB[0], RGB[1], RGB[2]).getRGB());
            }
        }

        repaint();
    }

    /**
     * Get the color of the segmented object, from the object list. Search the
     * object by id.
     *
     * @param id the object id for which the color is wanted
     * @param objList the list of segmented objects
     * @return the color of the wanted object
     */
    private Color getObjColor(byte id, List<Objects> objList) {
        for (Objects obj : objList) {
            if (getByteObjId(obj.getObjectId()) == id) {
                return obj.getColor();
            }
        }
        return objColor;
    }

    /**
     * Return the dimension of the image displayed on the panel.
     *
     * @return - the size of the image
     */
    public Dimension getImgDimensions() {
        return (new Dimension(origImage.getWidth(), origImage.getHeight()));
    }

    /**
     * Get the original image, not the one drawn.
     *
     * @return - the original image, before the segmentation was done
     */
    public BufferedImage getOriginalImage() {
        return origImage;
    }

    /**
     * Get the modified image, the one drawn on the panel.
     *
     * @return - the image, after the segmentation was done
     */
    public BufferedImage getWorkImg() {
        return workImg;
    }

    /**
     * Returns the map of object indexes, segmented by the algorithm.
     *
     * @return - the matrix of the objects in the image
     */
    public byte[][] getObjMap() {
        return objMap;
    }

    /**
     * Set the map of object indexes, segmented by the algorithm.
     *
     * @param objMap - the matrix representing the mapping of the objects in the image.
     */
    public void setObjMap(byte[][] objMap) {
        this.objMap = objMap;
    }

    /**
     * Export the image saved on the panel at the specified path and name.
     *
     * @param name - the name of the file to be exported
     */
    public void exportImage(String name) {
        ExportImage.exportAsPNG(workImg, name);
    }

    /**
     * Search for the object id in the given area of the object map and remove
     * it. It was cancel.
     *
     * @param objectId - the id of the object to be removed from the object map.
     * @param area     - the area where to search for the object id and remove it from the object map
     */
    public void removeObj(long objectId, Rectangle area) {
        if (area == null) {
            return;
        }

        // map the object id on byte
        byte mapId = getByteObjId(objectId);

        for (int y = 0; y < area.height; y++) {
            for (int x = 0; x < area.width; x++) {
                if (objMap[x + area.x][y + area.y] == mapId) {
                    // erase the object, set as background
                    objMap[x + area.x][y + area.y] = (byte) 0;
                }
            }
        }
    }

    /**
     * Returns the equivalent of the object Id, on byte. It is considered that
     * in the image there are no more than 255 different objects.
     *
     * @param objectId - the image id of the object
     * @return - the equivalent id on byte
     */
    private static byte getByteObjId(long objectId) {
        return ((byte) (1 + (objectId % 255)));
    }

    /**
     * Filter the object map array.
     * <p>
     * The filtering is done by sliding a window over the input array and
     * compute the sum of the values in the window (sum of 1s). If the sum is
     * smaller than a threshold, make the middle value of the window 0, else 1.
     * <p>
     * The filter aims to keep the white pixels which are in a neighbourhood of
     * other white pixels and discard the lonely pixels.
     */
    public void filterObjectMap() {
        Utils.filterObjectMap(objMap);
    }

    /**
     * Set the color of the object being segmented.
     *
     * @param objColor - the color of the object being segmented
     */
    public void setObjColor(Color objColor) {
        this.objColor = objColor;
    }

    /**
     * Search for the object id in the given area of the object map and change
     * it to the new specified one.
     *
     * @param oldObjId the old id of the object to be changed in the object map
     * @param newObjId the new id of the object
     */
    public void changeObjId(long oldObjId, long newObjId) {

        // map the object id on byte
        byte oldMapId = getByteObjId(oldObjId);
        byte newObjMap = getByteObjId(newObjId);

        for (int y = 0; y < origImage.getHeight(); y++) {
            for (int x = 0; x < origImage.getWidth(); x++) {
                if (objMap[x][y] == oldMapId) {
                    // erase the object, set as background
                    objMap[x][y] = newObjMap;
                }
            }
        }
    }
} public class MattingThreading implements Runnable {

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    private final NotifyObservers observable = new NotifyObservers();

    /**
     * Shows who called the matting application. The cuda matting application
     * can be called either for the crop image or for the full image. Class
     * ObservedActions shows the possible actions.
     */
    private final ObservedActions.Action actionOwner;

    /**
     * Object to pixel segmentation algorithm.
     */
    private final PixelSegmentation segmentation;

    /**
     * Initialise the needed data, received from the main thread.
     *
     * @param origImg     - the original image to be processed
     * @param scribbles   - the map of pixels/scribbles for the matting application
     * @param actionOwner - who created the request
     */
    public MattingThreading(BufferedImage origImg, List<ScribbleInfo> scribbles, ObservedActions.Action actionOwner) {

        segmentation = new PixelSegmentation(origImg, (ArrayList<ScribbleInfo>) scribbles);
        this.actionOwner = actionOwner;
    }

    @Override
    public void run() {
        // run the matting algorithm
        runMattingAlgo();

        // notify the main thread that the execution is over and the result image is available to be displayed
        observable.notifyObservers(actionOwner);
    }

    /**
     * Run the matting algorithm on the given input image
     *
     * @return byte [ ] [ ]
     */
    public byte[][] runMattingAlgo() {
        return segmentation.runMattingAlgo();
    }

    /**
     * Returns the map of object indexes, segmented by the algorithm.
     *
     * @return - the matrix of the objects in the image
     */
    public byte[][] getObjMap() {
        return segmentation.getObjMap();
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }
} public class PlayImagesRunnable implements Runnable {

    /**
     * Keep the thread running until the user presses the pause button (true =
     * play images; false = pause images).
     */
    private volatile boolean run = true;

    /**
     * An instance of the GUIController for being able to simulate the play of
     * data, based on images.
     */
    private final GUIController gc;

    /**
     * Makes the marked methods observable. It is part of the mechanism to
     * notify the frame on top about changes.
     */
    private final NotifyObservers observable = new NotifyObservers();

    /**
     * The panel containing the original image, where the segmentation/labeling
     * is happening.
     */
    private final DrawingPanel dPImgToLabel;

    /**
     * Instantiates a new Play images runnable.
     *
     * @param gc the gc
     */
    public PlayImagesRunnable(GUIController gc) {
        this.gc = gc;
        this.dPImgToLabel = gc.getdPImgToLabel();
    }

    @Override
    public void run() {
        while (run) {
            // get next frame
            dPImgToLabel.newFrame(gc.getNextFrame(), gc.getAvailableDrawSize());

            // notify the main thread that the frame number changed
            observable.notifyObservers(ObservedActions.Action.REFRESH_FRAME_NO);

            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
                Logger.getLogger(PlayImagesRunnable.class.getName()).log(Level.SEVERE, null, ex);
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * Is run boolean.
     *
     * @return - true for a running thread and false for a paused/stopped thread
     */
    public boolean isRun() {
        return run;
    }

    /**
     * Set true if the thread should run and false otherwise.
     *
     * @param run - true for a running thread and false for a paused/stopped thread
     */
    public void setRun(boolean run) {
        this.run = run;
    }

    /**
     * Allows another module to put an observer into the current module.
     *
     * @param o - the observer to be added
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }

    /**
     * Allows another module to erase an observer from the current module.
     *
     * @param o - the observer to be deleted
     */
    public void deleteObserver(Observer o) {
        observable.deleteObserver(o);
    }

}
import gui.editobject.SemanticObjEdit;
import common.Constants;
import common.ConstantsLabeling;
import common.UserPreferences;
import common.Utils;
import commonsegmentation.ScribbleInfo;
import gui.editobject.BoxEdit;
import gui.editobject.CropEdit;
import gui.editobject.EditWindow;
import gui.editobject.PolygonObjEdit;
import gui.support.*;
import gui.support.Objects;
import gui.viewer.AttributesDefinition;
import gui.viewer.GUILabelingTool;
import observers.NotifyObservers;
import observers.ObservedActions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import paintpanels.DrawConstants;
import paintpanels.DrawingPanel;
import paintpanels.ResultPanel;
import segmentation.MattingThreading;
import videomodule.PlayImagesRunnable;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.List;
import java.util.logging.Level;

    /**
     * Changes the play mode and get the new image, from the corresponding
     * location in the video, considering the new play mode.
     *
     * @param playMode set the video play mode(backward/forward)
     * @return the new frame, based on the new play mode
     */
    protected BufferedImage changePlayMode(int playMode) {
        this.playMode = playMode;

        long frame = Utils.limit(1L, currFrameNo, getNoFrames());

        return getJumpToFrame(frame);
    }

    /**
     * Checks if the data was loaded. It is considered that the data is loaded
     * if the frame grabber is not null, if a folder with files was loaded etc.
     *
     * @return - true if the data was loaded and false otherwise
     */
    public boolean isDataLoaded() {
        return (currentFrame != null);
    }

    /**
     * Loads the saved objects for the current frame (if there were saved).
     */
    protected void loadGroundTruth() {
        // reinit the list of objects read from the saved ground truth
        objectList = new ArrayList<>();

        // read the ground truth from the saved file and load the list of objects and the frame attributes
        jsonDataManag.readFile(getGTFilePath(), getCurrentFrameInfo(), objectList);

        // notify the gui to change the annotations
        observable.notifyObservers(ObservedActions.Action.LOAD_FRAME_ANNOTATION);

        // notify that a new object was added on the list and it has to be displayed in the list
        observable.notifyObservers(ObservedActions.Action.REFRESH_OBJ_LIST_PANEL);
    }

    /**
     * Returns the current frame object, containing the current frame
     * information.
     *
     * @return - the current frame information
     */
    public FrameInfo getCurrentFrameInfo() {
        return currentFrameInfo;
    }

    /**
     * Checks if the object exists in the objects list.
     *
     * @param obj - the object to check if it exists in the list
     * @return - true if the object exists and false otherwise
     */
    protected boolean isObjInObjList(Objects obj) {
        return objectList.stream().anyMatch((object) -> (object.getObjectId() == obj.getObjectId()));
    }

    /**
     * Load a new list of objects from the ground truth storage. Erase the
     * current list of objects and put just the loaded ones.
     */
    private void loadGroundTruthData() {
        // remove all the objects from the object list
        removeAllObjects();

        // load the saved objects if there are some
        loadGroundTruth();

        /* update the gui with the new object coordinates */
        refreshDisplayList();
    }

    /**
     * Cancel object map.
     *
     * @param object the object
     * @param area the area
     */
    public void cancelObjectMap(Objects object, Rectangle area) {
        // update the result image if it exists
        if ((dPImgResult != null) && (object instanceof ObjectScribble)) {

            // remove the object id from the object map
            dPImgResult.removeObj(object.getObjectId(), area);

            // update the output image result with the new map
            dPImgResult.updateResultImg(objectList);
        }
    }

    /**
     * Enables the user to draw one time a crop, which will be added to the
     * current object.
     */
    public void setAddCropToObj() {
        // enable only if the current object is scribble
        if (currentObject instanceof ObjectScribble) {
            dPImgToLabel.setAddCropToObj(true);
        }
    }

    /**
     * Get the information from the crops and run the matting algorithm. Compute
     * the outer box and update the result panel
     *
     * @param obj - the object for which the matting has to run
     */
    private void runMattingAllCrops(Objects obj) {
        // run the matting algorithm for all the crops              
        ((ObjectScribble) obj).getCropList().forEach((crop) -> {
            // get the cropped image
            BufferedImage bi = Utils.getSelectedImg(dPImgToLabel.getOrigImg(), crop.getPositionOrig());

            // instantiate the matting class
            MattingThreading matt = new MattingThreading(bi, crop.getScribbleList(), ObservedActions.Action.DO_NOTHING);