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 ObjectBBox extends Objects {

    /**
     * Instantiates a new Object b box.
     */
    public ObjectBBox() {
        super();
    }

    @Override
    public void computeOuterBBoxCurObj() {
    }

    @Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        // move only if by moving we do not get out of the image
        if ((outerBBox.x + xOffset >= 0)
                && (outerBBox.y + yOffset >= 0)
                && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {
            // move the outer bounding box; the crop will move as well because it is the same object
            moveBox(xOffset, yOffset, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // for boxes, it will always be the erase of the entire object, therefore nothing has to be done inside of the object.
        return false;
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        int xTopRight = outerBBox.x + outerBBox.width;
        int xBottomRight = outerBBox.y + outerBBox.height;

        boolean leftCheck = ((outerBBox.x + left >= 0)
                && (outerBBox.x + left < xTopRight));

        boolean topCheck = ((outerBBox.y + top >= 0)
                && (outerBBox.y + top < xBottomRight));

        boolean rightCheck = ((xTopRight + right > outerBBox.x)
                && (xTopRight + right < frameSize.width));

        boolean bottomCheck = ((xBottomRight + bottom > outerBBox.y)
                && (xBottomRight + bottom < frameSize.height));

        if (leftCheck && topCheck && rightCheck && bottomCheck) {
            // change the outer bounding box size; the crop will move as well because it is the same object
            changeSize(left, top, right, bottom, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }

    /**
     * Changes the size of the box with the specified offsets.
     *
     * @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 boxPosOrig - the initial position of the selected box in image
     * coordinates
     */
    private void changeSize(int left, int top, int right, int bottom, Rectangle bboxOrigPos) {
        bboxOrigPos.setBounds(bboxOrigPos.x + left, bboxOrigPos.y + top, bboxOrigPos.width + right - left, bboxOrigPos.height + bottom - top);
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

	@Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        move(xOffset, yOffset, frameSize);
    }
} public class ObjectPolygon extends Objects {

    /**
     * The polygon saving the vertices of the object.
     */
    private Polygon polygon;

    /**
     * Instantiates a new Object polygon.
     */
    public ObjectPolygon() {
        super();
    }

    @Override
    public void computeOuterBBoxCurObj() {
        int minX = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;

        for (int index = 0; index < polygon.npoints; index++) {
            Point vertex = new Point(polygon.xpoints[index], polygon.ypoints[index]);

            if (minX > vertex.x) {
                minX = vertex.x;
            }

            if (maxX < vertex.x) {
                maxX = vertex.x;
            }

            if (minY > vertex.y) {
                minY = vertex.y;
            }

            if (maxY < vertex.y) {
                maxY = vertex.y;
            }
        }

        outerBBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
    }

    @Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        move(xOffset, yOffset, frameSize);
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // for polygon, it will always be the erase of the entire object, therefore nothing has to be done inside of the object.
        return false;
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
         // in order to change the size of the polygon, an edit window has to be opened; no other changes can be done in the GUI
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

    /**
     * Moves one point with the specified offset and returns the new image
     * coordinates.
     *
     * @param xOffset      - how much should the point move on the X axis
     * @param yOffset      - how much the point should move on the Y axis
     * @param pointPosOrig - the position of the point in image coordinates
     */
    public void movePoint(int xOffset, int yOffset, Point pointPosOrig) {
        pointPosOrig.setLocation(pointPosOrig.x + xOffset, pointPosOrig.y + yOffset);
    }

    /**
     * Return the polygon with the list of points defining the edges of the
     * object.
     *
     * @return the polygon with the list of points representing the border of the object
     */
    public Polygon getPolygon() {
        return polygon;
    }

    /**
     * Set the polygon with the list of points defining the edges of the object.
     *
     * @param polygon the polygon with the list of points representing the border of the object
     */
    public void setPolygon(Polygon polygon) {
        this.polygon = polygon;
    }

	@Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        for (int index = 0; index < polygon.npoints; index++) {
            Point vertex = new Point(polygon.xpoints[index], polygon.ypoints[index]);

            boolean moveValid = ((vertex.x + xOffset >= 0)
                    && (vertex.y + yOffset >= 0)
                    && (vertex.x + xOffset <= frameSize.width)
                    && (vertex.y + yOffset <= frameSize.height));

            if (moveValid) {
                movePoint(xOffset, yOffset, vertex);
                polygon.xpoints[index] = vertex.x;
                polygon.ypoints[index] = vertex.y;
            }
        }

        // recompute the outer bounding box
        computeOuterBBoxCurObj();

        // if the label was changed, it means that the user touched it, therefore the segmentation is manual
        segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
    }
} public class ObjectScribble extends Objects {

    /**
     * The list of crops which together define the object.
     */
    private final List<CropObject> cropList;

    /**
     * The object map built from the crops of the object (for scribble object).
     */
    private byte[][] objectMap;

    /**
     * logger instance
     */
    private final Logger log = LoggerFactory.getLogger("gui.support.ObjectScribble");

    /**
     * Instantiates a new Object scribble.
     */
    public ObjectScribble() {
        super();
        this.cropList = new ArrayList<>();
    }

    @Override
    public void computeOuterBBoxCurObj() {
        generateObjMap();
    }

    /**
     * Returns the array of crop objects which was needed for the segmentation
     * of the object.
     *
     * @return - the list of crops for the object
     */
    public List<CropObject> getCropList() {
        return cropList;
    }

    /**
     * Adds a new crop in the list of crops needed to segment the object.
     *
     * @param cropObj - the crop object which has to be added in the list of crops needed to segment the object
     */
    public void addToCropList(CropObject cropObj) {
        this.cropList.add(cropObj);
    }

    /**
     * Returns the map of the object. It is computed based on the crop maps.
     * <p>
     * 1 = object; 0 = background
     *
     * @return - the map of the object
     */
    public byte[][] getObjectMap() {
        return objectMap;
    }

    /**
     * Generate the object map out of the object parts (crops). Compute and set
     * the outer box of the object.
     */
    public void generateObjMap() {
        byte[][] objMap;

        Rectangle objPos = computeCropsOuterBox();

        /*--------------------------STEP 1------------------------------------*/
        // create the object map with border, by merging all the crops
        byte[][] tempMap = new byte[objPos.width][objPos.height];
        int objBitCounter = 0;

        for (CropObject crop : cropList) {
            for (int y = 0; y < crop.getPositionOrig().height; y++) {
                // compute the y position in outer box coordinates
                int yPos = crop.getPositionCornerOrig().y - objPos.y + y;

                for (int x = 0; x < crop.getPositionOrig().width; x++) {
                    // compute the x position in outer box coordinates
                    int xPos = crop.getPositionCornerOrig().x - objPos.x + x;
                    // merge just the objects, not the background
                    if (crop.getObjectMap()[x][y] > (byte) 0) {
                        tempMap[xPos][yPos] = crop.getObjectMap()[x][y];
                        objBitCounter++;
                    }
                }
            }
        }

        // stop the computation if there are no object parts
        if (objBitCounter == 0) {
            return;
        }

        /*--------------------------STEP 2------------------------------------*/
        int minX = Integer.MAX_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int maxY = Integer.MIN_VALUE;

        // find the rectangle which contains the exact object
        for (int y = 0; y < objPos.height; y++) {
            for (int x = 0; x < objPos.width; x++) {
                // search for the edges of the object
                if (tempMap[x][y] > 0) {
                    if (x < minX) {
                        minX = x;
                    }
                    if (y < minY) {
                        minY = y;
                    }
                    if (x > maxX) {
                        maxX = x;
                    }
                    if (y > maxY) {
                        maxY = y;
                    }
                }
            }
        }

        /*--------------------------STEP 3------------------------------------*/
        int mapXPos = objPos.x + minX;
        int mapYPos = objPos.y + minY;

        // extract only the object and create its object map
        int mapWidth = Utils.limit(0, objPos.width - (objPos.width - maxX) - minX + 1, objPos.width);
        int mapHeight = Utils.limit(0, objPos.height - (objPos.height - maxY) - minY + 1, objPos.height);
        objMap = new byte[mapWidth][mapHeight];

        // copy the exact object in the obj map
        for (int y = 0; y < mapHeight; y++) {
            for (int x = 0; x < mapWidth; x++) {
                objMap[x][y] = tempMap[minX + x][minY + y];
            }
        }

        /*--------------------------STEP TEST--------------------------------*/
        // test
        int sumObjMap = 0;
        for (int y = 0; y < mapHeight; y++) {
            for (int x = 0; x < mapWidth; x++) {
                sumObjMap += objMap[x][y];
            }
        }

        int sumTempObjMap = 0;
        for (int y = 0; y < objPos.height; y++) {
            for (int x = 0; x < objPos.width; x++) {
                sumTempObjMap += tempMap[x][y];
            }
        }

        if (sumObjMap != sumTempObjMap) {
            log.error("THE OBJECT MAP IS DIFFERENT!!!! {} obj - temp = ", (sumObjMap - sumTempObjMap));
        }

        /*--------------------------STEP 4------------------------------------*/
        // set the object outer box as the borders of the object map
        setOuterBBox(new Rectangle(mapXPos, mapYPos, mapWidth, mapHeight));

        // set the object map of the current object
        objectMap = objMap;
    }

    /**
     * Compute the outer box of the crops; the box which contains all the edges
     * of the crops.
     *
     * @return - the rectangle containing all the crops
     */
    private Rectangle computeCropsOuterBox() {
        int minX = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;

        for (CropObject cropObj : cropList) {
            Rectangle cropPosImg = cropObj.getPositionOrig();

            int xLeftCrop = cropPosImg.x;
            int xRightCrop = cropPosImg.x + cropPosImg.width;
            int yLeftCrop = cropPosImg.y;
            int yRightCrop = cropPosImg.y + cropPosImg.height;

            if (minX > xLeftCrop) {
                minX = xLeftCrop;
            }

            if (maxX < xRightCrop) {
                maxX = xRightCrop;
            }

            if (minY > yLeftCrop) {
                minY = yLeftCrop;
            }

            if (maxY < yRightCrop) {
                maxY = yRightCrop;
            }
        }

        return (new Rectangle(minX, minY, maxX - minX, maxY - minY));
    }

    @Override
    public void move(int xOffset, int yOffset, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {

        // if the outer box is selected, move the whole object; else move only the selected crop
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        if (outerBBoxPanel.equals(coordPanelBox)) {
            // check to be in the image with the boundaries
            if ((outerBBox.x + xOffset >= 0)
                    && (outerBBox.y + yOffset >= 0)
                    && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                    && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {

                moveObject(xOffset, yOffset);

                // if the label was changed, it means that the user touched it, therefore the segmentation is manual
                segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
            }
        } else {
            for (CropObject cropObj : cropList) {
                Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
                if (cropPanelPos.equals(coordPanelBox)) {
                    Rectangle cropPosOrig = cropObj.getPositionOrig();

                    // check to be in the image with the boundaries
                    if ((cropPosOrig.x + xOffset >= 0)
                            && (cropPosOrig.y + yOffset >= 0)
                            && (cropPosOrig.x + cropPosOrig.width + xOffset < frameSize.width)
                            && (cropPosOrig.y + cropPosOrig.height + yOffset < frameSize.height)) {

                        cropObj.setPositionOrig(moveBox(xOffset, yOffset, cropPosOrig));
                    }
                }
            }
        }

    }

    /**
     * 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
     */
    public void moveObject(int xOffset, int yOffset) {
        // move all the crops
        cropList.stream().forEach((cropObj) -> {
            cropObj.setPositionOrig(moveBox(xOffset, yOffset, cropObj.getPositionOrig()));
        });

        // move the outer bounding box
        outerBBox = moveBox(xOffset, yOffset, outerBBox);
    }

    @Override
    public boolean remove(Rectangle coordPanelBox, Resize resizeRate) {
        // if the outer box is selected, the whole object shall be erased, therefore it will be done in the parent method; else remove only the selected crop
        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);
        if (outerBBoxPanel.equals(coordPanelBox)) {
            return false;
        } else {
            for (Iterator<CropObject> it = cropList.iterator(); it.hasNext();) {
                Rectangle cropPanelPos = resizeRate.originalToResized(it.next().getPositionOrig());
                if (cropPanelPos.equals(coordPanelBox)) {
                    it.remove();
                }
            }
            return (!cropList.isEmpty());
        }
    }

    @Override
    public void changeSize(int left, int top, int right, int bottom, Rectangle coordPanelBox, Resize resizeRate, Dimension frameSize) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());

            if (cropPanelPos.equals(coordPanelBox)) {
                Rectangle cropPosOrig = cropObj.getPositionOrig();

                // check to be in the image with the boundaries
                int xTopRight = cropPosOrig.x + cropPosOrig.width;
                int xBottomRight = cropPosOrig.y + cropPosOrig.height;

                boolean leftCheck = ((cropPosOrig.x + left >= 0)
                        && (cropPosOrig.x + left < xTopRight));

                boolean topCheck = ((cropPosOrig.y + top >= 0)
                        && (cropPosOrig.y + top < xBottomRight));

                boolean rightCheck = ((xTopRight + right > cropPosOrig.x)
                        && (xTopRight + right < frameSize.width));

                boolean bottomCheck = ((xBottomRight + bottom > cropPosOrig.y)
                        && (xBottomRight + bottom < frameSize.height));

                if (leftCheck && topCheck && rightCheck && bottomCheck) {
                    // change the outer bounding box size; the crop will move as well because it is the same object
                    changeSize(left, top, right, bottom, cropPosOrig);

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

                    // if the label was changed, it means that the user touched it, therefore the segmentation is manual
                    segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
                }
            }
        }
    }

    /**
     * Changes the size of the box with the specified offsets.
     *
     * @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 bboxOrigPos - the initial position of the selected box in image
     * coordinates
     */
    private void changeSize(int left, int top, int right, int bottom, Rectangle bboxOrigPos) {
        bboxOrigPos.setBounds(bboxOrigPos.x + left, bboxOrigPos.y + top, bboxOrigPos.width + right - left, bboxOrigPos.height + bottom - top);
    }

    /**
     * Removes the scribbles which no longer fit in the image; the ones which
     * would generate a coordinates outer box exception.
     *
     * @param cropObj - the object for which the scribbles have to be reviewed
     */
    private void removeScribbles(CropObject cropObj) {
        for (Iterator<ScribbleInfo> it = cropObj.getScribbleList().iterator(); it.hasNext();) {
            ScribbleInfo si = it.next();

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

        }
    }

    @Override
    public boolean contains(Rectangle coordPanelBox, Resize resizeRate) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
            if (cropPanelPos.equals(coordPanelBox)) {
                return true;
            }
        }

        Rectangle outerBBoxPanel = resizeRate.originalToResized(outerBBox);

        return outerBBoxPanel.equals(coordPanelBox);
    }

    /**
     * Returns the crop which is located at the same coordinates as the
     * specified ones.
     *
     * @param coordPanelBox - the position where the crop is located in panel coordinates
     * @param resizeRate    - the resize ratio between the image and the panel
     * @return - the crop found at the wanted location
     */
    public CropObject getCrop(Rectangle coordPanelBox, Resize resizeRate) {
        for (CropObject cropObj : cropList) {
            Rectangle cropPanelPos = resizeRate.originalToResized(cropObj.getPositionOrig());
            if (cropPanelPos.equals(coordPanelBox)) {
                return cropObj;
            }
        }

        return null;
    }
	
	@Override
    public void move(int xOffset, int yOffset, Dimension frameSize) {
        // move only if by moving we do not get out of the image
        if ((outerBBox.x + xOffset >= 0)
                && (outerBBox.y + yOffset >= 0)
                && (outerBBox.x + outerBBox.width + xOffset <= frameSize.width)
                && (outerBBox.y + outerBBox.height + yOffset <= frameSize.height)) {
            // move the outer bounding box; the crop will move as well because it is the same object
            moveBox(xOffset, yOffset, outerBBox);

            // if the label was changed, it means that the user touched it, therefore the segmentation is manual
            segmentationSource = ConstantsLabeling.LABEL_SOURCE_MANUAL;
        }
    }
} 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 ScreenResolution {

    /**
     * The resolution of the current screen
     */
    private Dimension resolution;

    /**
     * Constructor where the resolution is computed.
     *
     * @param comp - the frame of the gui - needed to get the resolution of the screen where the app is showed
     */
    public ScreenResolution(Component comp) {
        //set the current screen resolution                
        GraphicsConfiguration graphicsCfg = comp.getGraphicsConfiguration();
        DisplayMode displayMode = graphicsCfg.getDevice().getDisplayMode();
        resolution = new Dimension(displayMode.getWidth(), displayMode.getHeight());
    }

    /**
     * Gets screen resolution.
     *
     * @return Returns the value of the resolution of the screen.
     */
    public Dimension getScreenResolution() {
        return resolution;
    }

    /**
     * Gets screen width.
     *
     * @return the width of the screen
     */
    public int getScreenWidth() {
        return resolution.width;
    }

    /**
     * Gets screen height.
     *
     * @return the height of the screen
     */
    public int getScreenHeight() {
        return resolution.height;
    }

    /**
     * Saves the resolution of the screen where the component is showed.
     *
     * @param comp - the frame for which the resolution of the containing screen is needed
     */
    public void setScreenResolution(Component comp) {
        GraphicsConfiguration graphicsCfg = comp.getGraphicsConfiguration();
        DisplayMode displayMode = graphicsCfg.getDevice().getDisplayMode();
        resolution = new Dimension(displayMode.getWidth(), displayMode.getHeight());
    }
} public class BoxLRTB {

    /*
    *                top
    *        ____________________
    *       |                    |
    *       |                    |
    * left  |                    |  right
    *       |                    |
    *       |____________________|
    *              bottom
     */
    /**
     * The left coordinate of the box.
     */
    private int xLeft;

    /**
     * The right coordinate of the box.
     */
    private int xRight;

    /**
     * The top coordinate of the box.
     */
    private int yTop;

    /**
     * The bottom coordinate of the box.
     */
    private int yBottom;

    /**
     * Instantiates a new Box lrtb.
     *
     * @param value the value
     */
    public BoxLRTB(int value) {
        this.xLeft = value;
        this.xRight = value;
        this.yTop = value;
        this.yBottom = value;
    }

    /**
     * Instantiates a new Box lrtb.
     *
     * @param widthBorder  the width border
     * @param heightBorder the height border
     */
    public BoxLRTB(int widthBorder, int heightBorder) {
        this.xLeft = widthBorder;
        this.xRight = widthBorder;
        this.yTop = heightBorder;
        this.yBottom = heightBorder;
    }

    /**
     * Gets left.
     *
     * @return the left
     */
    public int getxLeft() {
        return xLeft;
    }

    /**
     * Sets left.
     *
     * @param xLeft the x left
     */
    public void setxLeft(int xLeft) {
        this.xLeft = xLeft;
    }

    /**
     * Gets right.
     *
     * @return the right
     */
    public int getxRight() {
        return xRight;
    }

    /**
     * Sets right.
     *
     * @param xRight the x right
     */
    public void setxRight(int xRight) {
        this.xRight = xRight;
    }

    /**
     * Gets top.
     *
     * @return the top
     */
    public int getyTop() {
        return yTop;
    }

    /**
     * Sets top.
     *
     * @param yTop the y top
     */
    public void setyTop(int yTop) {
        this.yTop = yTop;
    }

    /**
     * Gets bottom.
     *
     * @return the bottom
     */
    public int getyBottom() {
        return yBottom;
    }

    /**
     * Sets bottom.
     *
     * @param yBottom the y bottom
     */
    public void setyBottom(int yBottom) {
        this.yBottom = yBottom;
    }

} public class Resize {

    /* The resize ratio on width (widthImg1 / widthImg2) */
    private double ratioWidth;

    /* The resize ratio on height (heightImg1 / heightImg2) */
    private double ratioHeight;

    /* The zoomed width in pixels */
    private int zoomingWidth;

    /* The zoomed height in pixels */
    private int zoomingHeight;

    /* Zooming index */
    private int zoomingIndex;

    /* Zooming factor 10 = 10%; 5 = 20%; 4 = 25%; 2 = 50%; 1 = 100%*/
    private static final int ZOOMING_FACTOR = 4;

    /**
     * Instantiates a new Resize.
     *
     * @param ratioWidth  the ratio width
     * @param ratioHeight the ratio height
     */
    public Resize(double ratioWidth, double ratioHeight) {
        this.ratioWidth = ratioWidth;
        this.ratioHeight = ratioHeight;
    }

    /**
     * Instantiates a new Resize.
     *
     * @param ratioWidth  the ratio width
     * @param ratioHeight the ratio height
     * @param width       the width
     * @param height      the height
     */
    public Resize(double ratioWidth, double ratioHeight, int width, int height) {
        this.ratioWidth = ratioWidth;
        this.ratioHeight = ratioHeight;
        this.zoomingWidth = width;
        this.zoomingHeight = height;
        this.zoomingIndex = 0;
    }

    /**
     * Resize the width and height to fit the max display size In case the
     * provided width and height are smaller than max allowed size they will be
     * zoomed in(maximized) to max allowed size. In case the width and height
     * are bigger than max allowed size they will be zoomed out(minimized) to
     * max allowed size
     *
     * @param width  object's width
     * @param height object's height
     */
    public Resize(int width, int height) {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int widthSc = gd.getDisplayMode().getWidth();
        int heightSc = gd.getDisplayMode().getHeight();
        double ratioW = (double) width / (widthSc * MAX_SCREEN_PERCENT_RESIZE);
        double ratioH = (double) height / (heightSc * MAX_SCREEN_PERCENT_RESIZE);
        double cropRatio = (double) width / (double) height;

        computeResizeRatios(ratioW, ratioH, cropRatio, width, height, widthSc, heightSc);
    }

    /**
     * Resize the window to user prefer size. In case the zoomed user preference
     * is not set, then resize the width and height to fit the max display size
     * In case the provided width and height are smaller than max allowed size
     * they will be zoomed in(maximized) to max allowed size. In case the width
     * and height are bigger than max allowed size they will be zoomed
     * out(minimized) to max allowed size
     *
     * @param width           object's width
     * @param height          object's height
     * @param userZoomedIndex user's prefer zoomed index
     */
    public Resize(int width, int height, int userZoomedIndex) {
        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int widthSc = gd.getDisplayMode().getWidth();
        int heightSc = gd.getDisplayMode().getHeight();
        double ratioW;
        double ratioH;
        double cropRatio;

        /* did the user saved its zoomed preference? */
        if (userZoomedIndex == Integer.MAX_VALUE) {
            ratioW = (double) width / (widthSc * MAX_SCREEN_PERCENT_RESIZE);
            ratioH = (double) height / (heightSc * MAX_SCREEN_PERCENT_RESIZE);
            cropRatio = (double) width / (double) height;
        } else {
            int tmpWidth = Math.abs(width) + userZoomedIndex * Math.abs(width / ZOOMING_FACTOR);
            int tmpHeight = Math.abs(height) + userZoomedIndex * Math.abs(height / ZOOMING_FACTOR);

            ratioW = (double) width / (double) tmpWidth;
            ratioH = (double) height / (double) tmpHeight;
            cropRatio = (double) width / (double) height;
        }

        computeResizeRatios(ratioW, ratioH, cropRatio, width, height, widthSc, heightSc);
    }

    /**
     * Resize the width and height to fit the max display size In case the
     * provided width and height are smaller than max allowed size they will be
     * zoomed in(maximized) to max allowed size. In case the width and height
     * are bigger than max allowed size they will be zoomed out(minimized) to
     * max allowed size
     *
     * @param currentWidth  object's width
     * @param currentHeight object's height
     * @param newWidth      - the wanted width for the object
     * @param newHeight     - the wanted height for the object
     */
    public Resize(int currentWidth, int currentHeight, int newWidth, int newHeight) {
        // compute the ratio for width and height
        this.ratioWidth = (double) currentWidth / (double) newWidth;
        this.ratioHeight = (double) currentHeight / (double) newHeight;

        this.zoomingIndex = ((int) ((double) currentHeight / ratioHeight) - currentHeight) / Math.abs(currentHeight / ZOOMING_FACTOR);

        this.zoomingWidth = currentWidth + zoomingIndex * Math.abs(currentWidth / ZOOMING_FACTOR);
        this.zoomingHeight = currentHeight + zoomingIndex * Math.abs(currentHeight / ZOOMING_FACTOR);

        this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) newWidth);
        this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) newHeight);
    }

    /**
     *
     * @param ratioW max possible ratio on width, such as the image will not use
     * more than a certain percentage of the screen width
     * @param ratioH max possible ratio on height, such as the image will not
     * use more than a certain percentage of the screen height
     * @param cropRatio the ratio of the crop; the ratio between the original
     * width and height of the crop
     * @param width object's width
     * @param height object's height
     * @param widthSc the width of the screen
     * @param heightSc the height of the screen
     */
    private void computeResizeRatios(double ratioW, double ratioH, double cropRatio, int width, int height, int widthSc, int heightSc) {
        /* keep original aspect ratio            |-|     |-----|
         * 1.75 = objects allmost squared        | |  or |     |
         * < 1.75 include all objects which are  |_|     |_____|
         */
        if (cropRatio < 1.75) {
            this.ratioWidth = ratioH;
            this.ratioHeight = ratioH;
            this.zoomingIndex = ((int) ((double) height / ratioH) - height) / Math.abs(height / ZOOMING_FACTOR);
            this.zoomingWidth = width + zoomingIndex * Math.abs(width / ZOOMING_FACTOR);
            this.zoomingHeight = height + zoomingIndex * Math.abs(height / ZOOMING_FACTOR);

            this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) (widthSc * MAX_SCREEN_PERCENT_RESIZE));
            this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) (heightSc * MAX_SCREEN_PERCENT_RESIZE));
        } else {
            this.ratioWidth = ratioW;
            this.ratioHeight = ratioW;
            this.zoomingIndex = ((int) ((double) width / ratioW) - width) / Math.abs(width / ZOOMING_FACTOR);
            this.zoomingWidth = width + zoomingIndex * Math.abs(width / ZOOMING_FACTOR);
            this.zoomingHeight = height + zoomingIndex * Math.abs(height / ZOOMING_FACTOR);

            this.zoomingWidth = Utils.limit(10, this.zoomingWidth, (int) (widthSc * MAX_SCREEN_PERCENT_RESIZE));
            this.zoomingHeight = Utils.limit(10, this.zoomingHeight, (int) (heightSc * MAX_SCREEN_PERCENT_RESIZE));
        }
    }

    /**
     * Resizes an image to the wanted size, based on the provided resize ratio.
     * The resize is done the same for width and height.
     *
     * @param origImg - the image to be resized
     * @return - returns the new resized image
     */
    public BufferedImage resizeImage(BufferedImage origImg) {
        if ((origImg == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        int w1 = origImg.getWidth();
        int h1 = origImg.getHeight();
        int w2 = (int) (w1 / ratioWidth);
        int h2 = (int) (h1 / ratioHeight);
        float wRatio = (float) w2 / (float) w1;
        float hRatio = (float) h2 / (float) h1;

        BufferedImage workImg = new BufferedImage(w2, h2, BufferedImage.TYPE_3BYTE_BGR);

        // for down-scale
        if ((ratioWidth >= 1.0f) && (ratioHeight >= 1.0f)) {
            for (int y = 0; y < h2; y++) {
                for (int x = 0; x < w2; x++) {
                    workImg.setRGB(x, y, origImg.getRGB((int) (x * ratioWidth + 0.5f), (int) (y * ratioHeight + 0.5f)));
                }
            }
        } else {
            // for up-scale
            for (int y = 0; y < h2; y++) {
                for (int x = 0; x < w2; x++) {
                    workImg.setRGB(x, y, origImg.getRGB((int) (x / wRatio), (int) (y / hRatio)));
                }
            }
        }

        return workImg;
    }

    /**
     * Computes the corespondent position in the original image, of the given
     * pixel from the resized image.
     *
     * @param x - the X coordinate of the pixel from the resized image
     * @param y - the Y coordinate of the pixel from the resized image
     * @return - returns the corresponding point in the original image
     */
    public Point resizedToOriginal(int x, int y) {
        Point origPoint = new Point();
        origPoint.setLocation((int) (x * ratioWidth), (int) (y * ratioHeight));
        return origPoint;
    }

    /**
     * Computes the corespondent value in the original image, of the given value
     * from the resized image.
     *
     * @param value the value from the resized image
     * @return the corresponding value in the original image
     */
    public int resizedToOriginal(int value) {
        return (int) (value * ratioWidth);
    }

    /**
     * Computes the corespondent dimension in the original image, of the given
     * dimension from the resized image.
     *
     * @param origImgDim the dimension from the resized image
     * @return the corresponding dimension in the original image
     */
    public Dimension resizedToOriginal(Dimension origImgDim) {
        int width = (int) Math.round(origImgDim.width * ratioWidth);
        int height = (int) Math.round(origImgDim.height * ratioHeight);
        return new Dimension(width, height);
    }

    /**
     * Computes the corespondent rectangle in the original image, of the given
     * rectangle from the resized image.
     *
     * @param resizedRectangle the rectangle from the resized image
     * @return the corresponding rectangle in the original image
     */
    public Rectangle resizedToOriginal(Rectangle resizedRectangle) {
        Rectangle origiRectangle = new Rectangle(0, 0, 0, 0);

        int x = (int) Math.round(resizedRectangle.x * ratioWidth);
        int y = (int) Math.round(resizedRectangle.y * ratioHeight);
        int xRightPanel = resizedRectangle.x + resizedRectangle.width;
        int yRightPanel = resizedRectangle.y + resizedRectangle.height;
        int width = ((int) Math.round(xRightPanel * ratioWidth) - x);
        int height = ((int) Math.round(yRightPanel * ratioHeight) - y);

        origiRectangle.setBounds(x, y, width, height);

        return origiRectangle;
    }

    /**
     * Computes the corespondent polygon in the original image, of the given
     * polygon from the resized image.
     *
     * @param polyResized the polygon from the resized image
     * @return the corresponding polygon in the original image
     */
    public Polygon resizedToOriginal(Polygon polyResized) {
        if ((polyResized == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Polygon polyOrig = new Polygon();

        for (int index = 0; index < polyResized.npoints; index++) {
            Point point = resizedToOriginal(polyResized.xpoints[index], polyResized.ypoints[index]);
            polyOrig.addPoint(point.x, point.y);
        }

        return polyOrig;
    }

    /**
     * Computes the corespondent position in the original image, of the given
     * pixel from the resized image.
     *
     * @param resizedPoint - the (x,y) coordinate of the pixel from the resized image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the original image (in this order)
     */
    public Point resizedToOriginal(Point resizedPoint) {
        if ((resizedPoint == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Point origPoint = new Point();
        origPoint.setLocation((int) (resizedPoint.getX() * ratioWidth), (int) (resizedPoint.getY() * ratioHeight));
        return origPoint;
    }

    /**
     * Computes the corespondent value in the resized image, of the given value
     * from the original image.
     *
     * @param value the value from the original image
     * @return the corresponding value in the resized image
     */
    public int originalToResized(int value) {
        return (int) (value / ratioWidth);
    }

    /**
     * Computes the corespondent dimension in the resized image, of the given
     * dimension from the original image.
     *
     * @param origImgDim the dimension from the original image
     * @return the corresponding dimension in the resized image
     */
    public Dimension originalToResized(Dimension origImgDim) {
        int width = (int) Math.round(origImgDim.width / ratioWidth);
        int height = (int) Math.round(origImgDim.height / ratioHeight);
        return new Dimension(width, height);
    }

    /**
     * Computes the corespondent rectangle in the resized image, of the given
     * rectangle from the original image.
     *
     * @param origRectangle the rectangle from the original image
     * @return the corresponding rectangle in the resized image
     */
    public Rectangle originalToResized(Rectangle origRectangle) {
        Rectangle resizedRectangle = new Rectangle(0, 0, 0, 0);
        int x = (int) Math.round(origRectangle.x / ratioWidth);
        int y = (int) Math.round(origRectangle.y / ratioHeight);

        int xRightOrig = origRectangle.x + origRectangle.width;
        int yRightOrig = origRectangle.y + origRectangle.height;
        int width = ((int) Math.round(xRightOrig / ratioWidth) - x);
        int height = ((int) Math.round(yRightOrig / ratioHeight) - y);

        resizedRectangle.setBounds(x, y, width, height);

        return resizedRectangle;
    }

    /**
     * Computes the corespondent polygon in the resized image, of the given
     * polygon from the original image.
     *
     * @param polyOrig the polygon from the original image
     * @return the corresponding polygon in the resized image
     */
    public Polygon originalToResized(Polygon polyOrig) {
        if ((polyOrig == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Polygon polyResized = new Polygon();

        for (int index = 0; index < polyOrig.npoints; index++) {
            Point point = originalToResized(polyOrig.xpoints[index], polyOrig.ypoints[index]);
            polyResized.addPoint(point.x, point.y);
        }

        return polyResized;
    }

    /**
     * Computes the corespondent position in the resized image, of the given
     * pixel from the original image.
     *
     * @param x - the (x,y) coordinate of the pixel from the original image
     * @param y - the (x,y) coordinate of the pixel from the original image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the resized image (in this order)
     */
    public Point originalToResized(int x, int y) {
        return originalToResized(new Point(x, y));
    }

    /**
     * Computes the corespondent position in the resized image, of the given
     * pixel from the original image.
     *
     * @param origPoint - the (x,y) coordinate of the pixel from the original image
     * @return - returns a Point object with two elements: x and y coordinates of the point in the resized image (in this order)
     */
    public Point originalToResized(Point origPoint) {
        if ((origPoint == null) || (Double.compare(ratioWidth, 0.0) == 0) || (Double.compare(ratioHeight, 0.0) == 0)) {
            return null;
        }

        Point resizedPoint = new Point();
        resizedPoint.setLocation((int) (origPoint.getX() / ratioWidth), (int) (origPoint.getY() / ratioHeight));
        return resizedPoint;
    }

    /**
     * Gets ratio width.
     *
     * @return the ratio width
     */
    public double getRatioWidth() {
        return ratioWidth;
    }

    /**
     * Gets ratio height.
     *
     * @return the ratio height
     */
    public double getRatioHeight() {
        return ratioHeight;
    }

    /**
     * Increment the width ratio with the given value.
     *
     * @param value - the value used for incrementing the width. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioWidth(double value) {
        ratioWidth = ratioWidth + value;
    }

    /**
     * Increment the height ratio with the given value.
     *
     * @param value - the value used for incrementing the height. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioHeight(double value) {
        ratioHeight = ratioHeight + value;
    }

    /**
     * Increment both the width and height ratio with the given value.
     *
     * @param value - the value used for incrementing the width and height. When the value is positive, an increment happens; when the value is negative, a decrement happens.
     */
    public void incrementRatioWidthHeight(double value) {
        ratioWidth = Math.max((ratioWidth + value), 0.1);
        ratioHeight = Math.max((ratioHeight + value), 0.1);
    }

    /**
     * Compute the zoomed width and height based on the crop resolution and
     * zooming factor
     *
     * @param crop resolution of the crop
     * @return the zoomingIndex for the current object
     */
    public int incrementWidthHeight(Dimension crop) {

        /* chek if the user zooms in or out */
        if (crop.width > 0) {
            zoomingIndex++;
        } else {
            zoomingIndex--;
        }

        /* no zooming has been made */
        if (zoomingIndex == 0) {
            zoomingWidth = Math.abs(crop.width);
            zoomingHeight = Math.abs(crop.height);
        } /* zoomed image; recompute the size of the window */ else {
            zoomingWidth = Math.abs(crop.width) + zoomingIndex * Math.abs(crop.width / ZOOMING_FACTOR);
            zoomingHeight = Math.abs(crop.height) + zoomingIndex * Math.abs(crop.height / ZOOMING_FACTOR);
        }

        /* compute the new ratios based on the new size of the window */
        ratioWidth = (double) Math.abs(crop.width) / (double) zoomingWidth;
        ratioHeight = (double) Math.abs(crop.height) / (double) zoomingHeight;

        return zoomingIndex;
    }

    /**
     * Returns true if the two objects have the same resize ratio in both
     * coordinates.
     *
     * @param resizeObj - the object with which the current object should be
     * compared with
     * @return - true when the objects match (have the same ratio on width and
     * height) and false otherwise
     */
    @Override
    public boolean equals(Object resizeObj) {
        if ((resizeObj != null) && (resizeObj instanceof Resize)) {
            Resize resize = (Resize) resizeObj;
            return ((Double.compare(this.ratioWidth, resize.ratioWidth) == 0) && (Double.compare(this.ratioHeight, resize.ratioHeight) == 0));
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 67 * hash + (int) (Double.doubleToLongBits(this.ratioWidth) ^ (Double.doubleToLongBits(this.ratioWidth) >>> 32));
        hash = 67 * hash + (int) (Double.doubleToLongBits(this.ratioHeight) ^ (Double.doubleToLongBits(this.ratioHeight) >>> 32));
        return hash;
    }

    /**
     * Check if by incrementing the size of the image, it will not become bigger
     * than the screen itself.
     *
     * @param crop      resolution of the crop
     * @param screenRes - the resolution of the current screen
     * @return - true if the resize shall be done; false if by increasing the size of the image, it will generate a bigger image than the screen
     */
    public boolean isSizeIncreaseOK(Dimension crop, Dimension screenRes) {
        int w2 = zoomingWidth + (crop.width / ZOOMING_FACTOR);
        int h2 = zoomingHeight + (crop.height / ZOOMING_FACTOR);

        /* limit the zoom in to not go beyond screen's resolution */
        return ((w2 <= (screenRes.width * MAX_SCREEN_PERCENT_RESIZE)) && (h2 <= (screenRes.height * MAX_SCREEN_PERCENT_RESIZE)));
    }

    /**
     * Check if by decreasing the size of the image, it will not become smaller
     * than the minimum allowed crop resolution.
     *
     * @param crop resolution of the crop
     * @return - true if the resize shall be done; false if by increasing the size of the image, it will generate a bigger image than the screen
     */
    public boolean isSizeDecreaseOK(Dimension crop) {
        int w2 = zoomingWidth + (crop.width / ZOOMING_FACTOR);
        int h2 = zoomingHeight + (crop.height / ZOOMING_FACTOR);

        /* limit the zooming out to a minimum 10x10 pixels */
        return ((w2 > 10) && (h2 > 10));
    }

    /**
     * Gets zooming factor.
     *
     * @return the zooming Factor
     */
    public int getZoomingFactor() {
        return ZOOMING_FACTOR;
    }

    /**
     * Resize the input rectangle with the resize ratio.
     *
     * @param rectangle - the rectangle to be resized
     * @return - the resize rectangle
     */
    public Rectangle resizeBox(Rectangle rectangle) {
        Rectangle resizedBox = new Rectangle(0, 0, 0, 0);

        int x = (int) Math.round(rectangle.x / ratioWidth);
        int y = (int) Math.round(rectangle.y / ratioHeight);
        int xRightPanel = rectangle.x + rectangle.width;
        int yRightPanel = rectangle.y + rectangle.height;

        int width = ((int) Math.round(xRightPanel / ratioWidth) - x);
        int height = ((int) Math.round(yRightPanel / ratioHeight) - y);

        resizedBox.setBounds(x, y, width, height);

        return resizedBox;
    }
} 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 CustomTreeNode<T extends Serializable> implements Serializable {

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

    /**
     * The data saved in the root of the node.
     */
    private T root;

    /**
     * The parent tree of the current node.
     */
    private CustomTreeNode parent;

    /**
     * The children of the node.
     */
    private List<CustomTreeNode> children;

    /**
     * Create a new node or a new tree.
     *
     * @param root the data saved in the root of the node
     */
    public CustomTreeNode(T root) {
        this.root = root;
        this.children = new LinkedList<>();
    }

    /**
     * Return the data saved in the current node.
     *
     * @return the data saved in the root of the node
     */
    public T getRoot() {
        return root;
    }

    /**
     * Set the data of the current node.
     *
     * @param root the new data of the current node
     */
    public void setRoot(T root) {
        this.root = root;
    }

    /**
     * Get the parent of the current node.
     *
     * @return the parent node of the current one
     */
    public CustomTreeNode getParent() {
        return parent;
    }

    /**
     * Set the parent of the current node.
     *
     * @param parent the parent node of the current one
     */
    private void setParent(CustomTreeNode parent) {
        this.parent = parent;
    }

    /**
     * Add a child to the current node.
     *
     * @param child the data saved in the root of the child node
     * @return the newly created child
     */
    public CustomTreeNode addChild(T child) {
        CustomTreeNode childNode = new CustomTreeNode(child);
        childNode.parent = this;
        this.children.add(childNode);

        return childNode;
    }

    /**
     * Add a child to the current node.
     *
     * @param child the child node to be added to the current one
     */
    public void addChild(CustomTreeNode child) {
        child.setParent(this);
        this.children.add(child);
    }

    /**
     * Add a list of children nodes to the current node.
     *
     * @param children the list of children nodes to be added to the current one
     */
    public void addChildren(List<CustomTreeNode> children) {
        for (CustomTreeNode child : children) {
            child.setParent(this);
        }
        this.children.addAll(children);
    }

    /**
     * Add a list of strings as children of the current node.
     *
     * @param children the list of children strings to be added to the current
     * one (the nodes are created based on the list of input strings)
     */
    public void addChildrenStr(List<T> children) {
        for (T child : children) {
            addChild(child);
        }
    }

    /**
     * Return the child node with the given root name.
     *
     * @param rootName the data saved in the root of the searched node
     * @return the child node of the current one, containing the given root node
     */
    public CustomTreeNode getChild(T rootName) {
        for (CustomTreeNode child : children) {
            if (rootName.equals(child.getRoot())) {
                return child;
            }
        }
        return null;
    }

    /**
     * Return the child node with the given root name.
     *
     * @param rootName the string saved in the root of the searched node
     * @return the child node of the current one, containing the given root node
     */
    public CustomTreeNode getChild(String rootName) {
        for (CustomTreeNode child : children) {
            if (rootName.equalsIgnoreCase(child.getRoot().toString())) {
                return child;
            }
        }
        return null;
    }

    /**
     * Build a list of children, containing just the data from the nodes.
     *
     * @return a list containing the data of all children nodes
     */
    public List<T> getChildren() {
        List<T> childrenList = new ArrayList<>();

        children.stream().forEach(child -> childrenList.add((T) child.root));

        return childrenList;
    }

    /**
     * Build a list of children nodes.
     *
     * @return a list containing the children nodes
     */
    public List<CustomTreeNode<T>> getChildrenNodes() {
        List<CustomTreeNode<T>> childrenList = new ArrayList<>();

        children.stream().forEach(child -> childrenList.add(child));

        return childrenList;
    }

    /**
     * Remove the specified child.
     *
     * @param child the child node to be removed
     */
    public void removeChild(CustomTreeNode child) {
        children.remove(child);
    }

    /**
     * Remove all the children of the current node.
     */
    public void removeAllChildren() {
        children = new LinkedList();
    }

    /**
     * Check the list of children and return the index of the wanted child.
     *
     * @param child the child for which the index is wanted
     * @return the index of the child, in the list of children of the current
     * node
     */
    public int getIndexOfChild(CustomTreeNode child) {
        return children.indexOf(child);
    }

    /**
     * Returns the child from the specified index, from the list of children of
     * the current node.
     *
     * @param index the index of the wanted child
     * @return a node representing the child of the current node, with the
     * specified index
     */
    public CustomTreeNode getChildAt(int index) {
        return children.get(index);
    }

    /**
     * Get the number of children of the current node.
     *
     * @return the number of children of the current node
     */
    public int getChildCount() {
        return children.size();
    }

    /**
     * Check if the current node is final / leaf.
     *
     * @return true if the current nod is final / has no children; false
     * otherwise
     */
    public boolean isLeaf() {
        return (children.isEmpty());
    }

    /**
     * Returns the path from the root to this node. The last element in the path
     * is the current node and the first one is the root of the tree.
     *
     * @return an object array of CustomTreeNode objects specifying the path
     * from the root node to the current node (where the first element in the
     * path is the tree root and the last element is the current node)
     */
    public Object[] getPath() {
        List path = new ArrayList();
        // add the current node
        path.add(this);

        // add all the other nodes, till the tree root
        CustomTreeNode father = getParent();
        while (father != null) {
            path.add(father);
            father = father.getParent();
        }

        // reverse the list because the tree node has to be first and the current node, last
        Collections.reverse(path);

        return path.toArray();
    }

    /**
     * Returns the path from the root to this node. The last element in the path
     * is the current node and the first one is the root of the tree.
     *
     * @return the TreePath from the tree root to the current node
     */
    public TreePath getTreePath() {
        return new TreePath(getPath());
    }

    @Override
    public String toString() {
        return root.toString();
    }
}
import common.Constants;
import common.ConstantsLabeling;
import common.UserPreferences;
import common.Utils;
import gui.support.ObjectBBox;
import gui.support.ObjectPolygon;
import gui.support.ObjectScribble;
import gui.support.Objects;
import gui.support.ScreenResolution;
import library.BoxLRTB;
import java.awt.Point;
import java.awt.Rectangle;
import library.Resize;
import observers.NotifyObservers;
import observers.ObservedActions;
import gui.support.CustomTreeNode;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
/*
 * The MIT License
 *
 * Copyright 2017 Olimpia Popica, Benone Aligica
 *
 * Contact: contact[a(t)]annotate[(d){o}t]zone
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package gui.viewer;


/**
 * The type Bounding box window.
 *
 * @author Olimpia Popica
 */
public final class BoundingBoxWindow extends javax.swing.JDialog {

    /**
     * The event dispatcher for the keyboard in order to execute specific tasks
     * for the crop window.
     */
    private transient BBoxWinKeyEventDispatcher boxKeyEventDispatch;

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

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

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

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

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

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

    /**
     * Used to resize the image for better understanding.
     */
    private transient Resize resize;

    /**
     * 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();

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

    /**
     * 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 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.
     */
    private Color objectColor;

    /**
     * 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.
     */
    private final List<Color> objColorsList;

    /**
     * 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).
     */
    private int borderPX;

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

    /**
     * The text being displayed on the histogram button when the image has been
     * enhanced and the user can go to the original one.
     */
    private static final String HISTO_ORIGINAL = "Original";

    /**
     * The text being displayed on the histogram button when the image shown is
     * original, but can be enhanced.
     */
    private static final String HISTO_HIGHLIGHT = "Highlight";

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

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

    /**
     * Creates new form BoundingBoxWindow
     *
     * @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 BoundingBoxWindow(java.awt.Frame parent,
            BufferedImage frameImage,
            Objects currentObj,
            CustomTreeNode objectAttributes,
            ObservedActions.Action actionOwner,
            List<Color> objColorsList,
            UserPreferences userPreferences) {
        super(parent, true);

        initComponents();

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

        // save the original image
        this.frameImg = frameImage;
        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();

        // save the user prederences
        this.userPrefs = userPreferences;

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

        initAttributesComponents();

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

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

        initOtherVariables();

        initResolutions();

        prepareFrame();
    }

    /**
     * Get the piece of image representing the object from the frame image.
     */
    private void displayImage() {
        displayBox = getBorderedSize(currentObject.getOuterBBox(), borderPX);

        // get the selected box from the whole image
        this.origImg = Utils.getSelectedImg(frameImg, displayBox);

        this.resize = new Resize(displayBox.width, displayBox.height, currentObject.getUserPreference().getZoomingIndex());

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

        prepareFrame();
    }

    /**
     * Display the image with the segmented box.
     */
    private void showImage() {
        // resize image with the ratio relative to the original image, in order to prevent data loose
        workImg = resize.resizeImage(origImg);

        // if the image was highlighted, do it again
        if (HISTO_ORIGINAL.equals(jBHistogramEq.getText())) {
            workImg = Utils.histogramEqColor(workImg);
        }

        // get the graphics of the image
        Graphics2D g2d = workImg.createGraphics();

        if ((currentObject instanceof ObjectBBox) || (currentObject instanceof ObjectScribble)) {
            // draw the outer box of the object
            Rectangle bBox = drawOuterBox(g2d);

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

        } else if (currentObject instanceof ObjectPolygon) {